根據第三版《UNIX網絡編程 卷1》2.7節,TIME_WAIT狀態的主要目的有兩個:
-
優雅的關閉TCP連接,也就是盡量保證被動關閉的一端收到它自己發出去的FIN報文的ACK確認報文;
-
處理延遲的重復報文,這主要是為了避免前后兩個使用相同四元組的連接中的前一個連接的報文干擾后一個連接。
很明顯,要實現上述兩個目標,TIME_WAIT狀態需要持續一段時間,但這段時間應該是多長呢?
如果只考慮上述第一個目標,則TIME_WAIT狀態需要持續的時間應該參考對端的RTO(重傳超時時間)以及MSL(報文在網絡中的最大生存時間)來計算而不是僅僅按MSL來計算,因為只要對端沒有收到針對FIN報文的ACK,就會一直持續重傳FIN報文直到重傳超時,所以最能實現完美關閉連接的時長計算方式應該是從對端發送第一個FIN報文開始計時到它最后一次重傳FIN報文這段時長加上MSL,但這個計算方式過於保守,只有在所有的ACK報文都丟失的情況下才需要這么長的時間;另外,第一個目標雖然重要,但並不十分關鍵,因為既然已經到了關閉連接的最后一步,說明在這個TCP連接上的所有用戶數據已經完成可靠傳輸,所以要不要完美的關閉這個連接其實已經不是那么關鍵了。因此,(我猜)RFC標准的制定者才決定以網絡丟包不太嚴重為前提條件,然后根據第二個目標來計算TIME_WAIT狀態應該持續的時長。
對於剛才說的第二點,如何理解TIME_WAIT狀態持續2MSL的時間就可以避免前后兩個使用相同四元組的連接中的前一個連接的報文干擾后一個連接呢?
首先我們需要了解如下要點:
-
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個目的,但這段時間具體應該多長主要是為了達成上述第二個目的而設計的。