阿里Java一面:熟悉TCP粘包、拆包?說說粘包、拆包產生原因

大家好,我是發財!今天給小夥伴介紹一下TCP粘包、拆包!有自己看法的也可以在評論區留言探討,也可以轉發關注下我以後會長期分享!
一、TCP粘包、拆包圖解
在這裏插入圖片描述
假設客戶端分別發送了兩個數據包D1和D2給服務端,由於服務端一次讀取到字節數是不確定的,故可能存在以下四種情況:

服務端分兩次讀取到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包
服務端一次接受到了兩個數據包,D1和D2粘合在一起,稱之爲TCP粘包
服務端分兩次讀取到了數據包,第一次讀取到了完整的D1包和D2包的部分內容,第二次讀取到了D2包的剩餘內容,這稱之爲TCP拆包
服務端分兩次讀取到了數據包,第一次讀取到了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算法的規則:

如果SO_SNDBUF(發送緩衝區)中的數據長度達到MSS,則允許發送;
如果該SO_SNDBUF中含有FIN,表示請求關閉連接,則先將SO_SNDBUF中的剩餘數據發送,再關閉;
設置了TCP_NODELAY=true選項,則允許發送。TCP_NODELAY是取消TCP的確認延遲機制,相當於禁用了Nagle 算法。
未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送;
上述條件都未滿足,但發生了超時(一般爲200ms),則立即發送。
剛入駐CSDN,定期會更新3~4篇乾貨。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章