1. 程式人生 > >TCP的狀態,兼談Close_Wait和Time_Wait的狀態 (keepalive機制)

TCP的狀態,兼談Close_Wait和Time_Wait的狀態 (keepalive機制)

一TCP的狀態: 
1)、LISTEN:首先服務端需要開啟一個socket進行監聽,狀態為LISTEN. /* The socket is listening for incoming connections. 偵聽來自遠方TCP埠的連線請求 */ 
2)、SYN_SENT:客戶端通過應用程式呼叫connect進行active open.於是客戶端tcp傳送一個SYN以請求建立一個連線.之後狀態置為SYN_SENT. /*The socket is actively attempting to establish a connection. 在傳送連線請求後等待匹配的連線請求 */ 
3)、SYN_RECV:服務端應發出ACK確認客戶端的SYN,同時自己向客戶端傳送一個SYN.之後狀態置為SYN_RECV /* A connection request has been received from the network. 在收到和傳送一個連線請求後等待對連線請求的確認 */ 

4)、ESTABLISHED: 代表一個開啟的連線,雙方可以進行或已經在資料互動了。/* The socket has an established connection. 代表一個開啟的連線,資料可以傳送給使用者 */ 
5)、FIN_WAIT1:主動關閉(active close)端應用程式呼叫close,於是其TCP發出FIN請求主動關閉連線,之後進入FIN_WAIT1狀態./* The socket is closed, and the connection is shutting down. 等待遠端TCP的連線中斷請求,或先前的連線中斷請求的確認 */ 
6)、CLOSE_WAIT:被動關閉(passive close)端TCP接到FIN後,就發出ACK以迴應FIN請求(它的接收也作為檔案結束符傳遞給上層應用程式),並進入CLOSE_WAIT. /* The remote end has shut down, waiting for the socket to close. 等待從本地使用者發來的連線中斷請求 */ 

7)、FIN_WAIT2:主動關閉端接到ACK後,就進入了FIN-WAIT-2 ./* Connection is closed, and the socket is waiting for a shutdown from the remote end. 從遠端TCP等待連線中斷請求 */ 
8)、LAST_ACK:被動關閉端一段時間後,接收到檔案結束符的應用程式將呼叫CLOSE關閉連線。這導致它的TCP也傳送一個 FIN,等待對方的ACK.就進入了LAST-ACK . /* The remote end has shut down, and the socket is closed. Waiting for acknowledgement. 等待原來發向遠端TCP的連線中斷請求的確認 */ 

9)、TIME_WAIT:在主動關閉端接收到FIN後,TCP就傳送ACK包,並進入TIME-WAIT狀態。/* The socket is waiting after close to handle packets still in the network.等待足夠的時間以確保遠端TCP接收到連線中斷請求的確認 */ 
10)、CLOSING: 比較少見./* Both sockets are shut down but we still don't have all our data sent. 等待遠端TCP對連線中斷的確認 */ 
11)、CLOSED: 被動關閉端在接受到ACK包後,就進入了closed的狀態。連線結束./* The socket is not being used. 沒有任何連線狀態 */ 
TCP狀態圖: 


二TCP正常關閉連線的狀態變化根據《TCP/IP詳解》中的TCP的建立和終止中有關"TCP的終止"的講解TCP的終止通過雙方的四次握手實現。發起終止的一方執行主動關閉,響應的另一方執行被動關閉。1. 發起方更改狀態為FIN_WAIT_1,關閉應用程式程序,發出一個TCP的FIN段;2. 接收方收到FIN段,返回一個帶確認序號的ACK,同時向自己對應的程序傳送一個檔案結束符EOF,同時更改狀態為CLOSE_WAIT,發起方接到ACK後狀態更改為FIN_WAIT_2;3. 接收方關閉應用程式程序,更改狀態為LAST_ACK,並向對方發出一個TCP的FIN段;4. 發起方接到FIN後狀態更改為TIME_WAIT,併發出這個FIN的ACK確認。ACK傳送成功後(2MSL內)雙方TCP狀態變為CLOSED。 
三Time_Wait狀態解釋 
根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態(TCP實現必須可靠地終止連線的兩個方向(全雙工關閉)),持續2*MSL(Max Segment Lifetime),預設為240秒.TIME_WAIT的等待時間為2MSL,即最大段生存時間.如果 TIME_WAIT 狀態保持時間不足夠長(比如小於2MSL),第一個連線就正常終止了。第二個擁有相同相關五元組的連接出現(因為連線終止前發起的一方可能需要重發 ACK,所以停留在該狀態的時間必須為MSL的2倍。),而第一個連線的重複報文到達,干擾了第二個連線。TCP實現必須防止某個連線的重複報文在連線終 止後出現,所以讓TIME_WAIT態保持時間足夠長(2MSL),連線相應方向上的TCP報文要麼完全響應完畢,要麼被丟棄。建立第二個連線的時候,不 會混淆。為什麼TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間也就是TCP/IP設計者本來是這麼設計的主要有兩個原因1。防止上一次連線中的包,迷路後重新出現,影響新連線(經過2MSL,上一次連線中所有的重複包都會消失)2。可靠的關閉TCP連線在主動關閉方傳送的最後一個 ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。修改Time_Wait引數的方法 Windows下在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters,新增名為TcpTimedWaitDelay的DWORD鍵,設定為60,以縮短TIME_WAIT的等待時間 Linux下修改:vi /etc/sysctl.conf編輯檔案,加入以下內容:net.ipv4.tcp_syncookies = 1net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_tw_recycle = 1net.ipv4.tcp_fin_timeout = 30 然後執行 /sbin/sysctl -p 讓引數生效。 net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉;net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉;net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。net.ipv4.tcp_fin_timeout 修改系統預設的 TIMEOUT 時間 
Time_Wait引發的問題:Timewait是正常的現象,但是比較多的時候(比如3000)則可能引發CPU利用率高需要有效降低 
四   Close_Wait狀態解釋 
CLOSE_WAIT狀態的生成原因通過TCP的狀態圖我們可以看出只有被動關閉的一端才有CLOSE_WAIT狀態,當收到Fin併發送了Ack後伺服器狀態就變成了CLOSE_WAIT狀態,如果我們的伺服器一直處於CLOSE_WAIT狀態的話,說明套接字是被動關閉的!,並且沒有傳送Fin信令,原因往往是沒有呼叫TCP的CloseSocket。 
解決CLOSE_WAIT的方法:1 一般原因都是TCP連線沒有呼叫關閉方法。需要應用來處理網路連結關閉。2 對於Web請求出現這個原因,經常是因為Response的BodyStream沒有呼叫Close.比如Widnows下:使用HttpWebRequest 一定要保證GetRequestStream和GetResponse物件關閉,否則容易造成連線處於CLOSE_WAIT狀態3 TCP的KeepLive功能,可以讓作業系統替我們自動清理掉CLOSE_WAIT的連線。但是KeepLive在Windows作業系統下預設是7200秒,也就是2個小時才清理一次。往往滿足不了要求。可以調小該數值。Windows下的調整方法為HKEY_LOCAL_MACHINE\CurrentControlSet\Services\Tcpip\Parameters下的以下三個引數: KeepAliveInterval,設定其值為1000 KeepAliveTime,設定其值為300000(單位為毫秒,300000代表5分鐘) TcpMaxDataRetransmissions,設定其值為5 
Close_Wait引發的問題:Close_Wait會佔用一個連線,網路可用連線小。數量過多,可能會引起網路效能下降,並佔用系統非換頁記憶體。 尤其是在有連線池的情況下(比如HttpRequest)會耗盡連線池的網路連線數,導致無法建立網路連線五   TCP的KeepAlive 
在一個正常的TCP連線上,當我們用無限等待的方式呼叫下面的recv或send的時候: 
    ret=recv(s,&buf[idx],nLeft,flags);或 
    ret=send(s,&buf[idx],nLeft,flags); 

如果TCP連線被對方正常關閉,也就是說,對方是正確地呼叫了closesocket(s)或者shutdown(s)的話,那麼上面的recv或send呼叫就能馬上返回,並且報錯。這是由於closesocket()或者shutdown()有個正常的關閉過程,會告訴對方“TCP連線已經關閉,你不需要再發送或者接受訊息了”。但是,如果是網線突然被拔掉,TCP連線的任何一端的機器突然斷電或重啟動,那麼這時候正在執行recv或send操作的一方就會因為沒有任何連線中斷的通知而一直等待下去,也就是會被長時間卡住。這種情形解決的辦法是啟動TCP程式設計裡的keepAlive機制。 
    #include “mstcpip.h” 
    tcp_keepalive inKeepAlive = {0};    unsigned long ulInLen = sizeof(tcp_keepalive);    tcp_keepalive utKeepAlive = {0};    unsigned long ulOutLen = sizeof(tcp_keepalive);    unsigned long ulBytesReturn = 0; 
    int ret; 
    inKeepAlive.onoff=1;    inKeepAlive.keepaliveinterval=5000; //單位為毫秒    inKeepAlive.keepalivetime=1000;     //單位為毫秒 
    ret=WSAIoctl(s, SIO_KEEPALIVE_VALS, (LPVOID)&inKeepAlive, ulInLen,  (LPVOID)&outKeepAlive, ulOutLen, &ulBytesReturn, NULL, NULL); 

    此處的keepalivetime表示的是TCP連線處於暢通時候的探測頻率,一旦探測包沒有返回,就以keepaliveinterval的頻率傳送,經過若干次的重試,如果探測包都沒有返回,那麼就得出結論:TCP連線已經斷開,於是上面的recv或send呼叫也就能馬上返回,不會無限制地卡住了。 

圖 1 Wireshark Keep-Alive抓包 

上圖是對上面文字的說明。被選中的包(第32行)之前,TCP處於暢通狀態,KeepAlive是以1000毫秒(keepalivetime的值)的頻率傳送探測包,在傳送到第32個探測包的時候,探測包沒有返回,於是就以5000毫秒(keepalivetime的值)的頻率傳送探測包,重發幾次後,探測包都沒有返回, 於是就得出結論:此TCP連線已經斷開了! 

對於Win2K/XP/2003,可以從下面的登錄檔項找到影響整個系統所有連線的keepalive引數: 

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters] 
“KeepAliveTime”=dword:006ddd00“KeepAliveInterval”=dword:000003e8“MaxDataRetries”=”5″ 

對於實用程式來說,2小時的空閒時間太長。因此,我們需要手工開啟Keepalive功能並設定合理的Keepalive引數。在XP和WIN2003系統上,可以針對單獨的socket來設定,但是在windows 2000,不能單獨設定,如果設定,那麼影響是整個系統的所有socket。 

REF: http://blog.zhuzhaoyuan.com/2009/03/a-word-on-time_wait-and-close_wait/ (這個講解清楚)http://kerry.blog.51cto.com/172631/105233http://www.php-oa.com/2008/04/25/apachedekeepalivehetcpipdetime_wait.htmlhttp://davidhew.blogbus.com/logs/48967567.htmlh