.Net培訓 |收費模板 收費技術服務 |二次開發
網站建設套餐 |網站訂制
空間域名 |軟件系統開發
RGB顏色查詢對照表
熟悉的TCP編程的都知道,無論是客戶端或服務端,在發送和接收數據的時候都需要考慮TCP底層的粘包/半包機制。
那么什么是TCP粘包和拆包呢?TCP是一個“流”協議,所謂流,就是沒有界限的一串數據。網絡中傳輸的數據流你可以想象成水龍頭里面的水,水流并沒有界限。所以TCP底層并不了解上層業務數據的具體含義,它會根據TCP緩沖區的實際情況進行包的劃分,所以在業務上一個完整的包可能會被TCP拆成多個包進行發送,也有可能把多個小的包合并成一個在的數據包發送,就是所謂的TCP粘包半包問題。
之所以出現粘包和半包現象,是因為TCP當中,只有流的概念,沒有包的概念. 半包 指接受方沒有接受到一個完整的包,只接受了部分,這種情況主要是由于TCP為提高傳輸效率,將一個包分配的足夠大,導致接受方并不能一次接受完。(在長連接和短連接中都會出現)。 粘包與分包 指發送方發送的若干包數據到接收方接收時粘成一包,從接收緩沖區看,后一包數據的頭緊接著前一包數據的尾。出現粘包現象的原因是多方面的,它既可能由發送方造成,也可能由接收方造成。發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一包數據。若連續幾次發送的數據都很少,通常TCP會根據優化算法把這些數據合成一包后一次發送出去,這樣接收方就收到了粘包數據。接收方引起的粘包是由于接收方用戶進程不及時接收數據,從而導致粘包現象。這是因為接收方先把收到的數據放在系統接收緩沖區,用戶進程從該緩沖區取數據,若下一包數據到達時前一包數據尚未被用戶進程取走,則下一包數據放到系統接收緩沖區時就接到前一包數據之后,而用戶進程根據預先設定的緩沖區大小從系統接收緩沖區取數據,這樣就一次取到了多包數據。分包是指在出現粘包的時候我們的接收方要進行分包處理。(在長連接中都會出現) 什么時候需要考慮半包的情況? 我們了解到Socket內部默認的收發緩沖區大小大概是8K,但是我們在實際中往往需要考慮效率問題,重新配置了這個值,來達到系統的最佳狀態。 一個實際中的例子:用mina作為服務器端,使用的緩存大小為10k,這里使用的是短連接,所以不用考慮粘包的問題。 問題描述:在并發量比較大的情況下,就會出現一次接受并不能完整的獲取所有的數據。 粘包與分包常用處理方式: 1.通過【包長+包體】的協議形式,當服務器端獲取到指定的包長時才說明獲取完整。
發送信息前構造含包大小的字節:
public byte[] BuildMsg<T>(T model) { byte[] msgBuffer; var msgBodyBuffer = MessagePackSerializer.Serialize<T>(model); using (MemoryStream ms = new MemoryStream()) { var boyLengthBytes = BitConverter.GetBytes(msgBodyBuffer.Length); if (BitConverter.IsLittleEndian) { Array.Reverse(boyLengthBytes);//java大端對齊和小端對齊與windows是反的,java默認是big endian,網絡字節也是大端(符合人的思維) } ms.Write(boyLengthBytes, 0, 4); ms.Write(msgBodyBuffer, 0, msgBodyBuffer.Length); msgBuffer = ms.ToArray(); ms.Close(); } return msgBuffer; }
接收信息:
byte[] msgHeader = new byte[4];//4字節為包內容大小。 clientSocket.Receive(msgHeader, 0, 4, SocketFlags.Partial); Array.Reverse(msgHeader);//網絡字節序列默認為大端,java默認為大端,c#默認為小端。所以要轉換一下,否則獲取包大小不正確。 int msgLength = BitConverter.ToInt32(msgHeader, 0); byte[] msgContent = new byte[msgLength]; var receiveMsgByteCount = 0; var leftByteCount = msgLength; while (true) { //myClientSocket.Available=默認8192,8K //處理粘包問題 int readByteLength = clientSocket.Receive(msgContent, receiveMsgByteCount, clientSocket.Available > leftByteCount ? leftByteCount : clientSocket.Available, SocketFlags.Partial);//默認次最多收8192字節 receiveMsgByteCount += readByteLength; leftByteCount -= readByteLength; if (receiveMsgByteCount >= msgContent.Length) break; }
其實對于java的一些框架已經有比較成熟的方案。比如nettty中的LineBasedFrameDecoder、DelimiterBasedFrameDecoder、FixedLengthBasedFrameDecoder。如查要自定協議,使用netty的比較復雜的LengthFieldBasedFrameDecoder。