網路基礎問題整理:為什麼TCP四次揮手最後需要TIME_WAIT狀態?

Tony_Xian發表於2020-12-19

一:首先奉上
TCP三次握手的過程
TCP三次握手的過程
TCP連線釋放的過程:
TCP連線釋放的過程
二:
1。為什麼兩次就建立連線還要三次握手呢?這主要是為了防止已失效的連線請求報文又突然傳遞伺服器。
所謂“防止已失效的連線請求報文又突然傳遞伺服器。”是這樣一種情況:
A客戶端發出連線請求,因為連線請求報文丟失而未等到確認。於是A再次重傳了連線請求,建立了連線。資料傳輸完畢後,釋放了連線。現在假設那第一個請求只是因為網路節點長時間滯留了,使得它在第二個連線釋放後才到達B伺服器,那麼B會以為這是一個新的連線請求,於是就向A發了個連線確認,注意了:如果沒有最後一次的確認B會一廂情願的以為連線已經建立,可人家A同學一看那個B給的是什麼呀!跟自己沒關係,簡單粗暴的丟掉。這時B孩子還傻傻的等著A給他發資料,就這樣,B白白浪費的大把的時光和資源。
那B會一直傻等嗎?當然不是,它的等待也是有限的,答案就是保活計時器。

2.為什麼要有四次揮手的TIME_WAIT的狀態?
(1)第一是為了保證最後一個的一個ACK報文能到達B。這個ACK報文有可能丟失,因而使得處在LAST_ACK狀態得不到對已傳送的FIN+ACK報文的確認,B會超時重傳這個FIN+ACk ,而A就能在這TIME_WAIT時間(2MSL)裡收到這個重傳的報文,A就可以重傳一次確認,如果沒有這個TIME_WAIT, 那B重傳的FIN_ACK,可A早就走了,自然不會再重發確認,這樣B就無法按照正常步驟進入CLOSE 狀態。
(2)第二是防止“已失效的報文連線請求”,A在TIME_WAIT中,經過這2MSL的時間,就可以使本連結持續的時間內產生的所有連線消失,這樣就可以使下一個新的連線中不會出現這樣舊的連線請求報文段。
2.2 聰明的你會發現誰先關閉誰就有一個TIME_WAIT的狀態;
在linux的網路程式設計中,如果伺服器如果先關閉,你會發現,現在想要立馬再次啟動伺服器,就會報錯說這個埠號被佔用著,那就是因為有這個TIME_WAIT,2msl的時間.那麼怎麼解決 ?
解決:setsockopt()函式。在這就不多說了。

根據第三版《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個目的,但這段時間具體應該多長主要是為了達成上述第二個目的而設計的。

轉自:
https://blog.csdn.net/lthirdonel/article/details/103015627
https://www.cnblogs.com/joker1937/p/12487776.html

相關文章