計算機網絡——淺析TCP粘包,拆包發生的原因以及解決方式


一、前言

  這篇博客來分析一下TCP數據傳輸中發生的粘包、拆包問題,我將描述一下這兩種情況的概念,分析它們發生的原因,最后再來談一談解決方式。


二、正文

  2.1 什么是粘包、拆包

  由於TCP傳輸協議是面向字節流的傳輸協議,沒有消息保護邊界,所以發送方發送的多個數據包,接收方應用層不知如何區分,可能會被當成一個包來處理,這就是粘包;或者,發送方將一個打包分成多個小包發送,而接收方將它們當成多個包進行處理,這就是拆包。看下面這張圖來具體理解一下:

  看上面這張圖片,演示了TCP傳輸的四種情況:

  1. 客戶端向服務器發送了兩個包,兩個數據包之間互不影響,這是正常的,我們不需要管;
  2. 客戶端向服務器發送了兩個包,但是兩個包被並在了一起,當作一個包發送,這就是發生了粘包現象,服務器可能會將它們當成一個數據包處理;
  3. 客戶端向服務器發送了兩個包D1D2,但是D2的一部分與D1合並在了一起,發生了粘包,而D2另一部分被單獨發送,也就是說D2被拆分成了兩個小包,發生了拆包現象;
  4. 第四種情況和第三種類似,只是順序反了一下,D1發生了拆包,而D1的后半部分與D2發生了粘包;

  2.2 粘包發生的原因

(1)套接字緩沖區

  應用層需要發送數據時,假設是基於TCP發送,則會將數據交給TCP套接字。數據被放入套接字發送緩存中,由於各種原因,往往不會立即發送,比如數據來的太快,還來不及發送。這就導致在發送緩存中,可能存在多個不同的數據包的字節並排在一起。當TCP需要發送數據時,會從發送緩存中讀取一段字節,封裝成TCP報文段發送出去,而讀取的這些字節,可能屬於多個數據包。

  在接收端,TCP接收到的數據也會被放入套接字的接收緩沖區中,再由應用層進行讀取。但是,應用層可能並不會立即讀取緩沖區中的數據,或者來不及讀取,此時就會造成多個數據包同時在緩沖區中。因為沒有划定邊界,所以應用層也無法將它們拆分開來,而是一同讀取,這就會造成粘包。

(2)Nagle算法

  TCP的發送方每次發送報文段,都希望能包含盡量多的字節,這樣可以最大限度的利用網絡帶寬。假設發送方需要要向接收方發送一個字節的數據,經過運輸層和網絡層的封裝后,將會為這一個字節加上40個字節的首部,這是一種非常浪費的情況,而Nagle算法正是為了減少這種情況。

  Nagle算法是基本原則就是:在任意時刻,只能有一個未被確認的小段報文。未被確認就是已經發送,但是還沒有接收到ACK的報文段,而小段報文指的是沒有達到網絡最大傳輸單元的報文段。使用Nagle算法時,會盡量地將一些小段湊成一個大段進行發送,而這就導致了粘包現象的發生。


  2.3 拆包現象發生的原因

(1)最大報文段長度MSS、最大傳輸單元MTU

  MSS表示一個TCP報文段能夠承載數據的最大字節數,而MTU則是網絡傳輸種能夠接受的報文的最大長度。這兩個概念說明網絡傳輸中,每個報文能夠承載的數據是有限的。TCP為了能將數據發送出去,且每個報文中的數據不超過MSS,會將一個大的數據包分為多個小段,為每個段加上首部后逐一發送,而這就造成了拆包。比如說,對於一張圖片,一般都需要拆分成多個段進行發送。

(2)TCP滑動窗口

  TCP采用了流水線的傳輸機制,而流水線傳輸中通過維護一個窗口來限制數據的發送,也可以叫做一個區間。只有序號落在窗口中的那些字節,才允許被發送。而窗口是動態變化的,它受到網絡擁塞情況以及接收方緩沖區剩余空間的限制。如果當前要發送的數據包的長度,大於窗口中的剩余空間,那這個數據包就會被拆分,先發送一部分,這樣也就造成了拆包。


  2.4 如何解決粘包和拆包

  這里需要強調一點,TCP協議可以保證數據完整,並且順序地接收,但是並不幫助區分多個數據,因為它是面向字節流的傳輸協議。也就是說,要解決粘包、拆包問題的是應用層協議,應用層協議對字節進行拆分。

(1)定長協議

  定長協議,顧名思義,就是應用層需要發送的每份數據,長度都是固定的。比如說,將數據長度定義為1024字節,所有不滿足1024字節的數據,可以通過補0進行填充。而接收方每次讀取1024字節,就可以正確區分每一份數據。

  • 發送方:每次發送固定長度的數據,若數據長度不夠,就使用其他字符填充;
  • 接收方:每次讀取固定字節的數據;

  不過,稍微想想也知道,這種方式並不好,對數據進行填充,完全就是一種浪費帶寬的行為,而且處理起來也麻煩。

(2)特殊字符分隔

  我們可以為每一份數據,添加起始字符和結束字符,這樣就可以區分了。

  • 發送方:對數據的開始和結束分別加上相應的標記字符;
  • 接收方:根據標記字符,逐個讀取每一份數據;

  當然,有時候我們並不確定應該選擇哪個字符作為標記字符,因為不確定這個字符是否原本就在數據中包含。此時我們可以對數據進行轉碼,比如說將數據轉成Base64編碼,而Base64只有64種字符,然后我們就可以使用這64種之外的字符作為標記。

(3)變長協議

  這種實現也是比較簡單的,對於應用層的報文,可以將它分為報文頭部以及報文體,而我們可以在報文頭中指定當前報文中數據的長度,這樣,接收方就能根據長度,正確地拆分多個粘在一起的數據了。

  • 發送方:將發送的報文分為頭部和實體,在頭部中指明實體中數據的長度;
  • 接收方:根據報文頭部中的信息,正確地區分多個數據;

  大部分應用層協議應該使用的都是這種方式,比如說HTTP協議,HTTP報文分為頭部(header)以及實體(body),在HTTP協議的首部中,有一個Content-Length首部行,就是指明body中攜帶數據的字節數。


三、總結

  最后在強調一遍,TCP可以保證完整,並且按序地接收字節,但是並不會幫忙拆分多個包的字節,真正做這個工作的是應用層的協議,應用層負責解決粘包和拆包。


四、參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM