TCP11種狀態

 

二、所有11種狀態服務器

    2.一、客戶端獨有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT 。網絡

    2.二、服務器獨有的:(1)LISTEN (2)SYN_RCVD (3)CLOSE_WAIT (4)LAST_ACK 。併發

    2.三、共有的:(1)CLOSED (2)ESTABLISHED 。oracle

 

 

TCP狀態遷移socket

你們對netstat -a命令很熟悉,可是,你有沒有注意到STATE一欄呢,基本上顯示着established,time_wait,close_wait等,這些究竟是 什麼意思呢,在這篇文章,我將會詳細的闡述。tcp

你們很明白TCP初始化鏈接三次握手吧:發SYN包,而後返回SYN/ACK包,再發ACK包,鏈接正式創建。可是這裏有點出入,當請求者收到SYS /ACK包後,就開始創建鏈接了,而被請求者第三次握手結束後才創建鏈接。可是你們明白關閉鏈接的工做原理嗎?關閉鏈接要四次握手:發FIN包,ACK 包,FIN包,ACK包,四次握手!!爲何呢,由於TCP鏈接是全雙工,我關了你的鏈接,並不等於你關了個人鏈接。測試

客戶端TCP狀態遷移:spa

 

CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSEDcode

服務器TCP狀態遷移:blog

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_*系列參數有助於解決這個 問題。

解決這個問題的方法是修改系統的參數,系統默認超時時間的是7200秒,也就是2小時, 這個太大了,能夠修改以下幾個參數:

sysctl -w net.ipv4.tcp_keepalive_time=30
sysctl -w net.ipv4.tcp_keepalive_probes=2
sysctl -w net.ipv4.tcp_keepalive_intvl=2

而後,執行sysctl命令使修改生效。

鏈接進程是經過一系列狀態表示的,這些狀態有:

LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE- WAIT,CLOSING,LAST-ACK,TIME-WAIT和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狀態發生變化。

這個圖n多人都 知道,它對排除和定 位網絡或系統故障時大有幫助,可是怎樣緊緊地將這張圖刻在腦中呢?那麼你就必定要對 這張圖的每個狀態,及轉換的過程有深入地認識,不能只停留在只知其一;不知其二之中。下面對這張圖的11種狀 態詳細解釋一下,以便增強記憶!不過在這以前,先回顧一下TCP創建鏈接的三次握手過程,以及關閉鏈接的四次握手過程。

 

一、創建鏈接協議(三次握手)

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

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

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

二、鏈接終止協議(四次握手)

   因爲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%正確)

一、 爲何創建鏈接協議是三次握手,而關閉鏈接倒是四次握手呢?

這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它能夠把ACK和SYN(ACK起 應答做用,而SYN起同步做用)放在一個報文裏來發送。但關閉鏈接時,當收到對方的FIN報文 通知時,它僅僅表示對方沒有數據發送給你了;但未必你全部的數據都所有發送給對方了,因此你能夠未必會立刻會關閉SOCKET,也即你可能還須要發送一些數據給對方以後,再發送FIN報文給對方來表示你贊成如今能夠關閉鏈接了,因此它這裏的ACK報文 和FIN報文多數狀況下都是分開發送的。

二、 爲何TIME_WAIT狀態還須要等2MSL後才能返回到CLOSED狀 態?

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

     斷開鏈接的時候, 當發起主動關閉的左邊這方發送一個FIN過去後,

右邊被動關閉的這方要回應一個ACK,這個ACK是TCP迴應的,而不是應用程序發送的,

此時,被動關閉的一方就處於CLOSE_WAIT狀態了。

若是此時被動關閉的這一方再也不繼續調用closesocket,那麼他就不會發送接下來的FIN,致使本身總是處於CLOSE_WAIT。

只有被動關閉的這一方調用了 closesocket,纔會發送一個FIN給主動關閉的這一方,同時也使得本身的狀態變遷爲LAST_ACK。 

好比被動關閉的是客戶端

當對方調用closesocket的時候,你的程序正在 

 

int nRet = recv(s,....);
if (nRet == SOCKET_ERROR)
{
    // closesocket(s);&#160;
    return FALSE;
}

不少人就是忘記了那句closesocket,這種代碼太常見了。 

個人理解,

當主動關閉的一方發送FIN到被動關閉這邊後,被動關閉這邊的TCP立刻迴應一個ACK過去,同時向上面應用程序提交一個ERROR,

致使上面的SOCKET的send或者recv返回SOCKET_ERROR.

正常狀況下,若是上面在返回SOCKET_ERROR後調用了closesocket, 那麼被動關閉的者一方的TCP就會發送一個FIN過去,本身的狀態就變遷到LAST_ACK.

服務器上出現大量的close_wait的例子和解決方法(例子從網上找的,基本差很少)

 

$ /usr/sbin/lsof -i | grep 6800

$ /usr/sbin/lsof -i | grep 6800

oracle    22725 oracle9i    3u IPv4 18621468       TCP RHEL3:6800 (LISTEN)

oracle    22725 oracle9i    4u IPv4 18621469       TCP RHEL3:6800->RHEL3:2174 (CLOSE_WAIT)

oracle    22725 oracle9i    8u IPv4 18621568       TCP RHEL3:6800->RHEL3:2175 (CLOSE_WAIT)

oracle    22725 oracle9i    9u IPv4 18621578       TCP RHEL3:6800->RHEL3:2176 (CLOSE_WAIT)

oracle    22726 oracle9i    3u IPv4 18621468       TCP RHEL3:6800 (LISTEN)

oracle    22726 oracle9i    4u IPv4 18621469       TCP RHEL3:6800->RHEL3:2174 (CLOSE_WAIT)

oracle    22726 oracle9i    8u IPv4 18621568       TCP RHEL3:6800->RHEL3:2175 (CLOSE_WAIT)

oracle    22726 oracle9i    9u IPv4 18621578       TCP RHEL3:6800->RHEL3:2176 (CLOSE_WAIT)

$ kill -9 22725
# 22725, 22726就是使用該6800端口的進程號(PID)。
$ /usr/sbin/lsof -i | grep 6800

 

進程被kill時,會釋放佔用的全部連接句柄。 

該問題的出現緣由網上處處都是,也就是Socket的Client端出現異常沒有Close就退出了

相關文章
相關標籤/搜索