大家在使用SOCKET通信編程的時候,一般會采用UDP和TCP兩種方式;TCP因為它沒有包的概念,它只有流的概念,並且因為發送或接收緩沖區大小的設置問題,會產生粘包及半包的現象。
場景:
服務端向連續發送三個“HelloWorld”(三次消息無間隔),那么客戶端接收到的情況會有以下三種:
1)HelloWorld HelloWorld HelloWorld (客戶端接收三次)
2)HelloWorldHelloWor ldHelloWorld (客戶端接收兩次)
3)HelloWorldHelloWorldHelloWorld (客戶端接收一次)
我們這里不詳細討論這些情況是如何產生的(博客園相關的文章有很多,大家不清楚的可以去查一查),我以自己的方式來描述一下如何處理粘包、半包的消息。
1)不要使消息產生粘包、半包現象
這個我是這樣做的:把每個包的大小固定,並且把發送緩沖區和接收緩沖區的大小都設置成包的大小(這個做法也許是不成熟的,但我試驗下來,還是比較有效而且高效的,希望有其它更好處理方式的人可以指正)
2)把消息進行包裝,根據外部包裝特性來剝出每一個粘在一起的消息
比如,發送HelloWorld,在HelloWorld外套個殼,變成<msg>HelloWorld</msg>,那么這個時候可能會收到這樣的一個包:<msg>HelloWorld</msg><msg>HelloWorld</msg><msg>Hello和另一個這樣的包World</msg>,我是以最簡單的方式,把這兩個消息加工成三個HellWorld,請看代碼:
//這是暫存上一個消息中不完整的消息內容 private string halfMsg = ""; private void ReceiveCallback(IAsyncResult AR) { try { int REnd = sckClient.EndReceive(AR); //上次未處理的消息內容+本次接收到的內容 string temp = halfMsg + Encoding.Default.GetString(msgBuffer, 0, REnd); //使用正則來提取消息內容 string pattern = "^<msg>.*?</msg>"; //循環提取,直到剩下的消息是不完整的數據(或剛好全部提取完) while(Regex.IsMatch(temp, pattern)) { string match = Regex.Match(temp, pattern).Groups[0].Value; temp = temp.Remove(0,match.Length); } //將正則循環提取后剩下的內容暫存(可能為空串) halfMsg = temp; msgBuffer = new byte[128]; //同時接收客戶端回發的數據,用於回發 sckClient.BeginReceive(msgBuffer, 0, msgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null); } catch (Exception ex) { msgBuffer = new byte[128]; sckClient.BeginReceive(msgBuffer, 0, msgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null); } }
大家不喜勿噴,此篇文章的主要目的是給自己做個筆記,如能幫到一些后來人,那當然是極好的事情了。