close_wait狀態的產生原因及解決(轉)


最近測試環境server由於需要與大量的后台server交互,今天突然發現有大量的close_wait產生,於是仔細研究了一下: 
如果我們的服務器程序處於CLOSE_WAIT狀態的話,說明套接字是被動關閉的! 
因為如果是CLIENT端主動斷掉當前連接的話,那么雙方關閉這個TCP連接共需要四個packet: 

1.Client -> FIN  -> Server   
2.Client <- ACK  <- Server   這時候Client端處於FIN_WAIT_2狀態;而Server 程序處於CLOSE_WAIT狀態。   
3.Client <- FIN  <- Server   這時Server 發送FIN給Client,Server 就置為LAST_ACK狀態。   
4.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_*系列參數有助於解決這個問題。 
但是實際上,還是主要是因為我們的程序代碼有問題,通常是如下問題: 
比如被動關閉的是客戶端。。。 

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

C代碼 
int nRet = recv(s,....); 
if (nRet == SOCKET_ERROR) 

// closesocket(s); 
 return FALSE; 
}    

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

我的理解,當主動關閉的一方發送FIN到被動關閉這邊后,被動關閉這邊的 TCP馬上回應一個ACK過去,同時向上面應用程序提交一個ERROR, 

導致上面的SOCKET的send或者recv返回SOCKET_ERROR,正常情況下,如果上面在返回SOCKET_ERROR后調用了 closesocket,那么被動關閉的者一方的TCP就會發送一個FIN過去,自己的狀態就變遷到LAST_ACK.

close_wait

TCP狀態變遷

 

轉自:https://my.oschina.net/gehui/blog/494898

 

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

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

       Server  --->  FIN  --->  Client 

       Server  <---  ACK  <---  Client 

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

       Server  <---  FIN  <---  Client 

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

        Server  --->  ACK  --->  Client 

Server回應了ACK,那么Client的套接字才會真正置為CLOSED狀態。 



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



原因知道了,那么為什么不發FIN包呢,難道會在關閉己方連接前有那么多事情要做嗎? 

elssann舉例說,當對方調用closesocket的時候,我的程序正在調用recv中,這時候有可能對方發送的FIN包我沒有收到,而是由TCP代回了一個ACK包,所以我這邊套接字進入CLOSE_WAIT狀態。 

所以他建議在這里判斷recv函數的返回值是否已出錯,是的話就主動closesocket,這樣防止沒有接收到FIN包。 

因為前面我們已經設置了recv超時時間為30秒,那么如果真的是超時了,這里收到的錯誤應該是WSAETIMEDOUT,這種情況下也可以主動關閉連接的。 



還有一個問題,為什么有數千個連接都處於這個狀態呢?難道那段時間內,服務器端總是主動拆除我們的連接嗎? 



不管怎么樣,我們必須防止類似情況再度發生! 

首先,我們要保證原來的端口可以被重用,這可以通過設置SO_REUSEADDR套接字選項做到: 


重用本地地址和端口 
以前我總是一個端口不行,就換一個新的使用,所以導致讓數千個端口進入CLOSE_WAIT狀態。如果下次還發生這種尷尬狀況,我希望加一個限定,只是當前這個端口處於CLOSE_WAIT狀態! 

在調用 

sockConnected = socket(AF_INET, SOCK_STREAM, 0); 

之后,我們要設置該套接字的選項來重用: 

 1 /// 允許重用本地地址和端口: 
 2 
 3 /// 這樣的好處是,即使socket斷了,調用前面的socket函數也不會占用另一個,而是始終就是一個端口 
 4 
 5 /// 這樣防止socket始終連接不上,那么按照原來的做法,會不斷地換端口。 
 6 
 7 int nREUSEADDR = 1; 
 8 
 9 setsockopt(sockConnected, 
10 
11               SOL_SOCKET, 
12 
13               SO_REUSEADDR, 
14 
15               (const char*)&nREUSEADDR, 
16 
17               sizeof(int)); 

 




教科書上是這么說的:這樣,假如服務器關閉或者退出,造成本地地址和端口都處於TIME_WAIT狀態,那么SO_REUSEADDR就顯得非常有用。 

也許我們無法避免被凍結在CLOSE_WAIT狀態永遠不出現,但起碼可以保證不會占用新的端口。 

其次,我們要設置SO_LINGER套接字選項: 

從容關閉還是強行關閉? 
LINGER是“拖延”的意思。 

默認情況下(Win2k),SO_DONTLINGER套接字選項的是1;SO_LINGER選項是,linger為{l_onoff:0,l_linger:0}。 

如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們一般采取的措施是“從容關閉”: 

因為在退出服務或者每次重新建立socket之前,我都會先調用 

1 /// 先將雙向的通訊關閉 
2 
3      shutdown(sockConnected, SD_BOTH); 
4 
5      /// 安全起見,每次建立Socket連接前,先把這個舊連接關閉 
6 
7 closesocket(sockConnected); 

 





我們這次要這么做: 

設置SO_LINGER為零(亦即linger結構中的l_onoff域設為非零,但l_linger為0),便不用擔心closesocket調用進入“鎖定”狀態(等待完成),不論是否有排隊數據未發送或未被確認。這種關閉方式稱為“強行關閉”,因為套接字的虛電路立即被復位,尚未發出的所有數據都會丟失。在遠端的recv()調用都會失敗,並返回WSAECONNRESET錯誤。 

在connect成功建立連接之后設置該選項: 

 1 linger m_sLinger; 
 2 
 3 m_sLinger.l_onoff = 1;  // (在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留) 
 4 
 5 m_sLinger.l_linger = 0; // (容許逗留的時間為0秒) 
 6 
 7 setsockopt(sockConnected, 
 8 
 9          SOL_SOCKET, 
10 
11          SO_LINGER, 
12 
13          (const char*)&m_sLinger, 
14 
15          sizeof(linger)); 

 



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

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

只能通過修改一下TCP/IP的參數,來縮短這個時間:修改tcp_keepalive_*系列參數有助於解決這個問題。tcp_keepalive_time :INTEGER 
默認值是7200(2小時)當keepalive打開的情況下,TCP發送keepalive消息的頻率。(由於目前網絡攻擊等因素,造成了利用這個進行的攻擊很頻繁,曾經也有cu的朋友提到過,說如果2邊建立了連接,然后不發送任何數據或者rst/fin消息,那么持續的時間是不是就是2小時,空連接攻擊? tcp_keepalive_time就是預防此情形的.我個人在做nat服務的時候的修改值為1800秒) 





總結 
也許我們避免不了CLOSE_WAIT狀態凍結的再次出現,但我們會使影響降到最小,希望那個重用套接字選項能夠使得下一次重新建立連接時可以把CLOSE_WAIT狀態踢掉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM