原文出自:公眾號 golang小白成長記
原文鏈接:
https://mp.weixin.qq.com/s/VLHCu6b5Anx8HEj_gQZfOg
什么是TCP分段和IP分片
我們知道網絡就像一根管子,而管子吧,就會有粗細。
一個數據包想從管子的一端到另一端,得過這個管子。(廢話)
但數據包的量有大有小,想過管子,數據包不能大于這根管子的粗細。
問題來了,數據包過大時怎么辦?
答案比較簡單。會把數據包切分小塊。這樣數據就可以由大變小,順利傳輸。
回去看下網絡分層協議,數據先過傳輸層,再到網絡層。
這個行為在傳輸層和網絡層都有可能發生。
在傳輸層(TCP協議)里,叫分段。
在網絡層(IP層),叫分片。(注意以下提到的IP沒有特殊說明的情況下,都是指IPV4)
那么不管是分片還是分段,肯定需要按照一定的長度切分。
在TCP里,這個長度是MSS。
在IP層里,這個長度是MTU。
那MSS和MTU是什么關系呢?這個在之前的文章里簡單提到過。這里單獨拿出來。
MSS是什么
MSS:Maximum Segment Size 。TCP 提交給 IP 層最大分段大小,不包含 TCP Header 和 TCP Option,只包含 TCP Payload ,MSS 是 TCP 用來限制應用層最大的發送字節數。
假設 MTU= 1500 byte,那么 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果應用層有 2000 byte 發送,那么需要兩個切片才可以完成發送,第一個 TCP 切片 = 1460,第二個 TCP 切片 = 540。
如何查看MSS?
我們都知道TCP三次握手,而MSS會在三次握手的過程中傳遞給對方,用于通知對端本地最大可以接收的TCP報文數據大?。ú话琓CP和IP報文首部)。
比如上圖中,B將自己的MSS發送給A,建議A在發數據給B的時候,采用MSS=1420進行分段。而B在發數據給A的時候,同樣會帶上MSS=1372。兩者在對比后,會采用小的那個值(1372)作為通信的MSS值,這個過程叫MSS協商。
另外,一般情況下MSS + 20(TCP頭)+ 20(IP頭)= MTU,上面抓包的圖里對應的MTU分別是1372+40 和 1420+40。同一個路徑上,MTU不一定是對稱的,也就是說A到B和B到A,兩條路徑上的MTU可以是不同的,對應的MSS也一樣。
三次握手中協商了MSS就不會改變了嗎?
當然不是,每次執行TCP發送消息的函數時,會重新計算一次MSS,再進行分段操作。
對端不傳MSS會怎么樣?
我們再看TCP的報頭。
其實MSS是作為可選項引入的,只不過一般情況下MSS都會傳,但是萬一遇到了哪臺機器的實現上比較調皮,不傳MSS這個可選項。那對端該怎么辦?
如果沒有接收到對端TCP的MSS,本端TCP默認采用MSS=536Byte。
那為什么會是536?
536(data) + 20(tcp頭)+20(ip頭)= 576Byte
前面提到了IP會切片,那會切片,也就會重組,而這個576正好是 IP 最小重組緩沖區的大小。
MTU是什么
MTU: Maximum Transmit Unit,最大傳輸單元。其實這個是由數據鏈路層提供,為了告訴上層IP層,自己的傳輸能力是多大。IP層就會根據它進行數據包切分。一般 MTU=1500 Byte。
假設IP層有 <= 1500 byte 需要發送,只需要一個 IP 包就可以完成發送任務;假設 IP 層有 > 1500 byte 數據需要發送,需要分片才能完成發送,分片后的 IP Header ID 相同,同時為了分片后能在接收端把切片組裝起來,還需要在分片后的IP包里加上各種信息。比如這個分片在原來的IP包里的偏移offset。
如何查看MTU
在mac控制臺輸入 ifconfig命令,可以看到MTU的值為多大。
$ ipconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
...
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
...
p2p0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 2304
...
可以看到這上面有好幾個MTU,可以簡單理解為每個網卡的處理能力不同,所以對應的MTU也不同。當然這個值是可以修改的,但不在今天的討論范疇內,不再展開。
在一臺機器的應用層到這臺機器的網卡,這條鏈路上,基本上可以保證,MSS < MTU。
為什么MTU一般是1500
這其實是由傳輸效率決定的。首先,雖然我們平時用的網絡感覺挺穩定的,但其實這是因為TCP在背地里做了各種重傳等保證了傳輸的可靠,其實背地里線路是動不動就丟包的,而越大的包,發生丟包的概率就越大。
那是不是包越小就越好?也不是
但是如果選擇一個比較小的長度,假設選擇MTU為300Byte,TCP payload = 300 - IP Header - TCP Header = 300 - 20 - 20 = 260 byte。那有效傳輸效率= 260 / 300 = 86%
而如果以太網長度為1500,那有效傳輸效率= 1460 / 1500 = 96% ,顯然比 86%高多了。
所以,包越小越不容易丟包,包越大,傳輸效率又越高,因此權衡之下,選了1500。
為什么IP層會分片,TCP還要分段
由于本身IP層就會做分片這件事情。就算TCP不分段,到了IP層,數據包也會被分片,數據也能正常傳輸。
既然網絡層就會分片了,那么TCP為什么還要分段?是不是有些多此一舉?
假設有一份數據,較大,且在TCP層不分段,如果這份數據在發送的過程中出現丟包現象,TCP會發生重傳,那么重傳的就是這一大份數據(雖然IP層會把數據切分為MTU長度的N多個小包,但是TCP重傳的單位卻是那一大份數據)。
如果TCP把這份數據,分段為N個小于等于MSS長度的數據包,到了IP層后加上IP頭和TCP頭,還是小于MTU,那么IP層也不會再進行分包。此時在傳輸路上發生了丟包,那么TCP重傳的時候也只是重傳那一小部分的MSS段。效率會比TCP不分段時更高。
類似的,傳輸層除了TCP外,還有UDP協議,但UDP本身不會分段,所以當數據量較大時,只能交給IP層去分片,然后傳到底層進行發送。
也就是說,正常情況下,在一臺機器的傳輸層到網絡層這條鏈路上,如果傳輸層對數據做了分段,那么IP層就不會再分片。如果傳輸層沒分段,那么IP層就可能會進行分片。
說白了,數據在TCP分段,就是為了在IP層不需要分片,同時發生重傳的時候只重傳分段后的小份數據。
TCP分段了,IP層就一定不會分片了嗎
上面提到了,在發送端,TCP分段后,IP層就不會再分片了。
但是整個傳輸鏈路中,可能還會有其他網絡層設備,而這些設備的MTU可能小于發送端的MTU。此時雖然數據包在發送端已經分段過了,但是在IP層就還會再分片一次。
如果鏈路上還有設備有更小的MTU,那么還會再分片,最后所有的分片都會在接收端處進行組裝。
因此,就算TCP分段過后,在鏈路上的其他節點的IP層也是有可能再分片的,而且哪怕數據被第一次IP分片過了,也是有可能被其他機器的IP層進行二次、三次、四次….分片的。
IP層怎么做到不分片
上面提到的IP層在傳輸過程中因為各個節點間MTU可能不同,導致數據是可能被多次分片的。而且每次分片都要加上各種信息便于在接收端進行分片重組。那么IP層是否可以做到不分片?
如果有辦法知道整個鏈路上,最小的MTU是多少,并且以最小MTU長度發送數據,那么不管數據傳到哪個節點,都不會發生分片。
整個鏈路上,最小的MTU,就叫PMTU(path MTU)。
有一個獲得這個PMTU的方法,叫 Path MTU Discovery。
$cat /proc/sys/net/ipv4/ip_no_pmtu_disc
0
默認為0,意思是開啟PMTU發現的功能。現在一般機器上都是開啟的狀態。
原理比較簡單,首先我們先回去看下IP的數據報頭。
這里有個標紅的標志位DF(Don't Fragment),當它置為1,意味著這個IP報文不分片。
當鏈路上某個路由器,收到了這個報文,當IP報文長度大于路由器的MTU時,路由器會看下這個IP報文的DF
- 如果為0(允許分片),就會分片并把分片后的數據傳到下一個路由器
- 如果為1,就會把數據丟棄,同時返回一個ICMP包給發送端,并告訴它"達咩!"數據不可達,需要分片,同時帶上當前機器的MTU
理解了上面的原理后,我們再看下PMTU發現是怎么實現的。
- 應用通過TCP正常發送消息,傳輸層TCP分段后,到網絡層加上IP頭,DF置為1,消息再到更底層執行發送
- 此時鏈路上有臺路由器由于各種原因MTU變小了
- IP消息到這臺路由器了,路由器發現消息長度大于自己的MTU,且消息自帶DF不讓分片。就把消息丟棄。同時返回一個ICMP錯誤給發送端,同時帶上自己的MTU。
- 發送端收到這個ICMP消息,會更新自己的MTU,同時記錄到一個PMTU表中。
- 因為TCP的可靠性,會嘗試重傳這個消息,同時以這個新MTU值計算出MSS進行分段,此時新的IP包就可以順利被剛才的路由器轉發。
- 如果路徑上還有更小的MTU的路由器,那上面發生的事情還會再發生一次。
總結
- 數據在TCP分段,在IP層就不需要分片,同時發生重傳的時候只重傳分段后的小份數據
- TCP分段時使用MSS,IP分片時使用MTU
- MSS是通過MTU計算得到,在三次握手和發送消息時都有可能產生變化。
- IP分片是不得已的行為,盡量不在IP層分片,尤其是鏈路上中間設備的IP分片。因此,在IPv6中已經禁止中間節點設備對IP報文進行分片,分片只能在鏈路的最開頭和最末尾兩端進行。
- 建立連接后,路徑上節點的MTU值改變時,可以通過PMTU發現更新發送端MTU的值。這種情況下,PMTU發現通過浪費N次發送機會來換取的PMTU,TCP因為有重傳可以保證可靠性,在UDP就相當于消息直接丟了。