C# 非同步Socket (BeginXXXX)伺服器代碼

来源:http://www.cnblogs.com/AssertionBird/archive/2016/07/29/5717777.html
-Advertisement-
Play Games

前言: 1、最近維護公司的一個舊項目,是Socket通訊的,主要用於接收IPC(客戶端)發送上來的抓拍圖像,期間要保持通訊,監測數據包併進行處理。但是看之前那人寫的代碼個人覺得並不是很適合自己,於是就重寫了,不過項目暫時棄置了,為了以後能夠方便使用,也方便更多像我一樣還是渣渣程式員的人,記錄一些心得 ...


前言:

1、最近維護公司的一個舊項目,是Socket通訊的,主要用於接收IPC(客戶端)發送上來的抓拍圖像,期間要保持通訊,監測數據包併進行處理。但是看之前那人寫的代碼個人覺得並不是很適合自己,於是就重寫了,不過項目暫時棄置了,為了以後能夠方便使用,也方便更多像我一樣還是渣渣程式員的人,記錄一些心得。我還是堅信那句話,分享才能夠進步得更快

 

2、其實在做之前我對這個東西瞭解也很少,畢竟以我的認識,在國內C#更多地是用來開髮網站,於是也在網上看了很多前輩貼的代碼,我嘗試過直接複製粘貼,發現很多都用不了最後自己寫了才發現原來是這麼一回事。所以在這裡也奉勸各位不要做伸手黨,通過自己的理解才能夠將別人的知識轉變成為自己的資本

 

開始

1、Socket 通信分為同步和非同步兩種模式,客戶端一般用同步,非同步多用於服務端。至於兩者的區別,很多前輩已經有博客闡述過了,我這裡就貼其中一個鏈接幫助大家理解:http://www.cnblogs.com/BLoodMaster/archive/2010/07/02/1769774.html。萬一這個鏈接失效,相信大家也會搜索到其他資源的

 

2、做這種Socket通信,我覺得關鍵點在於【數據處理】而不是建立鏈接,但往往很多不瞭解人會遇到各種困難,比如Socket鏈接關閉,丟包,粘包之類的。通用做法就是

  2.1:使用緩衝區,將接收到的位元組數組全部儲存起來,再去分析數據,獲取有效部分。

  2.2:伺服器跟客戶端需要約定好報文的格式,一般來說至少要有【數據頭標識】、【數據實體長度】、【數據尾標識】,這樣才能更好地對數據進行處理,否則你很難界定到底哪一段數據才是你想要的。

  2.3:數據分析一定要少用字元串或者字元拆解,因為這樣很有可能獲取到的長度是錯誤的(裡面有很多空位元組,或者編碼解碼問題導致字元串長度不一致),所以一定要用位元組數組來處理,通過約定好的編碼解碼格式,查找標識所在位置進行數據拆解。【比如:客戶端跟伺服器連接需要核對身份信息,因此對一字元串進行的特殊加密再編碼發送,伺服器接收到數據之後如果先編譯成了字元串再獲取密碼轉成byte[],長度跟之前記錄的很可能就會不一樣】。不過對於獲取報文頭信息這一類通過字元串來拆解更為方便

3、在建立連接時通常發生的問題就是沒有迴圈接收客戶端消息導致socket被釋放,或者只接受到客戶端了幾條消息之後就停了,我也遇到過這個問題,所以建立連接的關鍵在於BeginAccept和BeginReceive兩個方法的遞歸迴圈調用

4、接下來就是貼代碼了,這裡只有伺服器的,客戶端的我還沒有做以後補全了會發上來,入口函數是公開的,主要方法全部在這裡,至於輔助函數(比如獲取IP地址,檢查埠是否被占用、獲取發送消息大家可以自己去找一下吧,畢竟這些已經有很多前輩開源了)

  4.1:由於本人只是一渣渣野生程式猿,如果大家有什麼不同的看法請提出來,交流也是一種很好的進步方式

  4.2:如果大家覺得有用,還是希望能夠推薦一下,讓更多新人看到,讓大家能夠彼此學習,也不枉費我寫博客的力氣,謝謝

  4.3:我更希望有大神來告訴我不足的地方,因為我知道這些代碼寫得並不好~

public void Create_Server_Socket()
        {
            Thread th_server = null;
            string result = string.Empty;
            try
            {
                socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress ip = Pubilc.Helper.Computer.GetIPv4();
                IPEndPoint point = new IPEndPoint(ip, this._port);
                socketServer.Bind(point);
                socketServer.Listen(this._listen);
                th_server = new Thread(() =>
                {
                    if (this._isRunning)
                        socketServer.BeginAccept(AcceptCallBack, socketServer);
                    else
                        Close_Server_Socket();
                });
                th_server.IsBackground = true;
                th_server.Start();
            }
            catch (Exception ex)
            {
                if (th_server.IsAlive)
                    th_server.Abort();
                ShowLog(ex.Message);
            };
        }
        public void Close_Server_Socket()
        {
            if (socketServer != null)
            {
                socketServer.Close();
            }
        }

        void AcceptCallBack(IAsyncResult ar)
        {
            Socket socketServer = (Socket)ar.AsyncState;
            try
            {
                Socket socketAccept = socketServer.EndAccept(ar);
                DynamicBuffer state = new DynamicBuffer();
                state.workSocket = socketAccept;
                socketAccept.BeginReceive(state.Buffers, 0, state.Buffers.Length, SocketFlags.None, ReceiveCallBack, state);
            }
            catch(Exception ex)
            {
                LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
                ShowLog("AcceptCallBack" + ex.Message);
            }
            finally
            {
                if (this._isRunning)
                    socketServer.BeginAccept(AcceptCallBack, socketServer);
            }

        }
        void ReceiveCallBack(IAsyncResult ar)
        {
            string sendMsg = string.Empty;
            DynamicBuffer state = (DynamicBuffer)ar.AsyncState;
            Socket socketAccept = state.workSocket;
            try
            {
                int len = socketAccept.EndReceive(ar);
                if (len > 0)
                {
                    state.WritBuffer();
                }
                else
                {
                    sendMsg=doMsg.CheckConnection(socketAccept.RemoteEndPoint.ToString());
                    if (!string.IsNullOrEmpty(sendMsg))
                    {
                        byte[] buffer = Encoding.Default.GetBytes(sendMsg);
                        state.sb.AddRange(buffer);
                    }
                    else
                    {
                        socketAccept.Shutdown(SocketShutdown.Both);
                        socketAccept.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
                ShowLog("ReceiveCallBack" + ex.Message);
            }
            finally
            {
                if (this._isRunning)
                {
                    try
                    {
                        Thread th_send = null;
                        th_send = new Thread(() =>
                        {
                            Send(ref state);
                            th_send.Abort();
                        });
                        th_send.IsBackground = true;
                        th_send.Start();
                        socketAccept.BeginReceive(state.Buffers, 0, state.Buffers.Length, SocketFlags.None, ReceiveCallBack, state);
                    }
                    catch (Exception ex)
                    {
                        showLog(ex.Message);
                        LogHelper.WriteLog(typeof(IpcServerDo), ex);
                    }
                }
            }
        }
        void Send(ref DynamicBuffer state)
        {
            byte[] buffer = null;
            string sendMsg = string.Empty;
            Socket socketAccept = state.workSocket;
            Dictionary<string, byte[]> data;
            state.GetAllData(out data);
            if (data.Count > 0)
            {
                sendMsg = doMsg.AcceptFromIpc(socketAccept.RemoteEndPoint.ToString(), ref data);
                if (!string.IsNullOrEmpty(sendMsg))
                {
                    buffer = Encoding.Default.GetBytes(sendMsg.ToCharArray());
                    socketAccept.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallBack, socketAccept);
                }
                Send(ref state);
            }
        }
        void SendCallBack(IAsyncResult ar)
        {
            Socket socketAccept = (Socket)ar.AsyncState;
            try
            {
                int sendBytes = socketAccept.EndSend(ar);
            }
            catch(Exception ex)
            {
                LogHelper.WriteLog(typeof(SocketHelper_Server), ex);
            }
        }

4、下麵是State對象類,其實這個就是相當於緩衝區,將東西放到這裡來保存,包括socket對象,其實有用的就是上面的聲明部分和構造函數,至於下麵的寫入方法和數據拆解,緩衝區清理的方法我都覺得有待提高,因為用這種方法我在接收一張500KB左右的圖片竟然要10秒才有返回結果(整個邏輯流程完成併發送數據的時間),這是不可以接受的,所以下麵的數據處理方法,大家見仁見智吧,根據自己的需求去找到自己適合的方法,這裡只是提供思路

/// <summary>
    /// 迴圈隊列
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DynamicBuffer
    {
        /// <summary>
        /// 緩衝區大小
        /// </summary>
        public int bufferSize { get; set; }
       /// <summary>
       /// 通訊對象
       /// </summary>
        public Socket workSocket { get; set; }
        /// <summary>
        /// 緩衝區
        /// </summary>
        public byte[] Buffers { get; set; }
        /// <summary>
        /// 儲存區
        /// </summary>
        public List<byte> sb;
        public DynamicBuffer()
        {
            this.bufferSize = 1024 *64;
            this.sb = new List<byte>();
            this.Buffers = new byte[bufferSize];
        }
        /// <summary>
        /// 將緩衝區數據放入儲存區
        /// </summary>
        public void WritBuffer()
        {
            int endPosition = 0;
            byte[] buffer;
            int len = 0;
            for (endPosition = this.Buffers.Length - 1; endPosition >= 0; endPosition--)
            {
                //由於發現數據中有很多\0的空位元組,此處從最後開始執行反向刪除
                if (this.Buffers[endPosition] != (byte)'\0')
                    break;
            }
            len = endPosition + 1;//因為是取長度,而不是位置,因此此處+1
            buffer = new byte[len];
            Array.Copy(this.Buffers, 0, buffer, 0, len);
            this.sb.AddRange(buffer);
            this.Buffers = new byte[bufferSize];
        }

        //返回指定的長度的byet[]數據,並會清理對應的儲存區
        public void GetAllData(out Dictionary<string, byte[]> data)
        {
            int DataLength = 0;
            DataLength = GetAllAcceptMsg(out data);
            if (DataLength > 0)
            {
               ClearList(DataLength);
            }
        }
        //清理儲存區已經發送的內容以及空包數據,以便下一次接收使用
        private void ClearList(int DataLength)
        {
            this.sb.RemoveRange(0, DataLength);
        }
        /// <summary>
        /// 判斷儲存區是否有接受完整的包
        /// 並將完整的包存放,拆解
        /// </summary>
        /// <returns>單次有效數據的長度</returns>
        private int GetAllAcceptMsg(out Dictionary<string, byte[]> data)
        {
            int DataLength=0;
            int contentLength = 0;
            int newlinePosition = 0;
            string title = string.Empty;
            string arr_msg = string.Empty;
            byte[] buffer = this.sb.ToArray();
            data = new Dictionary<string, byte[]>();
            try
            {
                newlinePosition = StaticHelp.Search(buffer, Encoding.Default.GetBytes("\r\n\r\n\r\n"), false);//查找換行的位置
                if (newlinePosition > 0)
                {
                    arr_msg = Encoding.UTF8.GetString(buffer);
                    contentLength = Convert.ToInt32(CommonHelper.GetStrByRegex(arr_msg, "Content-Length: ", "\r\n"));
                    int dataLen = buffer.Length - newlinePosition;
                    if (dataLen >= contentLength)
                    {
                        title = arr_msg.Split(new string[] { "\r\n\r\n\r\n" }, StringSplitOptions.None)[0] + "\r\n";
                        byte[] postData = new byte[contentLength];
                        if (contentLength > 0)
                            Buffer.BlockCopy(buffer, newlinePosition, postData, 0, contentLength);
                        data.Add(title, postData);
                        DataLength = newlinePosition + contentLength;
                    }
                }
                else
                {
                    DataLength = 0;
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog(typeof(IpcServerDo), ex);
            }
            return DataLength;
        }
    }        

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 寫這點東西主要是看到知乎上有人在討論相關的問題,但是有不少人都在說一些不嚴謹,甚至是完全錯誤 但是流傳甚廣的東西,甚至是一些大神都在說,以下根據我的回答總結。 一個很常見又很低級的誤區是:認為引用類型保存在堆上,值類型保存在棧上……其實這個問題幼稚得我懶得說……但是實在又忍不住吐槽。 很多人都在說這 ...
  • 開篇 每一個版本的.net都會引入一些新的特性,這些特性方便開發人員能夠快速實現一些功能。雖然.net版本一直在更新,但是新版本對舊版本的程式都是相容的,在這一點上微軟做的還是非常好的。每次學一個新內容,第一次接觸的方法在腦海裡占的位置還是比較重要的,從剛開始接觸.net的多線程編程是使用Threa ...
  • Ajax製作智能提示搜索[js效果,後續博客園會更下麵顯示的部分,只是一個簡單的搜索結果,真正的大型網站用的是lucene全文搜索引擎。 歡迎關註博客園wangwangwangMax, 新浪:NewITdog, 微信公眾號:wangwangwangMax ...
  • 初入碼田--ASP.NET MVC4 Web應用開發之一 實現簡單的登錄 初入碼田--ASP.NET MVC4 Web應用開發之二 實現簡單的增刪改查 2016-07-29 在次之前,需要一臺電腦(~ ̄▽ ̄)~,以及Visual Studio 2013或者更高版本,SQL Sever資料庫(暫時不用 ...
  • 當我們在後臺得到一個字元串後比如字元串內容是:哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈 我們要在每兩個或者多個加入一個逗號或者一個分號時候,這個時候很多人都想的是迴圈,這個確實也不錯,但是在迴圈的基礎上呢,大家都知道C#中有一個字元 ...
  • 目錄: 1.判斷是否為空或者null 2.普通包含模糊查詢 1)以某字元串開頭的模糊查詢 2)以某字元串結尾的模糊查詢 3)包含某字元串的模糊查詢 3.精確到字元串對應位數字元的模糊查詢(*重點) linq大家肯定用過,對於其中的模糊查詢肯定也有所瞭解 提起linq的模糊查詢首先大家想到的肯定是 C ...
  • 初入碼田--ASP.NET MVC4 Web應用之創建一個空白的MVC應用程式 初入碼田--ASP.NET MVC4 Web應用開發之一 實現簡單的登錄 2016-07-29 一、創建M002AdminDemo.cs 右鍵【Models】-->添加-->類 二、創建Vm002新增管理員視圖模型.cs ...
  • 向空項目添加 ASP.NET Identity 的基本步驟。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...