TCP連接管理(二):TCP狀態遷移

TCP連接管理(二):TCP狀態遷移


上篇文章介紹了一個常規的TCP連接從建立到釋放的簡單過程,本文繼續探討連接創建與斷開時的狀態遷移過程。

一、協議狀態機

我們已經知道關於一個TCP連接啟動終止的規則和不同階段需要發送的各種類型報文段,這些規則是由TCP的協議狀態機決定的,當前狀態也會在觸發各種條件後發生改變,如收到或發送特定報文段、計時器超時、應用層讀寫操作等。下圖展示了一個完整的TCP狀態遷移過程(該圖出自RFC793):

TCP連接管理(二):TCP狀態遷移


上圖有如下部分需要注意:

① 導向ESTAB(establish)狀態的兩種轉換規則用於開啟一個連接,從ESTAB狀態導出的兩種規則用於結束一個連接,數據傳輸過程均發生在ESTAB狀態。後續文章中將詳細說明該狀態。

② FINWAIT-1、FINWAIT-2及TIME WAIT狀態為“主動關閉”的狀態集合,表示本地應用程序發起一個關閉連接請求時會進入的狀態;CLOSE WAIT、LAST-ACK為“被動關閉”的狀態集合,表示先收到斷開連接請求時會進入的狀態。

③ CLOSED狀態為TCP連接的開始狀態點和終止狀態點,從netstat命令中並不能看到該狀態。

④ 從LISTEN進入SYN SENT狀態是TCP允許的狀態遷移過程,但使用較少;從SYN RCVD狀態退會到LISTEN狀態的過程只有在執行被動打開時是合法的。

下圖顯示了正常的TCP連接建立與終止過程與客戶端和服務器經歷的各種狀態,是上圖的簡略版本,省略了選項與初始序列號等細節。圖中左側客戶端執行主動打開操作,右側服務器執行被動打開操作;通信雙方均可用執行主動關閉操作,下圖以客戶端主動關閉為例:

TCP連接管理(二):TCP狀態遷移


二、MSL

MSL為報文的最大段生存期(Maximum Segment Lifetime),即任何報文被丟棄前在網絡中被允許存在的最長時間。因為TCP報文以IP數據報的形式傳輸,IP數據報有TTL字段和跳數限制(現在通常直接使用TTL字段記錄跳數),這兩個字段限制了IP數據報的有效生存時間。

RFC793中將MSL規定為2分鐘(120s),但在大部分系統中該數值是可用修改的。Linux系統中使用系統變量net.ipv4.tcp_fin_timeout記錄了MSL時間(以秒為單位)可用如下方法查看:

TCP連接管理(二):TCP狀態遷移


該值保存在文件/proc/sys/net/ipv4/tcp_fin_timeout中,因此也可用如下方法查看:

TCP連接管理(二):TCP狀態遷移


由此可知該系統的MSL為60s。Windows中默認的MSL為120s,可通過修改註冊表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay鍵值的方法修改,注意該鍵值為2MSL即TIME WAIT狀態時間,如需修改MSL為60s則需要把該鍵值設為120s,如下圖:

TCP連接管理(二):TCP狀態遷移


windows server 2000以後系統不存在該鍵值,新建32位DWORD值即可。

三、TIME WAIT

TIMEWAIT狀態也稱為2MSL等待狀態,TCP在該狀態中會等待兩倍於最大段生存期的時間。當MSL時間已經確定時,TCP執行主動關閉併發送最後一個ACK後會進入TIMEWAIT狀態,持續2MSL時間後使連接進入CLOSED狀態。設置這個等待時間主要出於以下兩點原因:

①當被動關閉端(此處默認為服務器端)在正常通信階段(即ESTAB階段)發出的一個重傳報文由於瞬間的網絡阻塞、環路等原因延遲到達主動關閉端(此處默認為客戶端),在這個延遲時間內客戶端關閉了連接並使用相同五元組建立了一個新連接,之後這個延遲的報文到達客戶端,如此就會引起客戶端無法識別該報文屬於當前連接還是已經被關閉了的上一個連接。當等待2MSL時間後,網絡中傳輸的TCP報文因超過最大生存時間已經全部消亡,因此執行主動關閉的一端可以釋放該連接。

②執行被動關閉端在發出最後一個FIN後進入LAST-ACK狀態等待主動關閉端發送的ACK確認,此時可分為如下情況:

A) 被動關閉端正常收到ACK確認,狀態進入CLOSED。

B) 被動關閉端未收到ACK包,等待ACK超時後重傳FIN。若此時主動關閉端處於TIMEWAIT狀態,則繼續發送ACK包直到被動關閉端收到進入CLOSED狀態。

C) 被動關閉端未收到ACK包,等待ACK超時後重傳FIN。若此時主動關閉端TIMEWAIT超時進入CLOSED狀態,則發送RST重置連接,被動關閉端收到後進入CLOSED狀態。

D) 主動關閉端在發送完第一個FIN後因死機、斷電等原因即進入無響應狀態,被動關閉端會觸發重傳機制不斷重發FIN包直到重傳超時,之後被動關閉端自行重置該連接進入CLOSED狀態(重傳超時的計算方式是一個複雜的過程,將在後續文章中詳細說明)。

在大部分系統的實現中都採用了一種嚴格的措施,即如果一個端口號被處於2MSL狀態的任何通信端所採用,那麼該端口將不能被再次使用,若一個TCP連接要被其他進程重新使用,它必須等待2MSL時間的延遲。下圖中使用sock程序建立了一個處於LISTEN狀態的連接:

TCP連接管理(二):TCP狀態遷移


此時連接進入掛起狀態等待主動打開端發送的SYN,使用另一個終端可查看該端口的監聽狀態:

TCP連接管理(二):TCP狀態遷移


現在使用另一臺設備的終端連接該地址的65500端口:

TCP連接管理(二):TCP狀態遷移


連接成功後再次查看狀態:

TCP連接管理(二):TCP狀態遷移


此時新連接已經建立,應用程序使用該進程管理這個新連接,並開啟另一個進程繼續監聽連接請求。現手動關閉服務器端的監聽進程並嘗試重新監聽相同端口:

TCP連接管理(二):TCP狀態遷移


當嘗試重新啟動服務時會輸出一條錯誤信息提示該地址已被佔用,這是由於前一個連接處於2MSL狀態導致的,這是對於端口號重複使用的最嚴格的限制。此時可使用netstat命令查看該連接的狀態:

TCP連接管理(二):TCP狀態遷移


我們能夠通過某種方法使客戶端(主動關閉端)使用特定源端口建立連接,用於觀察在TIMEWAIT狀態下新建連接時TCP的行為。首先繼續監聽65500端口:

TCP連接管理(二):TCP狀態遷移


打開該服務器的另一個模擬終端,連接本地的65500端口:

TCP連接管理(二):TCP狀態遷移


上圖可知客戶端使用本地的50140端口與服務端的65500端口建立了連接,此時斷開客戶端的連接(使客戶端執行主動關閉操作),並指定同一源端口再次嘗試建立連接:

TCP連接管理(二):TCP狀態遷移


應用程序報錯提示該地址已經被佔用,此時在客戶端上查看連接狀態:

TCP連接管理(二):TCP狀態遷移


可以看到源端口(50140)進入TIMEWAIT狀態,必須等待2MSL時間後才能重新使用該端口建立連接。

在大部分系統中提供了一種高於默認行為的連接使用方式,可以使客戶端在TIMEWAIT狀態下強制使用同一端口號建立連接,雖然違背了TCP協議的規範,但這種行為在RFC1122和RFC6191中是允許的。除非我們有充足的理由相信新連接的報文段不會因為序列號、時間戳等原因與前一個連接的報文發生混淆,否則請保留TCP默認的連接管理方式,該方式可使在連接中發送的報文段足夠的可靠且可控。

四、平靜時間

現假設如下情況:客戶端發送的一部分報文段由於網絡原因發生重傳,之後發送FIN並接收FIN、ACK進入TIMEWAIT狀態,此時客戶端崩潰重啟並繼續使用崩潰前的連接(即五元組),然後之前重傳的報文到達了服務端,則這些報文會被錯誤地認為是屬於崩潰後新建立的連接的報文。

為避免這種情況,TCP規定在重啟後的MSL時間內不能建立任何連接,以等待崩潰前發送的報文段在網絡中消亡,這段時間就叫作平靜時間(Quiet Time)。但只有極少的實現方式遵循這一規則,因為大部分系統的重啟時間都大於MSL時間。

五、FINWAIT-2

當主動關閉端發送一個FIN並收到對這個報文的確認ACK後即進入FINWAIT-2狀態,除非出現半關閉的情況,否則TCP將等待收到對端發送的FIN後進入TIMEWAIT狀態。主動關閉端可以永遠處於FINWAIT-2狀態,被動關閉端也可以永遠處於CLOSEWAIT狀態,直到應用程序認為可以釋放該連接。為避免出現無限等待的情況,應用程序可直接使用全關閉操作,並設置一個定時器(MSL),當定時器超時時連接一直處於空閒狀態,則將該連接狀態遷移至CLOSED並釋放連接資源。Linux系統中該定時器默認時間為60s。

六、同時打開與同時關閉

當同時打開的情況發生時,通信雙方同時發送一個SYN然後進入SYNSENT狀態,各自收到對端的SYN後進入SYNRCVD狀態,併發送一個新的SYN和對前一個SYN的確認ACK,當兩端都收到SYN和ACK後進入ESTAB狀態。TCP經過專門設計後能夠處理以上情況,前提是通信雙方的源、目的端口號互相對應,此時可以只打開一條連接。

當應用程序同時發送關閉連接的消息後,通信兩端的狀態都會從ESTAB狀態遷移至FINWAIT-1,同時他們都會向對端發送一個FIN報文段。在收到對端傳來的FIN後,本地通信端從FINWAIT-1狀態進入CLOSING狀態,然後發送最終的ACK。當各自收到對端的ACK後,兩端將狀態更改為TIMEWAIT,初始化2MSL等待過程。


分享到:


相關文章: