前言: 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; } }