不久前,個人Socket Client程序遇到了一個很是尷尬的錯誤。它原本應該在一個socket長鏈接上持續不斷地向服務器發送數據,若是socket鏈接斷開,那麼程序會自動不斷地重試創建鏈接。
html
有一天發現程序在不斷嘗試創建鏈接,可是老是失敗。用netstat查看,這個程序居然有上千個socket鏈接處於CLOSE_WAIT狀態,以致於達到了上限,因此沒法創建新的socket鏈接了。java
爲何會這樣呢?算法
它們爲何會都處在CLOSE_WAIT狀態呢?編程
CLOSE_WAIT狀態的生成緣由後端
首先咱們知道,若是咱們的Client程序處於CLOSE_WAIT狀態的話,說明套接字是被動關閉的!api
由於若是是Server端主動斷掉當前鏈接的話,那麼雙方關閉這個TCP鏈接共須要四個packet:安全
Server ---> FIN ---> Client服務器
Server <--- ACK <--- Client網絡
這時候Server端處於FIN_WAIT_2狀態;而咱們的程序處於CLOSE_WAIT狀態。app
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包呢,難道會在關閉己方鏈接前有那麼多事情要作嗎?
還有一個問題,爲何有數千個鏈接都處於這個狀態呢?難道那段時間內,服務器端老是主動拆除咱們的鏈接嗎?
無論怎麼樣,咱們必須防止相似狀況再度發生!
首先,咱們要防止不斷開闢新的端口,這能夠經過設置SO_REUSEADDR套接字選項作到:
重用本地地址和端口
之前我老是一個端口不行,就換一個新的使用,因此致使讓數千個端口進入CLOSE_WAIT狀態。若是下次還發生這種尷尬情況,我但願加一個限定,只是當前這個端口處於CLOSE_WAIT狀態!
在調用
sockConnected = socket(AF_INET, SOCK_STREAM, 0);
以後,咱們要設置該套接字的選項來重用:
/// 容許重用本地地址和端口: /// 這樣的好處是,即便socket斷了,調用前面的socket函數也不會佔用另外一個,而是始終就是一個端口 /// 這樣防止socket始終鏈接不上,那麼按照原來的作法,會不斷地換端口。 int nREUSEADDR = 1; setsockopt(sockConnected, SOL_SOCKET, SO_REUSEADDR, (const char*)&nREUSEADDR, 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以前,我都會先調用
/// 先將雙向的通信關閉
shutdown(sockConnected, SD_BOTH);
/// 安全起見,每次創建Socket鏈接前,先把這個舊鏈接關閉
closesocket(sockConnected);
咱們此次要這麼作:
設置SO_LINGER爲零(亦即linger結構中的l_onoff域設爲非零,但l_linger爲0),便不用擔憂closesocket調用進入「鎖定」狀態(等待完成),不管是否有排隊數據未發送或未被確認。這種關閉方式稱爲「強行關閉」,由於套接字的虛電路當即被複位,還沒有發出的全部數據都會丟失。在遠端的recv()調用都會失敗,並返回WSAECONNRESET錯誤。
在connect成功創建鏈接以後設置該選項:
linger m_sLinger; m_sLinger.l_onoff = 1; // (在closesocket()調用,可是還有數據沒發送完畢的時候允許逗留) m_sLinger.l_linger = 0; // (允許逗留的時間爲0秒) setsockopt(sockConnected, SOL_SOCKET, SO_LINGER, (const char*)&m_sLinger, sizeof(linger)); |
總結
也許咱們避免不了CLOSE_WAIT狀態凍結的再次出現,但咱們會使影響降到最小,但願那個重用套接字選項可以使得下一次從新創建鏈接時能夠把CLOSE_WAIT狀態踢掉。
Feedback
# 回覆:[Socket]尷尬的CLOSE_WAIT狀態以及應對策略 2005-01-30 3:41 PM yun.zheng
回覆人: elssann(臭屁蟲和他的開心果) ( ) 信譽:51 2005-01-30 14:00:00 得分: 0
個人意思是:當一方關閉鏈接後,另一方沒有檢測到,就致使了CLOSE_WAIT的出現,上次個人一個朋友也是這樣,他寫了一個客戶端和 APACHE鏈接,當APACHE把鏈接斷掉後,他沒檢測到,出現了CLOSE_WAIT,後來我叫他檢測了這個地方,他添加了調用 closesocket的代碼後,這個問題就消除了。
若是你在關閉鏈接前仍是出現CLOSE_WAIT,建議你取消shutdown的調用,直接兩邊closesocket試試。
另一個問題:
好比這樣的一個例子:
當客戶端登陸上服務器後,發送身份驗證的請求,服務器收到了數據,對客戶端身份進行驗證,發現密碼錯誤,這時候服務器的通常作法應該是先發送一個密碼錯誤的信息給客戶端,而後把鏈接斷掉。
若是把
m_sLinger.l_onoff = 1;
m_sLinger.l_linger = 0;
這樣設置後,不少狀況下,客戶端根本就收不到密碼錯誤的消息,鏈接就被斷了。
# 回覆:[Socket]尷尬的CLOSE_WAIT狀態以及應對策略 2005-01-30 3:41 PM yun.zheng
elssann(臭屁蟲和他的開心果) ( ) 信譽:51 2005-01-30 13:24:00 得分: 0
出現CLOSE_WAIT的緣由很簡單,就是某一方在網絡鏈接斷開後,沒有檢測到這個錯誤,沒有執行closesocket,致使了這個狀態的實現,這在TCP/IP協議的狀態變遷圖上能夠清楚看到。同時和這個相對應的還有一種叫TIME_WAIT的。
另外,把SOCKET的SO_LINGER設置爲0秒拖延(也就是當即關閉)在不少時候是有害處的。
還有,把端口設置爲可複用是一種不安全的網絡編程方法。
# 回覆:[Socket]尷尬的CLOSE_WAIT狀態以及應對策略 2005-01-30 3:42 PM yun.zheng
elssann(臭屁蟲和他的開心果) ( ) 信譽:51 2005-01-30 14:48:00 得分: 0
能不能解釋請看這裏
http://blog.csdn.net/cqq/archive/2005/01/26/269160.aspx
再看這個圖:
http://tech.ccidnet.com/pub/attachment/2004/8/322252.png
斷開鏈接的時候,
當發起主動關閉的左邊這方發送一個FIN過去後,右邊被動關閉的這方要回應一個ACK,這個ACK是TCP迴應的,而不 是應用程序發送的,此時,被動關閉的一方就處於CLOSE_WAIT狀態了。若是此時被動關閉的這一方再也不繼續調用closesocket,那麼他就不會 發送接下來的FIN,致使本身總是處於CLOSE_WAIT。只有被動關閉的這一方調用了closesocket,纔會發送一個FIN給主動關閉的這一 方,同時也使得本身的狀態變遷爲LAST_ACK。
# 回覆:[Socket]尷尬的CLOSE_WAIT狀態以及應對策略 2005-01-30 3:54 PM yun.zheng
elssann(臭屁蟲和他的開心果) ( ) 信譽:51 2005-01-30 15:39:00 得分: 0
好比被動關閉的是客戶端。。。
當對方調用closesocket的時候,你的程序正在
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.
# 回覆:[Socket]尷尬的CLOSE_WAIT狀態以及應對策略 2005-01-30 4:17 PM yun.zheng
int nRecvBufLength =
recv(sockConnected,
szRecvBuffer,
sizeof(szRecvBuffer),
0);
/// zhengyun 20050130:
/// elssann舉例說,當對方調用closesocket的時候,個人程序正在
/// recv,這時候有可能對方發送的FIN包我沒有收到,而是由TCP代回了
/// 一個ACK包,因此我這邊程序進入CLOSE_WAIT狀態。
/// 因此他建議在這裏判斷是否已出錯,是就主動closesocket。
/// 由於前面咱們已經設置了recv超時時間爲30秒,那麼若是真的是超時了,
/// 這裏收到的錯誤應該是WSAETIMEDOUT,這種狀況下也能夠關閉鏈接的
if (nRecvBufLength == SOCKET_ERROR)
{
TRACE_INFO(_T("=用recv接收發生Socket錯誤="));
closesocket(sockConnected);
continue;
}
這樣能夠嗎?
關鍵字:TCP ,CLOSE_WAIT, Java, SocketChannel
問題描述:最 近性能測試碰到的一個問題。客戶端使用NIO,服務器仍是通常的Socket鏈接。當測試進行一段時間之後,發現服務器端的系統出現大量未釋放的網絡連 接。用netstat -na查看,鏈接狀態爲CLOSE_WAIT。這就奇怪了,爲何Socket已經關閉而鏈接依然未釋放。
解決:Google了半天,發現關於CLOSE_WAIT的問題通常是C的,Java彷佛碰到這個問題的很少(這有一篇不錯的,也是解決CLOSE_WAIT的,可是好像沒有根本解決,而是選擇了一個折中的辦法)。接着找,因爲使用了NIO,因此懷疑多是這方面的問題,結果找到了這篇。順着帖子翻下去,其中有幾我的說到了一個問題—— 一端的Socket調用close後,另外一端的Socket沒有調用close.因而查了一下代碼,果真發現Server端在某些異常狀況時,沒有關閉Socket。改正後問題解決。
時間基本上花在Google上了,不過也學到很多東西。下面爲一張TCP鏈接的狀態轉換圖:
< id="slideShowMovie" type="application/x-shockwave-flash" width="500" height="500" src="http://www.yupoo.com/images/slideshow.swf?api_key=4a0dfd625c8ad19b1e2105ff44dc962b&album_id=-1&username=lionzl&minH=350&minW=762" tplayername="SWF" splayername="SWF" mediawrapchecked="mediawrapchecked" quality="high" bgcolor="#000000" name="slideShowMovie" pluginspage="http://www.macromedia.com/go/getflashplayer">
說明:虛線和實線分別對應服務器端(被鏈接端)和客戶端端(主動鏈接端)。
結合上圖使用netstat -na命令便可知道到當前的TCP鏈接狀態。通常LISTEN、ESTABLISHED、TIME_WAIT是比較常見。
分析:
上面我碰到的這個問題主要由於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. 不過,根本問題仍是程序寫的很差,有待提升。
根據TCP協議,主動發起關閉的一方,會進入TIME_WAIT狀態,持續2*MSL(Max Segment Lifetime),缺省爲240秒,在這個post中簡潔的介紹了爲何須要這個狀態。
值得一說的是,對於基於TCP的HTTP協議,關閉TCP鏈接的是Server端,這樣,Server端會進入TIME_WAIT狀態,可 想而知,對於訪問量大的Web Server,會存在大量的TIME_WAIT狀態,假如server一秒鐘接收1000個請求,那麼就會積壓240*1000=240,000個 TIME_WAIT的記錄,維護這些狀態給Server帶來負擔。固然現代操做系統都會用快速的查找算法來管理這些TIME_WAIT,因此對於新的 TCP鏈接請求,判斷是否hit中一個TIME_WAIT不會太費時間,可是有這麼多狀態要維護老是很差。
HTTP協議1.1版規定default行爲是Keep-Alive,也就是會重用TCP鏈接傳輸多個 request/response,一個主要緣由就是發現了這個問題。還有一個方法減緩TIME_WAIT壓力就是把系統的2*MSL時間減小,由於 240秒的時間實在是忒長了點,對於Windows,修改註冊表,在HKEY_LOCAL_MACHINE\ SYSTEM\CurrentControlSet\Services\ Tcpip\Parameters上添加一個DWORD類型的值TcpTimedWaitDelay,通常認爲不要少於60,否則可能會有麻煩。
對於大型的服務,一臺server搞不定,須要一個LB(Load Balancer)把流量分配到若干後端服務器上,若是這個LB是以NAT方式工做的話,可能會帶來問題。假如全部從LB到後端Server的IP包的 source address都是同樣的(LB的對內地址),那麼LB到後端Server的TCP鏈接會受限制,由於頻繁的TCP鏈接創建和關閉,會在server上留 下TIME_WAIT狀態,並且這些狀態對應的remote address都是LB的,LB的source port撐死也就60000多個(2^16=65536,1~1023是保留端口,還有一些其餘端口缺省也不會用),每一個LB上的端口一旦進入 Server的TIME_WAIT黑名單,就有240秒不能再用來創建和Server的鏈接,這樣LB和Server最多也就能支持300個左右的鏈接。 若是沒有LB,不會有這個問題,由於這樣server看到的remote address是internet上廣闊無垠的集合,對每一個address,60000多個port實在是夠用了。
一開始我以爲用上LB會很大程度上限制TCP的鏈接數,可是實驗代表沒這回事,LB後面的一臺Windows Server 2003每秒處理請求數照樣達到了600個,難道TIME_WAIT狀態沒起做用?用Net Monitor和netstat觀察後發現,Server和LB的XXXX端口之間的鏈接進入TIME_WAIT狀態後,再來一個LB的XXXX端口的 SYN包,Server照樣接收處理了,而是想像的那樣被drop掉了。翻書,從書堆裏面找出覆滿塵土的大學時代買的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中間提到一句,對於BSD-derived實現,只要SYN的sequence number比上一次關閉時的最大sequence number還要大,那麼TIME_WAIT狀態同樣接受這個SYN,難不成Windows也算BSD-derived?有了這點線索和關鍵字 (BSD),找到這個post,在NT4.0的時候,仍是和BSD-derived不同的,不過Windows Server 2003已是NT5.2了,也許有點差異了。
作個試驗,用Socket API編一個Client端,每次都Bind到本地一個端口好比2345,重複的創建TCP鏈接往一個Server發送Keep-Alive=false 的HTTP請求,Windows的實現讓sequence number不斷的增加,因此雖然Server對於Client的2345端口鏈接保持TIME_WAIT狀態,可是老是可以接受新的請求,不會拒絕。那 若是SYN的Sequence Number變小會怎麼樣呢?一樣用Socket API,不過此次用Raw IP,發送一個小sequence number的SYN包過去,Net Monitor裏面看到,這個SYN被Server接收後如泥牛如海,一點反應沒有,被drop掉了。
按照書上的說法,BSD-derived和Windows Server 2003的作法有安全隱患,不過至少這樣至少不會出現TIME_WAIT阻止TCP請求的問題,固然,客戶端要配合,保證不一樣TCP鏈接的sequence number要上漲不要降低。