1. 程式人生 > >粘包和半包問題總結

粘包和半包問題總結

什麼是TCP粘包半包?

在這裡插入圖片描述
假設客戶端分別傳送了兩個資料包D1和D2給服務端,由於服務端一次讀取到的位元組數是不確定的,故可能存在以下4種情況。
(1)服務端分兩次讀取到了兩個獨立的資料包,分別是D1和D2,沒有粘包和拆包;
(2)服務端一次接收到了兩個資料包,D1和D2粘合在一起,被稱為TCP粘包;
(3)服務端分兩次讀取到了兩個資料包,第一次讀取到了完整的D1包和D2包的部分內容,第二次讀取到了D2包的剩餘內容,這被稱為TCP拆包;
(4)服務端分兩次讀取到了兩個資料包,第一次讀取到了D1包的部分內容D1_1,第二次讀取到了D1包的剩餘內容D1_2和D2包的整包。
如果此時服務端TCP接收滑窗非常小,而資料包D1和D2比較大,很有可能會發生第五種可能,即服務端分多次才能將D1和D2包接收完全,期間發生多次拆包。

TCP粘包/半包發生的原因

在這裡插入圖片描述
由於TCP協議本身的機制(面向連線的可靠地協議-三次握手機制)客戶端與伺服器會維持一個連線(Channel),資料在連線不斷開的情況下,可以持續不斷地將多個數據包發往伺服器,但是如果傳送的網路資料包太小,那麼他本身會啟用Nagle演算法(可配置是否啟用)對較小的資料包進行合併(基於此,TCP的網路延遲要UDP的高些)然後再發送(超時或者包大小足夠)。那麼這樣的話,伺服器在接收到訊息(資料流)的時候就無法區分哪些資料包是客戶端自己分開發送的,這樣產生了粘包;伺服器在接收到資料庫後,放到緩衝區中,如果訊息沒有被及時從快取區取走,下次在取資料的時候可能就會出現一次取出多個數據包的情況,造成粘包現象。

UDP:本身作為無連線的不可靠的傳輸協議(適合頻繁傳送較小的資料包),他不會對資料包進行合併傳送(也就沒有Nagle演算法之說了),他直接是一端傳送什麼資料,直接就發出去了,既然他不會對資料合併,每一個數據包都是完整的(資料+UDP頭+IP頭等等發一次資料封裝一次)也就沒有粘包一說了。
分包產生的原因就簡單的多:可能是IP分片傳輸導致的,也可能是傳輸過程中丟失部分包導致出現的半包,還有可能就是一個包可能被分成了兩次傳輸,在取資料的時候,先取到了一部分(還可能與接收的緩衝區大小有關係),總之就是一個數據包被分成了多次接收。

		更具體的原因有三個,分別如下。
			1. 應用程式寫入資料的位元組大小大於套接字傳送緩衝區的大小
			2. 進行MSS大小的TCP分段。MSS是最大報文段長度的縮寫。MSS是TCP報文段中的資料欄位的最大長度。資料欄位加上TCP首部才等於整個的TCP報文段。所以MSS並不是TCP報文段的最大長度,而是:MSS=TCP報文段長度-TCP首部長度
			3. 乙太網的payload大於MTU進行IP分片。MTU指:一種通訊協議的某一層上面所能通過的最大資料包大小。如果IP層有一個數據包要傳,而且資料的長度比鏈路層的MTU大,那麼IP層就會進行分片,把資料包分成託乾片,讓每一片都不超過MTU。注意,IP分片可以發生在原始傳送端主機上,也可以發生在中間路由器上。

解決粘包半包問題

由於底層的TCP無法理解上層的業務資料,所以在底層是無法保證資料包不被拆分和重組的,這個問題只能通過上層的應用協議棧設計來解決,根據業界的主流協議的解決方案,可以歸納如下。
(1)在包尾增加分割符,比如回車換行符進行分割,例如FTP協議;linebase包和delimiter包下,分別使用LineBasedFrameDecoder和DelimiterBasedFrameDecoder,如果超過規定位元組長度,會報錯。
(2)訊息定長,例如每個報文的大小為固定長度200位元組,如果不夠,空位補空格;fixed包下,使用FixedLengthFrameDecoder
(3)將訊息分為訊息頭和訊息體,訊息頭中包含表示訊息總長度(或者訊息體長度)的欄位,通常設計思路為訊息頭的第 一個欄位使用int32來表示訊息的總長度,LengthFieldBasedFrameDecoder;。