TCP狀態轉換圖詳解 tcp協議講解
在前面,已經介紹了TCP協議的三路握手和四次揮手。如下圖所示,TCP通訊過程包括三個步驟:建立TCP連線通道(三次握手)、資料傳輸、斷開TCP連線通道(四次揮手)。
這裡進一步探究TCP三路握手和四次揮手過程中的狀態變遷以及資料傳輸過程。先看TCP狀態狀態轉換圖。
上半部分是TCP三路握手過程的狀態變遷,下半部分是TCP四次揮手過程的狀態變遷。
- CLOSED:起始點,在超時或者連線關閉時候進入此狀態,這並不是一個真正的狀態,而是這個狀態圖的假想起點和終點。
- LISTEN:伺服器端等待連線的狀態。伺服器經過 socket,bind,listen 函式之後進入此狀態,開始監聽客戶端發過來的連線請求。此稱為應用程式被動開啟(等到客戶端連線請求)。
- SYN_SENT:第一次握手發生階段,客戶端發起連線。客戶端呼叫 connect,傳送 SYN 給伺服器端,然後進入 SYN_SENT 狀態,等待伺服器端確認(三次握手中的第二個報文)。如果伺服器端不能連線,則直接進入CLOSED狀態。
- SYN_RCVD:第二次握手發生階段,跟 3 對應,這裡是伺服器端接收到了客戶端的 SYN,此時伺服器由 LISTEN 進入 SYN_RCVD狀態,同時伺服器端迴應一個 ACK,然後再發送一個 SYN 即 SYN+ACK 給客戶端。狀態圖中還描繪了這樣一種情況,當客戶端在傳送 SYN 的同時也收到伺服器端的 SYN請求,即兩個同時發起連線請求,那麼客戶端就會從 SYN_SENT 轉換到 SYN_REVD 狀態。
- ESTABLISHED:第三次握手發生階段,客戶端接收到伺服器端的 ACK 包(ACK,SYN)之後,也會發送一個 ACK 確認包,客戶端進入 ESTABLISHED 狀態,表明客戶端這邊已經準備好,但TCP 需要兩端都準備好才可以進行資料傳輸。伺服器端收到客戶端的 ACK 之後會從 SYN_RCVD 狀態轉移到 ESTABLISHED 狀態,表明伺服器端也準備好進行資料傳輸了。這樣客戶端和伺服器端都是 ESTABLISHED 狀態,就可以進行後面的資料傳輸了。所以 ESTABLISHED 也可以說是一個數據傳送狀態。
上面就是 TCP 三次握手過程的狀態變遷。結合第一張三次握手過程圖,從報文的角度看狀態變遷:SYN_SENT 狀態表示已經客戶端已經發送了 SYN 報文,SYN_RCVD 狀態表示伺服器端已經接收到了 SYN 報文。
下面看看TCP四次揮手過程的狀態變遷。結合第一張四次揮手過程圖來理解。
- FIN_WAIT_1:第一次揮手。主動關閉的一方(執行主動關閉的一方既可以是客戶端,也可以是伺服器端,這裡以客戶端執行主動關閉為例),終止連線時,傳送 FIN 給對方,然後等待對方返回 ACK 。呼叫 close() 第一次揮手就進入此狀態。
- CLOSE_WAIT:接收到FIN 之後,被動關閉的一方進入此狀態。具體動作是接收到 FIN,同時傳送 ACK。之所以叫 CLOSE_WAIT 可以理解為被動關閉的一方此時正在等待上層應用程式發出關閉連線指令。前面已經說過,TCP關閉是全雙工過程,這裡客戶端執行了主動關閉,被動方伺服器端接收到FIN 後也需要呼叫 close 關閉,這個 CLOSE_WAIT 就是處於這個狀態,等待發送 FIN,傳送了FIN 則進入 LAST_ACK 狀態。
- FIN_WAIT_2:主動端(這裡是客戶端)先執行主動關閉傳送FIN,然後接收到被動方返回的 ACK 後進入此狀態。
- LAST_ACK:被動方(伺服器端)發起關閉請求,由狀態2 進入此狀態,具體動作是傳送 FIN給對方,同時在接收到ACK 時進入CLOSED狀態。
- CLOSING:兩邊同時發起關閉請求時(即主動方傳送FIN,等待被動方返回ACK,同時被動方也傳送了FIN,主動方接收到了FIN之後,傳送ACK給被動方),主動方會由FIN_WAIT_1 進入此狀態,等待被動方返回ACK。
- TIME_WAIT:從狀態變遷圖會看到,四次揮手操作最後都會經過這樣一個狀態然後進入CLOSED狀態。共有三個狀態會進入該狀態
- 由CLOSING進入:同時發起關閉情況下,當主動端接收到ACK後,進入此狀態,實際上這裡的同時是這樣的情況:客戶端發起關閉請求,傳送FIN之後等待伺服器端迴應ACK,但此時伺服器端同時也發起關閉請求,也傳送了FIN,並且被客戶端先於ACK接收到。
- 由FIN_WAIT_1進入:發起關閉後,傳送了FIN,等待ACK的時候,正好被動方(伺服器端)也發起關閉請求,傳送了FIN,這時客戶端接收到了先前ACK,也收到了對方的FIN,然後傳送ACK(對對方FIN的迴應),與CLOSING進入的狀態不同的是接收到FIN和ACK的先後順序。
- 由FIN_WAIT_2進入:這是不同時的情況,主動方在完成自身發起的主動關閉請求後,接收到了對方傳送過來的FIN,然後迴應 ACK。
下面來看看這個看似有點多餘的TIME_WAIT狀態:從上面進入TIME_WAIT狀態的三個狀態動作來看(可以直接看狀態變遷圖)都是主動方最後迴應一個ACK(CLOSING實際上前面的那個FIN_WAIT_1狀態就已經迴應了ACK)。
先考慮這樣的一個情況,假如這個最後迴應的ACK丟失了,也就是伺服器端接收不到這個ACK,那麼伺服器將繼續傳送它最終的那個FIN,因此客戶端必須維護狀態資訊(TIME_WAIT)允許它重發最後的那個ACK。如果沒有這個TIME_WAIT狀態,客戶端處於CLOSED狀態(開頭就說了CLOSED狀態實際並不存在,是我們為了方便描述假想的),那麼客戶端將響應RST,伺服器端收到後會將該RST分節解釋成一個錯誤,也就不能實現最後的全雙工關閉了(可能是主動方單方的關閉)。所以要實現TCP全雙工連線的正常終止(兩方都關閉連線),必須處理終止過程中四個分節任何一個分節的丟失情況,那麼主動關閉連線的主動端必須維持TIME_WAIT狀態,最後一個迴應ACK的是主動執行關閉的那端。從變遷圖可以看出,如果沒有TIME_WAIT狀態,我們將沒有任何機制來保證最後一個ACK能夠正常到達。前面的FIN,ACK正常到達均有相應的狀態對應。
還有這樣一種情況,如果目前的通訊雙方都已經呼叫了 close(),都到達了CLOSED狀態,沒有TIME_WAIT狀態時,會出現這樣一種情況,現在有一個新的連線被建立起來,使用的IP地址和埠和這個先前到達了CLOSED狀態的完全相同,假定原先的連線中還有資料報殘存在網路之中,這樣新的連線建立以後傳輸的資料極有可能就是原先的連線的資料報,為了防止這一點,TCP不允許從處於TIME_WAIT狀態的socket 建立一個連線。處於TIME_WAIT狀態的 socket 在等待了兩倍的MSL時間之後,將會轉變為CLOSED狀態。這裡TIME_WAIT狀態持續的時間是2MSL(MSL是任何IP資料報能夠在因特網中存活的最長時間),足以讓這兩個方向上的資料包被丟棄(最長是2MSL)。通過實施這個規則,我們就能保證每成功建立一個TCP連線時,來自該連線先前化身的老的重複分組都已經在網路中消逝了。
綜上來看:TIME_WAIT存在的兩個理由就是
- 可靠地實現TCP全雙工連線的終止;
- 允許老的重複分節(資料報)在網路中消逝。
參考資料《UNP》《TCP/IP Vol.1》