TCP狀態遷移,CLOSE_WAIT & FIN_WAIT2 的問題

TCP狀態遷移
大家對netstat -a命令很熟悉吧,但是,你有沒有注意到STATE一欄呢,基本上顯示着established,time_wait,close_wait等,這些到底是什麼意思呢,在這篇文章,我將會詳細的闡述。
大家很明白TCP初始化連接三次握手吧:發SYN包,然後返回SYN/ACK包,再發ACK包,連接正式建立。但是這裏有點出入,當請求者收到SYS /ACK包後,就開始建立連接了,而被請求者第三次握手結束後才建立連接。但是大家明白關閉連接的工作原理嗎?關閉連接要四次握手:發FIN包,ACK 包,FIN包,ACK包,四次握手!!爲什麼呢,因爲TCP連接是全雙工,我關了你的連接,並不等於你關了我的連接。
客戶端TCP狀態遷移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服務器TCP狀態遷移:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
當客戶端開始連接時,服務器還處於LISTENING,客戶端發一個SYN包後,他就處於SYN_SENT狀態,服務器就處於SYS收到狀態,然後互相確認進入連接狀態ESTABLISHED.


當客戶端請求關閉連接時,客戶端發送一個FIN包後,客戶端就進入FIN_WAIT_1狀態,等待對方的確認包,服務器發送一個ACK包給客戶,客戶端收 到ACK包後結束FIN_WAIT_1狀態,進入FIN_WAIT_2狀態,等待服務器發過來的關閉請求,服務器發一個FIN包後,進入 CLOSE_WAIT狀態,當客戶端收到服務器的FIN包,FIN_WAIT_2狀態就結束,然後給服務器端的FIN包給以一個確認包,客戶端這時進入 TIME_WAIT,當服務器收到確認包後,CLOSE_WAIT狀態結束了,這時候服務器端真正的關閉了連接.但是客戶端還在TIME_WAIT狀態 下,什麼時候結束呢.我在這裏再講到一個新名詞:2MSL等待狀態,其實TIME_WAIT就是2MSL等待狀態,爲什麼要設置這個狀態,原因是有足夠的 時間讓ACK包到達服務器端,如果服務器端沒收到ACK包,超時了,然後重新發一個FIN包,直到服務器收到ACK包,TIME_WAIT狀態等待時間是 在TCP重新啓動後不連接任何請求的兩倍.
大家有沒有發現一個問題:如果對方在第三次握手的時候出問題,如發FIN包的時候,不知道什麼原因丟了這個包,然而這邊一直處在FIN_WAIT_2狀 態,而且TCP/IP並沒有設置這個狀態的過期時間,那他一直會保留這個狀態下去,越來越多的FIN_WAIT_2狀態會導致系統崩潰.

上面我碰到的這個問題主要因爲TCP的結束流程未走完,造成連接未釋放。現設客戶端主動斷開連接,流程如下

 

如上圖所示,

      Client                            消息                                    Server

         close()
                                      ------ FIN ------->
        FIN_WAIT1                                                         CLOSE_WAIT
                                      <----- ACK -------
        FIN_WAIT2
                                                                                  close()
                                       <------ FIN ------                    
        TIME_WAIT                                                       LAST_ACK      

                                      ------ ACK ------->
                                                                                   CLOSED
           CLOSED


由於Server的Socket在客戶端已經關閉時而沒有調用關閉,
造成服務器端的連接處在“掛起”狀態,而客戶端則處在等待應答的狀態上。
此問題的典型特徵是:
一端處於FIN_WAIT2 ,而另一端處於CLOSE_WAIT.

不過,根本問題還是程序寫的不好,有待提高

-------------------------------------------------------------------------

CLOSE_WAIT,TCP的癌症,TCP的朋友。

CLOSE_WAIT狀態的生成原因
首先我們知道,如果我們的服務器程序APACHE處於CLOSE_WAIT狀態的話,說明套接字是被動關閉的!

因爲如果是CLIENT端主動斷掉當前連接的話,那麼雙方關閉這個TCP連接共需要四個packet:

Client ---> FIN ---> Server

Client <--- ACK <--- Server

這時候Client端處於FIN_WAIT_2狀態;而Server 程序處於CLOSE_WAIT狀態。

Client <--- FIN <--- Server

這時Server 發送FIN給Client,Server 就置爲LAST_ACK狀態。

Client ---> ACK ---> Server

Client迴應了ACK,那麼Server 的套接字纔會真正置爲CLOSED狀態。

Server 程序處於CLOSE_WAIT狀態,而不是LAST_ACK狀態,說明還沒有發FIN給Client,那麼可能是在關閉連接之前還有許多數據要發送或者其他事要做,導致沒有發這個FIN packet。

通常來說,一個CLOSE_WAIT會維持至少2個小時的時間。如果有個流氓特地寫了個程序,給你造成一堆的CLOSE_WAIT,消耗

你的資源,那麼通常是等不到釋放那一刻,系統就已經解決崩潰了。

只能通過修改一下TCP/IP的參數,來縮短這個時間:修改tcp_keepalive_*系列參數有助於解決這個問題。

 

連接進程是通過一系列狀態表示的,這些狀態有:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-
1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT和
CLOSED。CLOSED表示沒有連接,各個狀態的意義如下: LISTEN - 偵聽來自遠方TCP端口的連接請求; SYN-SENT -
在發送連接請求後等待匹配的連接請求; SYN-RECEIVED - 在收到和發送一個連接請求後等待對連接請求的確認; ESTABLISHED
- 代表一個打開的連接,數據可以傳送給用戶; FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認;
FIN-WAIT-2 - 從遠程TCP等待連接中斷請求; CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求; CLOSING -
等待遠程TCP對連接中斷的確認; LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認; TIME-WAIT -
等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認; CLOSED - 沒有任何連接狀態;
TCP連接過程是狀態的轉換,促使發生狀態轉換的是用戶調用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS;傳送過來的數
據段,特別那些包括以下標記的數據段SYN,ACK,RST和FIN;還有超時,上面所說的都會時TCP狀態發生變化。

 

TCP連接的狀態轉換圖

 

n 多人都知道, 排除和定位網 絡或系統 故障 大有幫助,但是怎 牢牢地 這張圖 刻在 中呢?那 你就一定要 對這張圖 一個狀 態,及轉換的過程有深刻地認識,不能只停留在一知半解之中。下面對這張圖的 11 種狀態詳細解釋一下,以便加強記憶!不過在這之前,先回顧一下 TCP 建立連接的三次握手過程,以及關閉連接的四次握手過程。

1 、建立連接協議(三次握手)
1 )客戶端發送一個帶 SYN 標誌的 TCP 報文到服務器。這是三次握手過程中的報文 1

2 ) 服務器端迴應客戶端的,這是三次握手中的第 2 個報文,這個報文同時帶 ACK 標誌和 SYN 標誌。因此它表示對剛纔客戶端 SYN 報文的迴應;同時又標誌 SYN 給客戶端,詢問客戶端是否準備好進行數據通訊。

3 ) 客戶必須再次迴應服務段一個 ACK 報文,這是報文段 3

2 、連接終止協議(四次握手)
   由於 TCP 連接是全雙工的,因此每個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個 FIN 來終止這個方向的連接。收到一個 FIN 只意味着這一方向上沒有數據流動,一個 TCP 連接在收到一個 FIN 後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另一方執行被動關閉。

 ( 1 TCP 客戶端發送一個 FIN ,用來關閉客戶到服務器的數據傳送(報文段 4 )。
 ( 2 ) 服務器收到這個 FIN ,它發回一個 ACK ,確認序號爲收到的序號加 1 (報文段 5 )。和 SYN 一樣,一個 FIN 將佔用一個序號。
 ( 3 ) 服務器關閉客戶端的連接,發送一個 FIN 給客戶端(報文段 6 )。
 ( 4 ) 客戶段發回 ACK 報文確認,並將確認序號設置爲收到序號加 1 (報文段 7 )。

CLOSED: 這個沒什麼好說的了,表示初始狀態。

LISTEN: 這個也是非常容易理解的一個狀態,表示服務器端的某個 SOCKET 處於監聽狀態,可以接受連接了。

SYN_RCVD: 這個狀態表示接受到了 SYN 報文,在正常情況下,這個狀態是服務器端的 SOCKET 在建立 TCP 連接時的三次握手會話過程中的一箇中間狀態,很短暫,基本上用 netstat 你是很難看到這種狀態的,除非你特意寫了一個客戶端測試程序,故意將三次 TCP 握手過程中最後一個 ACK 報文不予發送。因此這種狀態時,當收到客戶端的 ACK 報文後,它會進入到 ESTABLISHED 狀態。

SYN_SENT: 這個狀態與 SYN_RCVD 遙想呼應,當客戶端 SOCKET 執行 CONNECT 連接時,它首先發送 SYN 報文,因此也隨即它會進入到了 SYN_SENT 狀態,並等待服務端的發送三次握手中的第 2 個報文。 SYN_SENT 狀態表示客戶端已發送 SYN 報文。

ESTABLISHED :這個容易理解了,表示連接已經建立了。

FIN_WAIT_1: 這個狀態要好好解釋一下,其實 FIN_WAIT_1 FIN_WAIT_2 狀態的真正含義都是表示等待對方的 FIN 報文。而這兩種狀態的區別是: FIN_WAIT_1 狀態實際上是當 SOCKET ESTABLISHED 狀態時,它想主動關閉連接,向對方發送了 FIN 報文,此時該 SOCKET 即進入到 FIN_WAIT_1 狀態。而當對方迴應 ACK 報文後,則進入到 FIN_WAIT_2 狀態,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應 ACK 報文,所以 FIN_WAIT_1 狀態一般是比較難見到的,而 FIN_WAIT_2 狀態還有時常常可以用 netstat 看到。

FIN_WAIT_2 :上面已經詳細解釋了這種狀態,實際上 FIN_WAIT_2 狀態下的 SOCKET ,表示半連接,也即有一方要求 close 連接,但另外還告訴對方,我暫時還有點數據需要傳送給你,稍後再關閉連接。

TIME_WAIT: 表示收到了對方的 FIN 報文,併發送出了 ACK 報文,就等 2MSL 後即可回到 CLOSED 可用狀態了。如果 FIN_WAIT_1 狀態下,收到了對方同時帶 FIN 標誌和 ACK 標誌的報文時,可以直接進入到 TIME_WAIT 狀態,而無須經過 FIN_WAIT_2 狀態。

CLOSING: 這種狀態比較特殊,實際情況中應該是很少見,屬於一種比較罕見的例外狀態。正常情況下,當你發送 FIN 報文後,按理來說是應該先收到(或同時收到)對方的 ACK 報文,再收到對方的 FIN 報文。但是 CLOSING 狀態表示你發送 FIN 報文後,並沒有收到對方的 ACK 報文,反而卻也收到了對方的 FIN 報文。什麼情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時 close 一個 SOCKET 的話,那麼就出現了雙方同時發送 FIN 報文的情況,也即會出現 CLOSING 狀態,表示雙方都正在關閉 SOCKET 連接。

CLOSE_WAIT: 這種狀態的含義其實是表示在等待關閉。怎麼理解呢?當對方 close 一個 SOCKET 後發送 FIN 報文給自己,你係統毫無疑問地會迴應一個 ACK 報文給對方,此時則進入到 CLOSE_WAIT 狀態。接下來呢,實際上你真正需要考慮的事情是察看你是否還有數據發送給對方,如果沒有的話,那麼你也就可以 close 這個 SOCKET ,發送 FIN 報文給對方,也即關閉連接。所以你在 CLOSE_WAIT 狀態下,需要完成的事情是等待你去關閉連接。

LAST_ACK: 這個狀態還是比較容易好理解的,它是被動關閉一方在發送 FIN 報文後,最後等待對方的 ACK 報文。當收到 ACK 報文後,也即可以進入到 CLOSED 可用狀態了。

最後有 2 個問題的回答,我自己分析後的結論(不一定保證 100% 正確)

1、 爲什麼建立連接協議是三次握手,而關閉連接卻是四次握手呢?

這是因爲服務端的 LISTEN 狀態下的 SOCKET 當收到 SYN 報文的建連請求後,它可以把 ACK SYN ACK 起應答作用,而 SYN 起同步作用)放在一個報文裏來發送。但關閉連接時,當收到對方的 FIN 報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可以未必會馬上會關閉 SOCKET, 也即你可能還需要發送一些數據給對方之後,再發送 FIN 報文給對方來表示你同意現在可以關閉連接了,所以它這裏的 ACK 報文和 FIN 報文多數情況下都是分開發送的。

2、 爲什麼 TIME_WAIT 狀態還需要等 2MSL 後才能返回到 CLOSED 狀態?

這是因爲:雖然雙方都同意關閉連接了,而且握手的 4 個報文也都協調和發送完畢,按理可以直接回到 CLOSED 狀態(就好比從 SYN_SEND 狀態到 ESTABLISH 狀態那樣);但是因爲我們必須要假想網絡是不可靠的,你無法保證你最後發送的 ACK 報文會一定被對方收到,因此對方處於 LAST_ACK 狀態下的 SOCKET 可能會因爲超時未收到 ACK 報文,而重發 FIN 報文,所以這個 TIME_WAIT 狀態的作用就是用來重發可能丟失的 ACK 報文,並保證於此。

發佈了80 篇原創文章 · 獲贊 8 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章