TCP/IP協(xié)議棧中的TimeStamp選項(xiàng)
TCP應(yīng)該是以太網(wǎng)協(xié)議族中被應(yīng)用最為廣泛的協(xié)議之一,這里就聊一聊TCP協(xié)議中的TimeStamp選項(xiàng)。這個(gè)選項(xiàng)是由RFC 1323引入的,該C建議提交于1992年,到今天已經(jīng)足足有20個(gè)年頭。不過相信大部分程序猿對(duì)這個(gè)建議還是相當(dāng)陌生。
要理解為啥需要用TimeStamp選項(xiàng),還需要從TCP協(xié)議的幾個(gè)基本設(shè)計(jì)說起。
TCP協(xié)議的幾個(gè)設(shè)計(jì)初衷,以及引發(fā)的問題:
1. 協(xié)議規(guī)定收端不需要響應(yīng)每一個(gè)收到的數(shù)據(jù)報(bào)文,只需要收到N個(gè)報(bào)文后,向發(fā)端回復(fù)一個(gè)ack報(bào)文即可。
這樣的規(guī)定是為了提高通訊的效率,但是也引入了幾個(gè)問題:
A. 發(fā)端發(fā)出報(bào)文后,到底多久能夠收到ack是不確定的。
B. 萬一ack報(bào)文丟失了,判斷需要重發(fā)的timeout時(shí)間也很難確定。
2. TCP報(bào)文中,標(biāo)示Sequence號(hào)的地址長(zhǎng)度為32位。
這就限制了發(fā)端最多一次發(fā)送2^30長(zhǎng)度的數(shù)據(jù),就必須等待ack信號(hào)。為啥呢?在這個(gè)鏈接里有一些詳細(xì)的討論。
然而對(duì)于超高速以太網(wǎng)(1000M以至于10G),這樣會(huì)影響TCP連接的轉(zhuǎn)發(fā)效率。
為解決上面提到的問題,TimeStamp選項(xiàng)主要有兩個(gè)用途:
1. 測(cè)量TCP連接兩端通訊的延遲(Round Trip Time Measurement)
有了RTTM機(jī)制,TCP的兩端可以很容易的判斷出線路上報(bào)文的延遲情況,從而制定出一個(gè)優(yōu)化的發(fā)包間隔和報(bào)文TimeOut時(shí)間,從而解決了第一個(gè)問題。
2. 處理Sequence號(hào)反轉(zhuǎn)的問題(Protect Against Wrapped Sequence Numbers)。
TCP收端收到一個(gè)數(shù)據(jù)報(bào)文后,會(huì)先比較本次收到報(bào)文的TimeStamp和上次收到報(bào)文的TimeStamp。如果本次的比較新,那么可以直接判斷本次收到的報(bào)文是新的報(bào)文,不需要進(jìn)行復(fù)雜的Sequence Number Window Scale計(jì)算,從而解決了第二個(gè)問題。
然而,RFC1323建議還存在一些隱患。
建議中定義TimeStamp增加的間隔可以使1ms-1s。如果設(shè)備按照1ms的速度增加TimeStamp,那么只要一個(gè)TCP連接連續(xù)24.8天(1ms*2^31)沒有通訊,再發(fā)送報(bào)文,收端比較本次報(bào)文和上次報(bào)文TimeStamp的動(dòng)作就會(huì)出錯(cuò)。(問題1)
(注:TCP協(xié)議中并沒有定義KeepAlive。如果應(yīng)用層代碼不定義超時(shí)機(jī)制,TCP連接就永遠(yuǎn)不會(huì)中斷,所以連續(xù)24.8天不通訊的情況是卻有可能發(fā)生的。)
引用Linux相關(guān)代碼:((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)
比如 tp->rx_opt.rcv_tsval = 0x80000020, tp->rx_opt.ts_recent = 0x10
((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) = (s32)0x80000010,是一個(gè)負(fù)數(shù),必然小于0。
如果解決問題1呢?
已知按照RFC1323的規(guī)定,按照最快TimeStamp增加的速度,也需要24.8天TImeStamp才有可能發(fā)生反轉(zhuǎn)。
如果((s32)(tp->rx_opt.rcv_tsval - tp->rx_opt.ts_recent) < 0)判斷成立,還可以再用本地收到報(bào)文的本地TimeStamp減去上一次收到報(bào)文的本地TimeStamp。如果時(shí)間大于24.8天,那么就是TimeStamp發(fā)生了反轉(zhuǎn);否則就不是反轉(zhuǎn)的情況。這樣做是不是就萬無一失了呢?不一定!
別忘了本地TimeStamp的計(jì)數(shù)器也是個(gè)32位,也可能會(huì)翻轉(zhuǎn)的。(問題2)
舉個(gè)極端的例子:假設(shè)TCP兩端設(shè)備的TimeStamp增加間隔不一致,A為1ms,B為10ms。TCP連接連續(xù)248天沒有通訊;這個(gè)時(shí)候B向A發(fā)送了一個(gè)數(shù)據(jù)報(bào)文。
此時(shí)B發(fā)送給A的TCP報(bào)文中的TimeStamp,正好發(fā)生了翻轉(zhuǎn)。然而由于A的計(jì)數(shù)器是每1ms加一的,248天時(shí)間,A的計(jì)數(shù)器已經(jīng)歸零過5次了。這時(shí)候再用本地TimeStamp做判斷還是錯(cuò)的。
比較保險(xiǎn)的做法是:
如果TCP連接的速度不那么快(2^32/s),本地TimeStamp用最大間隔時(shí)間1S。從而規(guī)避了(問題2)。
如果TCP連接速度非常快,1S的TimeStamp間隔就有些不合時(shí)宜了,可以選小一級(jí),如100ms。如果這時(shí)候還會(huì)發(fā)生連續(xù)24800天(為啥是24800天呢)不通訊的情況,除了罵娘以外,我也沒辦法了。