1. 程式人生 > 實用技巧 >粘包和拆包

粘包和拆包

寫在前面

粘包、拆包是 Socket 程式設計中最常遇見的一個問題,本文只對粘包、拆包現象及發生的原因做簡要分析,具體如何解決粘包和拆包的問題,在後續文章中會詳細介紹。

什麼是粘包、拆包

TCP 是個"流"協議,所謂流,就是沒有界限的一串資料(無論你上層是如何封裝的資料,到通訊層都會轉換成“流”的形式,比如 Netty 的 ByteBuf),它會根據 TCP 緩衝區的實際情況進行包的劃分,所以實際場景可能是:

對於傳送端而言:

  • 當 TCP 傳送緩衝區剩餘空間不足時,一個完整的包可能會被拆分為多個包進行傳送,即可能發生拆包情況。

  • 當 TCP 傳送緩衝區剩餘空間足夠時,多個小的包也有可能被封裝成一個大的包進行傳送,即可能發生粘包情況。

粘包、拆包產生的原因

上面我們詳細瞭解了 TCP 粘包與拆包,那麼為什麼會發生粘包和拆包呢,大致上有三個方面的原因:

  1. 即上文描述的那種情況。

  2. Nagle 演算法,TCP 預設開啟 Nagle 演算法,Nagle 演算法主要做兩件事情:只有上一個分組得到確認,才傳送下一個分組,收集多個小分組,在一個確認到來時一起傳送,Nagle 演算法可能造成傳送方粘包。

  3. 進行 MSS 大小的 TCP,MSS 是最大報文段長度的縮寫,是 TCP 報文段中的 資料欄位 最大長度,MSS = TCP 報文段長度 - TCP 首部長度。

  4. 乙太網的 Payload 大於 MTU,進行 IP 分片,MTU 是最大傳輸單元的縮寫,乙太網的 MTU 為 1500 位元組。

拆包和粘包是相對的,一端粘了包,另外一端就需要將粘過的包拆開

如何處理 TCP 粘包和 TCP 拆包問題?

無論是 TCP 拆包還是 TCP 粘包本質問題都在於無法區分包的邊界,一般有三種區分包邊界的方式:

  1. 訊息資料固定長度,實際應用中基本不可能做到,即時做到了,也是很浪費儲存和網路資源。

  2. 使用分割符來區分包的界限

  3. 資料包的頭部中增加資料包長度欄位

UDP 存在粘包和拆包的問題嗎?

TCP 之所以存在拆包和粘包問題,本質就是 TCP 是面向位元組流的協議,位元組流協議即無邊界協議;而像 UDP 是面向報文的,當客戶端連續傳送多個包,並不會發生粘包現象,每一個包都是獨立的,傳送的時候也是以一個一個包為單位。

那麼問題來了,不會發生粘包,如果應用程式 write 一個大的包,那麼到底層進行傳送的時候會不會發生拆包呢?

答案是:不會。UDP 協議傳送時,用 sendto 函式最大能傳送資料的長度為:65535- IP 頭(20) - UDP 頭(8) = 65507 位元組。用 sendto 函式傳送資料時,如果傳送資料長度大於該值,則函式直接返回錯誤,不會發生拆包,而 TCP 流協議是會發生拆包的。

sendto 擴充套件

sendto 是一個計算機函式,指向一指定目的地傳送資料,sendto 適用於傳送未建立連線的 UDP 資料包 (引數為SOCK_DGRAM)。sendto 傳送資料必需注意資料長度不應超過通訊子網的 IP 包最大長度。IP 包最大長度在 WSAStartup() 呼叫返回的 WSAData 的 iMaxUdpDg 元素中。如果資料太長無法自動通過下層協議,則返回 WSAEMSGSIZE 錯誤,資料不會被髮送。

WSAEMSGSIZE:套介面為 SOCK_DGRAM 型別,且資料報大於 WINDOWS 套介面實現所支援的最大值。

int PASCAL FAR sendto(SOCKET s, const char FAR* buf, int len, int flags, const struct sockaddr FAR* to, int tolen);

s:一個標識套介面的描述字

buf:含待發送資料的緩衝區

len:buf 緩衝區中資料的長度

flags:呼叫方式標誌位

to:(可選)指標,指向目的套介面的地址

tolen:to 所指地址的長度

總結

到這裡關於 TCP 粘包和拆包是什麼,產生的原因是什麼,以及 UDP 是否也會發生粘包和拆包的問題做了簡要分析。這只是關於 TCP 粘包和拆包問題的第一篇文章,後面會詳細分析常用的解決方案,以及市面上常用通訊框架的解決方案是什麼。

參考