詳解TCP/IP重組
學(xué)習(xí)網(wǎng)絡(luò)應(yīng)用開發(fā)的時(shí)候最大的疑惑是“分片”。幾乎在TCP/IP的每一層都有這個(gè)概念,由于專注這方面的資料非常少所以對(duì)這部分內(nèi)容很多朋友多云山霧繞的,這篇文章總結(jié)了我關(guān)于TCP/IP分片、重組的一些認(rèn)識(shí),希望對(duì)大家有幫助。
MTU
按不同的網(wǎng)絡(luò)傳輸介質(zhì)和傳輸算法網(wǎng)絡(luò)有不同的種類,比如令牌環(huán)、FDDI、ATM、以太網(wǎng)。其中以太網(wǎng)最為普遍——幾乎成為了網(wǎng)絡(luò)的代名詞,所以我們所能接觸到的所有網(wǎng)絡(luò)幾乎都是以太網(wǎng)。以太網(wǎng)的傳輸介質(zhì)是銅纜、雙絞線、光纖;傳輸算法是CSMA/CD,按照這個(gè)算法規(guī)定數(shù)據(jù)并不是“源源不斷”的發(fā)送出去的,而是每次發(fā)送“一小段”,發(fā)送完畢后要檢測(cè)是否沖突。MTU(Maximum Transmission Unit,最大傳輸單元)就是指“一小段”數(shù)據(jù)有多大。按照IEEE802.3(以太網(wǎng)技術(shù)的標(biāo)準(zhǔn)化名稱)規(guī)范MTU的最大值是64字節(jié)、最大長度是1518字節(jié)。
IEEE之所以選擇這兩個(gè)值是為了考慮到線路的利用率,以10Mbps為例(以太網(wǎng)最早的標(biāo)準(zhǔn)),最大傳輸距離是500m,做多允許中繼4次,所以它最大允許2500m的傳輸距離。
數(shù)據(jù)在這個(gè)距離上跑個(gè)一來一回需要57.6μs,在這個(gè)時(shí)間內(nèi)A一共可以發(fā)送576bit也就是72字節(jié)。去掉8個(gè)字節(jié)的前導(dǎo)碼和幀開始符(一個(gè)幀以7個(gè)字節(jié)的前導(dǎo)碼和1個(gè)字節(jié)的幀開始符作為幀的開始)也就是64字節(jié)。最大值1518字節(jié)有點(diǎn)“拍腦袋”的意思,采用這個(gè)值可以讓線路利用率更高,至于為什么會(huì)這樣幾乎沒有人能回答上來(包括以太網(wǎng)之父Bob Metcalfe也說不清楚)
以太網(wǎng)最大MTU是1518,(去掉以太網(wǎng)的源MAC地址(6字節(jié))+目標(biāo)MAC地址(6字節(jié))+類型(2字節(jié))+冗余校驗(yàn)(4字節(jié));所以能夠給上層協(xié)議用的最大值是1500)基于以太網(wǎng)的IP協(xié)議無論多大,在物理傳輸?shù)臅r(shí)候都會(huì)被切分成1500個(gè)字節(jié)“按塊傳輸”。
雖然是“按塊傳輸”但是數(shù)據(jù)鏈路協(xié)議并沒有分塊和重組的概念,你可以想想發(fā)送端有一個(gè)“大水池”,數(shù)據(jù)鏈路每次從水池里取1518個(gè)字節(jié)后仍出去;接收端也有一個(gè)大水池,數(shù)據(jù)鏈路協(xié)議收到數(shù)據(jù)包后一股腦全部扔進(jìn)去。(“大水池”是所有進(jìn)程共享的,暫且忽略“溢出的”可能)
IP重組
數(shù)據(jù)鏈路層協(xié)議考慮到的是“兩塊網(wǎng)卡怎么傳輸數(shù)據(jù)”,兩端的“大水池”相當(dāng)于網(wǎng)卡的內(nèi)存空間。數(shù)據(jù)運(yùn)到“大水池”里面后要有人來“分揀”,完成這項(xiàng)工作的就是IP協(xié)議,它會(huì)對(duì)數(shù)據(jù)包進(jìn)行分揀,首先扔掉那些IP地址不是本機(jī)的數(shù)據(jù);然后根據(jù)端口來對(duì)數(shù)據(jù)包進(jìn)行分類。
每個(gè)IP端口都有一個(gè)大水池(其實(shí)所有的大水池都是鏈表),IP協(xié)議把分揀的數(shù)據(jù)放到每個(gè)端口對(duì)應(yīng)的大水池里面。如果IP協(xié)議的大小是1500那么我們就不用考慮重組的問題了,但是TCP/IP協(xié)議設(shè)計(jì)的時(shí)候不是針對(duì)以太網(wǎng)設(shè)計(jì)的。IP協(xié)議中表示長度的字段是16位,那么單個(gè)IP數(shù)據(jù)包大小最大可以達(dá)到——65536字節(jié)。這意味著一個(gè)IP數(shù)據(jù)包可能會(huì)被拆分成多個(gè)以太網(wǎng)數(shù)據(jù)幀(MTU1500)傳送,所以IP數(shù)據(jù)包必須考慮能夠把N個(gè)1500字節(jié)的幀組裝起來,這就是IP重組。(IP協(xié)議從來不會(huì)主動(dòng)“分片”,它只是被迫重組,受限于MTU所以有重組機(jī)制)
我們可以做個(gè)試驗(yàn),ICMP(ping)協(xié)議是基于IP的,我們模擬從172.16.46.141發(fā)送一個(gè)大小為1500的數(shù)據(jù)包到172.16.46.142。一個(gè)1500字節(jié)+ICMP頭部(8字節(jié))=1508;會(huì)被分成兩個(gè)IP數(shù)據(jù)包,一個(gè)大小是1480(+IP頭20字節(jié)剛好是1500),一個(gè)是28(+IP頭20字節(jié)是48字節(jié))。
可以看到截圖中兩個(gè)IP數(shù)據(jù)包的id都是22828,其中第一個(gè)的flags部分是[+],表示有IP分片并且這是第一個(gè)分片。
- 第一個(gè)IP分片完整大小是1500(MTU),去掉IP頭(20字節(jié)),實(shí)際大小是1480(ICMP數(shù)據(jù)包,包含頭部)
- 第二個(gè)IP分片完整大小是48,去掉IP頭部(20字節(jié)),實(shí)際大小是28字節(jié)
- 1480+28=1508,剛好是我們ICMP數(shù)據(jù)包的大小
這里需要解釋IP重組的幾個(gè)問題
- IP只有重組,沒有分片。IP數(shù)據(jù)包頭部的MF標(biāo)志位主要用于解決MTU和IP大小不匹配的問題,用于IP數(shù)據(jù)包重組,IP數(shù)據(jù)包從來不會(huì)主動(dòng)分片。
- IP沒有重傳,IP數(shù)據(jù)包被分為多個(gè)幀傳輸,**如果任何一個(gè)幀丟失IP數(shù)據(jù)包都會(huì)重組失敗那么整個(gè)數(shù)據(jù)包都會(huì)被丟棄**。所以基于IP協(xié)議的上層協(xié)議一般不會(huì)發(fā)送超過1500大小的數(shù)據(jù)包(考慮一下如果用UDP協(xié)議發(fā)送65535個(gè)字節(jié),被拆分成43個(gè)MTU大小的幀,收到了42個(gè),其中一個(gè)沒有收到那么IP數(shù)據(jù)包重組失敗,數(shù)據(jù)包被徹底丟棄,是不是很浪費(fèi)帶寬?)
TCP的MSS
TCP協(xié)議格式中沒有一個(gè)字段表示數(shù)據(jù)包大小,它被設(shè)計(jì)成一個(gè)“流式”協(xié)議,所以在三次握手的時(shí)候會(huì)相互交換MSS(Maximum Segment Size 最大分段大小),表示一個(gè)TCP分片是多大。
那么MSS取值多少合適呢?有了上面的結(jié)論IP沒有重傳不難得出答案——1500最合適。如果TCP數(shù)據(jù)分片超過這個(gè)值會(huì)被拆分成多個(gè)MTU幀,從而引起IP重組;IP重組本身是不可靠的所以很有可能丟失數(shù)據(jù)包(最重要的是IP重組失敗后不會(huì)報(bào)告上層協(xié)議)要命的是TCP協(xié)議不會(huì)知道重組失敗,也沒有辦法重傳。
所以如果我們用tcpdump抓包所有的TCP三次握手MSS都是等于MTU值的。
UDP呢?
長期以來我一直疑惑單個(gè)UDP數(shù)據(jù)包最大值是多少(調(diào)用send/recv函數(shù)時(shí)傳遞的大小),UDP頭部有2個(gè)字節(jié)表示長度理論上一個(gè)UDP數(shù)據(jù)包最大能夠傳輸65536字節(jié),這個(gè)也是IP數(shù)據(jù)包的最大理論值。有些系統(tǒng)定義了SO_MAX_MSG_SIZE宏來表示這個(gè)限制。
但是如果UDP數(shù)據(jù)包真的用這個(gè)值那么一定會(huì)觸發(fā)IP重組從而又回到了我們上面的結(jié)論IP只有重組沒有重傳。一個(gè)UDP數(shù)據(jù)包被拆分成43個(gè)MTU大小的幀,對(duì)端收到了42個(gè),其中一個(gè)沒有收到那么IP數(shù)據(jù)包重組失敗,數(shù)據(jù)包被徹底丟棄,如果網(wǎng)絡(luò)質(zhì)量不是特別好UDP數(shù)據(jù)包會(huì)經(jīng)常“丟包”。
所以UDP最大數(shù)據(jù)包值合適的大小是1500-8(UDP包頭)=1492。
總結(jié)
IP只有重組沒有重傳,如果任何一個(gè)IP包丟失那么就會(huì)把整個(gè)數(shù)據(jù)包丟棄。所以TCP用MTU值作為MSS;UDP用1492作為最大值以此來避免IP重組。
【本文是51CTO專欄作者“邢森”的原創(chuàng)文章,轉(zhuǎn)載請(qǐng)聯(lián)系作者本人獲取授權(quán)】