1. 程式人生 > >粘包、拆包發生原因滑動窗口、MSS/MTU限制、Nagle算法

粘包、拆包發生原因滑動窗口、MSS/MTU限制、Nagle算法

情況下 粘包 多次 設置 無法 以太網幀 fin tps targe

【TCP協議】(3)---TCP粘包黏包

有關TCP協議之前寫過兩篇博客:

1、【TCP協議】(1)---TCP協議詳解

2、【TCP協議】(2)---TCP三次握手和四次揮手

一、TCP粘包、拆包圖解

技術分享圖片

假設客戶端分別發送了兩個數據包D1和D2給服務端,由於服務端一次讀取到字節數是不確定的,故可能存在以下四種情況:

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包完全接受,期間發生多次拆包。

二、 粘包、拆包發生原因

產生原因主要有這3種:滑動窗口、MSS/MTU限制、Nagle算法

1、滑動窗口

TCP流量控制主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此可以控制發送端發送數據的大小,從而達到流量

控制的目的。這個窗口大小就是我們一次傳輸幾個數據。對所有數據幀按順序賦予編號,發送方在發送過程中始終保持著一個發送窗口,只有落在發送窗口內的幀才允許被發送;

同時接收方也維持著一個接收窗口,只有落在接收窗口內的幀才允許接收。這樣通過調整發送方窗口和接收方窗口的大小可以實現流量控制。

現在來看一下滑動窗口是如何造成粘包、拆包的?

粘包:假設發送方的每256 bytes表示一個完整的報文,接收方由於數據處理不及時,這256個字節的數據都會被緩存到SO_RCVBUF(接收緩存區)中。如果接收方的SO_RCVBUF

中緩存了多個報文,那麽對於接收方而言,這就是粘包。

拆包:考慮另外一種情況,假設接收方的窗口只剩了128,意味著發送方最多還可以發送128字節,而由於發送方的數據大小是256字節,因此只能發送前128字節,等到接收方ack

後,才能發送剩余字節。這就造成了拆包。

2、MSS和MTU分片

MSS: 是Maximum Segement Size縮寫,表示TCP報文中data部分的最大長度,是TCP協議在OSI五層網絡模型中傳輸層對一次可以發送的最大數據的限制。

MTU: 最大傳輸單元是Maxitum Transmission Unit的簡寫,是OSI五層網絡模型中鏈路層(datalink layer)對一次可以發送的最大數據的限制。

當需要傳輸的數據大於MSS或者MTU時,數據會被拆分成多個包進行傳輸。由於MSS是根據MTU計算出來的,因此當發送的數據滿足MSS時,必然滿足MTU。

為了更好的理解,我們先介紹一下在5層網絡模型中應用通過TCP發送數據的流程:

技術分享圖片

對於應用層來說,只關心發送的數據DATA,將數據寫入socket在內核中的發送緩沖區SO_SNDBUF即返回,操作系統會將SO_SNDBUF中的數據取出來進行發送。

傳輸層會在DATA前面加上TCP Header,構成一個完整的TCP報文。

當數據到達網絡層(network layer)時,網絡層會在TCP報文的基礎上再添加一個IP Header,也就是將自己的網絡地址加入到報文中。

到數據鏈路層時,還會加上Datalink Header和CRC。

當到達物理層時,會將SMAC(Source Machine,數據發送方的MAC地址),DMAC(Destination Machine,數據接受方的MAC地址 )和Type域加入。

可以發現數據在發送前,每一層都會在上一層的基礎上增加一些內容,下圖演示了MSS、MTU在這個過程中的作用。

技術分享圖片

MTU是以太網傳輸數據方面的限制,每個以太網幀都有最小的大小64bytes最大不能超過1518bytes。刨去以太網幀的幀頭 (DMAC目的MAC地址48bit=6Bytes

+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾 CRC校驗部分4Bytes(這個部分有時候大家也把它叫做FCS),那麽剩下承載上層協議的地方也

就是Data域最大就只能有1500Bytes這個值 我們就把它稱之為MTU。

由於MTU限制了一次最多可以發送1500個字節,而TCP協議在發送DATA時,還會加上額外的TCP Header和Ip Header,因此刨去這兩個部分,就是TCP協議一次可以

發送的實際應用數據的最大大小,也就是MSS。

MSS長度=MTU長度-IP Header-TCP Header

TCP Header的長度是20字節,IPv4中IP Header長度是20字節,IPV6中IP Header長度是40字節,因此:在IPV4中,以太網MSS可以達到1460byte;在IPV6中,以太網

MSS可以達到1440byte。

需要註意的是MSS表示的一次可以發送的DATA的最大長度,而不是DATA的真實長度。發送方發送數據時,當SO_SNDBUF中的數據量大於MSS時,操作系統會將數據進

行拆分,使得每一部分都小於MSS,這就是拆包,然後每一部分都加上TCP Header,構成多個完整的TCP報文進行發送,當然經過網絡層和數據鏈路層的時候,還會分別

加上相應的內容。

需要註意: 默認情況下,與外部通信的網卡的MTU大小是1500個字節。而本地回環地址的MTU大小為65535,這是因為本地測試時數據不需要走網卡,所以不受到1500

的限制。

3、 Nagle算法

TCP/IP協議中,無論發送多少數據,總是要在數據(DATA)前面加上協議頭(TCP Header+IP Header),同時,對方接收到數據,也需要發送ACK表示確認。

即使從鍵盤輸入的一個字符,占用一個字節,可能在傳輸上造成41字節的包,其中包括1字節的有用信息和40字節的首部數據。這種情況轉變成了4000%的消耗,這樣的

情況對於重負載的網絡來是無法接受的。

為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。

Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥著許多小數據塊。

Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去後,沒有

收到對方發送的ACK確認該數據已收到。

Nagle算法的規則:

1)如果SO_SNDBUF(發送緩沖區)中的數據長度達到MSS,則允許發送;

2)如果該SO_SNDBUF中含有FIN,表示請求關閉連接,則先將SO_SNDBUF中的剩余數據發送,再關閉;

3)設置了TCP_NODELAY=true選項,則允許發送。TCP_NODELAY是取消TCP的確認延遲機制,相當於禁用了Nagle 算法。

4)未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送;

5)上述條件都未滿足,但發生了超時(一般為200ms),則立即發送。


有關TCP粘包黏包的解決辦法,將在下一篇通過Netty代碼演示。

參考

這篇博客基本上參考一篇博客的,感謝原作者整理,看了許多相關博客,感覺這一篇就夠了。

參考博客鏈接:TCP粘包、拆包

粘包、拆包發生原因滑動窗口、MSS/MTU限制、Nagle算法