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 |
由於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,那麼可能是在關閉連線之前還有許多資料要傳送或者其 他事要做,導致沒有發這個FINpacket。
通常來說,一個CLOSE_WAIT會維持至少2個小時的時間。如果有個流氓特地寫了個程式,給你造成一堆的 CLOSE_WAIT,消耗你的資源,那麼通常是等不到釋放那一刻,系統就已經解決崩潰了。
只能通過修改一下TCP/IP的引數,來縮短這個時間:修改tcp_keepalive_*系列引數有助於解決這個 問題。
解決這個問題的方法是修改系統的引數,系統預設超時時間的是7200秒,也就是2小時, 這個太大了,可以修改如下幾個引數:
sysctl -w net.ipv4.tcp_keepalive_time=30 |
然後,執行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、建立連線協議(三次握手)
(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報 文,並保證於此。
斷開連線的時候, 當發起主動關閉的左邊這方傳送一個FIN過去後,
右邊被動關閉的這方要回應一個ACK,這個ACK是TCP回應的,而不是應用程式傳送的,
此時,被動關閉的一方就處於CLOSE_WAIT狀態了。
如果此時被動關閉的這一方不再繼續呼叫closesocket,那麼他就不會傳送接下來的FIN,導致自己老是處於CLOSE_WAIT。
只有被動關閉的這一方呼叫了 closesocket,才會傳送一個FIN給主動關閉的這一方,同時也使得自己的狀態變遷為LAST_ACK。
比如被動關閉的是客戶端.
當對方呼叫closesocket的時候,你的程式正在
int nRet = recv(s,....); |
很多人就是忘記了那句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 |
程式被kill時,會釋放佔用的所有連結控制程式碼。
該問題的出現原因網上到處都是,也就是Socket的Client端出現異常沒有Close就退出了。
相關文章
- TCP埠狀態說明ESTABLISHED、TIME_WAIT、 CLOSE_WAITTCPAI
- TCP連線的TIME_WAIT和CLOSE_WAIT 狀態解說TCPAI
- PV 與 PVC 狀態遷移
- KVM線上遷移(動態遷移)
- 關於 Laravel 遷移遇到的問題Laravel
- 遷移學習中的BN問題遷移學習
- MySQL遷移檔案的小問題MySql
- 處理表的行遷移的問題
- “遷移策略+新容器執行時”應對有狀態應用的冷熱遷移挑戰
- PostgreSQL狀態變遷SQL
- 行遷移(鏈化行)問題
- 遷移Qt專案的路徑問題QT
- 使用資料泵遷移遇到的問題
- 初次使用無狀態sessionbean的問題SessionBean
- 理解 TCP(四):狀態流轉TCP
- 資料遷移中需要考慮的問題
- 資料遷移部分問題總結
- 遷移後帶庫備份問題
- 【演算法】狀態之美,TCP/IP狀態轉換探索演算法TCP
- 記錄一次XTTS遷移碰到的問題TTS
- GoldenGate資料遷移的問題總結(一)Go
- GoldenGate資料遷移的問題總結(二)Go
- 資料遷移中的幾個問題總結
- WebSphere客戶端遷移的一般問題Web客戶端
- 利用RMAN遷移表空間碰到的問題(五)
- 利用RMAN遷移表空間碰到的問題(四)
- 利用RMAN遷移表空間碰到的問題(三)
- 利用RMAN遷移表空間碰到的問題(二)
- 利用RMAN遷移表空間碰到的問題(一)
- 遷移後帶庫備份問題(二)
- 使用bulkCollect解決資料遷移問題
- LINUX netstat連線狀態解析及TCP狀態轉換LinuxTCP
- 遷移後處理外來鍵約束的問題
- TCP的狀態 (SYN, FIN, ACK, PSH, RST, URG)TCP
- React專案實踐(二)一個登入頁面的狀態遷移React
- 遷移資料庫資料考慮問題資料庫
- 統計TCP連線數和狀態TCP
- TCP連線狀態異常記錄TCP