本文主要分析爲何TIME_WAIT狀態的持續時間是2MSL而不是1MSL,3MSL或其它的時長,而不會詳細描述爲何須要TIME_WAIT狀態。linux
閱讀本文須要的預備知識:編程
瞭解TCP協議的狀態變遷;網絡
瞭解TCP拆鏈的四次揮手過程;tcp
瞭解爲何須要TIME_WAIT狀態。函數
正文this
其實這個問題在《TCP/IP詳解》以及《UNIX網絡編程》這兩本書中都有說起,但這兩本書上的描述都比較簡潔並非特別容易理解,記得在第一次看《UNIX網絡編程》時,我曾經反覆閱讀相關段落並花了很多時間來想這個問題,但並無搞得很清楚,始終是懂非懂的樣子,直至後來有機會參與TCP/IP協議棧的開發後才真正got到這個問題的關鍵點。spa
根據第三版《UNIX網絡編程 卷1》2.7節,TIME_WAIT狀態的主要目的有兩個:設計
優雅的關閉TCP鏈接,也就是儘可能保證被動關閉的一端收到它本身發出去的FIN報文的ACK確認報文;ci
處理延遲的重複報文,這主要是爲了不先後兩個使用相同四元組的鏈接中的前一個鏈接的報文干擾後一個鏈接。開發
很明顯,要實現上述兩個目標,TIME_WAIT狀態須要持續一段時間,但這段時間應該是多長呢?
若是隻考慮上述第一個目標,則TIME_WAIT狀態須要持續的時間應該參考對端的RTO(重傳超時時間)以及MSL(報文在網絡中的最大生存時間)來計算而不是僅僅按MSL來計算,由於只要對端沒有收到針對FIN報文的ACK,就會一直持續重傳FIN報文直到重傳超時,因此最能實現完美關閉鏈接的時長計算方式應該是從對端發送第一個FIN報文開始計時到它最後一次重傳FIN報文這段時長加上MSL,但這個計算方式過於保守,只有在全部的ACK報文都丟失的狀況下才須要這麼長的時間;另外,第一個目標雖然重要,但並不十分關鍵,由於既然已經到了關閉鏈接的最後一步,說明在這個TCP鏈接上的全部用戶數據已經完成可靠傳輸,因此要不要完美的關閉這個鏈接其實已經不是那麼關鍵了。所以,(我猜)RFC標準的制定者才決定以網絡丟包不太嚴重爲前提條件,而後根據第二個目標來計算TIME_WAIT狀態應該持續的時長。
再來看一下《UNIX網絡編程》在描述爲何須要TIME_WAIT狀態時的一段話:
Since the duration of the TIME_WAIT state is twice the MSL, this allows MSL seconds for packet in one direction to be lost, and another MSL seconds for the reply to be lost. By enforcing this rule, we are guaranteed that when we successfully establish a TCP connecton, all old duplicates from previous incarnations of the connection have expired in the network.
這段文字說明了TIME_WAIT狀態持續2MSL的時間可讓一個TCP鏈接的兩端發出的報文都從網絡中消失,從而保證下一個使用了相同四元組的tcp鏈接不會被上一個鏈接的報文所幹擾。
如何理解TIME_WAIT狀態持續2MSL的時間就可讓一個TCP鏈接的兩端發出的報文都從網絡中消失呢?
首先咱們須要瞭解以下要點:
TCP鏈接中的一端發送了FIN報文以後若是收不到對端針對該FIN的ACK,則會反覆屢次重傳FIN報文,大約持續幾分鐘;
被動關閉處於LAST_ACK狀態的一端在收到最後一個ACK以後不會發送任何報文,當即進入CLOSED狀態;
主動關閉的一端在收到被動關閉端發送過來的FIN報文並回復ACK以後進入TIME_WAIT狀態;
之因此TIME_WAIT狀態須要維持一段時間而不是進入CLOSED狀態,是由於須要處理對端可能重傳的FIN報文或其它一些因網絡緣由而延遲的數據報文,不處理這些報文可能致使先後兩個使用相同四元組的鏈接中的後一個鏈接出現異常(詳見UNIX網絡編程卷1的2.7節 第三版);
處於TIME_WAIT狀態的一端在收到重傳的FIN時會從新計時(rfc793 以及 linux kernel源代碼tcp_timewait_state_process函數)。
下面咱們開始分析爲何在發送了最後一個ACK報文以後須要等待2MSL時長來確保沒有任何屬於當前鏈接的報文還存活於網絡之中(前提是在這2MSL時間內再也不收到對方的FIN報文,但即便收到了對端的FIN報文也並不影響咱們的討論,由於若是收到FIN則會回覆ACK並從新計時)。
爲了便於描述,咱們設想有一個處於拆鏈過程當中的TCP鏈接,這個鏈接的兩端分別是A和B,其中A是主動關閉鏈接的一端,由於剛剛向對端發送了針對對端發送過來的FIN報文的ACK,此時正處於TIME_WAIT狀態;而B是被動關閉的一端,此時正處於LAST_ACK狀態,在收到最後一個ACK以前它會一直重傳FIN報文直至超時。隨着時間的流逝,A發送給B的ACK報文將會有兩種結局:
ACK報文在網絡中丟失;如前所述,這種狀況咱們不須要考慮,由於除非屢次重傳失敗,不然AB兩端的狀態不會發生變化直至某一個ACK再也不丟失。
ACK報文被B接收到。咱們假設A發送了ACK報文後過了一段時間t以後B才收到該ACK,則有 0 < t <= MSL。由於A並不知道它發送出去的ACK要多久對方纔能收到,因此A至少要維持MSL時長的TIME_WAIT狀態才能保證它的ACK從網絡中消失。同時處於LAST_ACK狀態的B由於收到了ACK,因此它直接就進入了CLOSED狀態,而不會向網絡發送任何報文。因此晃眼一看,A只須要等待1個MSL就夠了,但仔細想一下其實1個MSL是不行的,由於在B收到ACK前的一剎那,B可能由於沒收到ACK而重傳了一個FIN報文,這個FIN報文要從網絡中消失最多還須要一個MSL時長,因此A還須要多等一個MSL。
綜上所述,TIME_WAIT至少須要持續2MSL時長,這2個MSL中的第一個MSL是爲了等本身發出去的最後一個ACK從網絡中消失,而第二MSL是爲了等在對端收到ACK以前的一剎那可能重傳的FIN報文從網絡中消失。另外,雖說維持TIME_WAIT狀態一段時間有2個目的,但這段時間具體應該多長主要是爲了達成上述第二個目的而設計的。