1. 程式人生 > >CLOSE_WAIT狀態的原因與解決方法

CLOSE_WAIT狀態的原因與解決方法

max lis echo 轉移 sin art option iad 默認

這個問題之前沒有怎麽留意過,是最近在面試過程中遇到的一個問題,面了兩家公司,兩家公司竟然都面到到了這個問題,不得不使我開始關註這個問題。說起CLOSE_WAIT狀態,如果不知道的話,還是先瞧一下TCP的狀態轉移圖吧。
技術分享圖片 關閉socket分為主動關閉(Active closure)和被動關閉(Passive closure)兩種情況。前者是指有本地主機主動發起的關閉;而後者則是指本地主機檢測到遠程主機發起關閉之後,作出回應,從而關閉整個連接。將關閉部分的狀態轉移摘出來,就得到了下圖:
技術分享圖片
產生原因
通過圖上,我們來分析,什麽情況下,連接處於CLOSE_WAIT狀態呢?
在被動關閉連接情況下,在已經接收到FIN,但是還沒有發送自己的FIN的時刻,連接處於CLOSE_WAIT狀態。
通常來講,CLOSE_WAIT狀態的持續時間應該很短,正如SYN_RCVD狀態。但是在一些特殊情況下,就會出現連接長時間處於CLOSE_WAIT狀態的情況。

出現大量close_wait的現象,主要原因是某種情況下對方關閉了socket鏈接,但是我方忙與讀或者寫,沒有關閉連接。代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno,如果不是AGAIN,就斷開連接。

參考資料4中描述,通過發送SYN-FIN報文來達到產生CLOSE_WAIT狀態連接,沒有進行具體實驗。不過個人認為協議棧會丟棄這種非法報文,感興趣的同學可以測試一下,然後把結果告訴我;-)

為了更加清楚的說明這個問題,我們寫一個測試程序,註意這個測試程序是有缺陷的。
只要我們構造一種情況,使得對方關閉了socket,我們還在read,或者是直接不關閉socket就會構造這樣的情況。
server.c:
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);

bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

listen(listenfd, 20);

printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd,
(struct sockaddr *)&cliaddr, &cliaddr_len);
//while (1)
{
n = read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));

for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
}
//這裏故意不關閉socket,或者是在close之前加上一個sleep都可以
//sleep(5);
//close(connfd);
}
}
client.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXLINE 80
#define SERV_PORT 8000

int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;

if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];

sockfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);

connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

write(sockfd, str, strlen(str));

n = read(sockfd, buf, MAXLINE);
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n);
write(STDOUT_FILENO, "\n", 1);

close(sockfd);
return 0;
}

結果如下:
debian-wangyao:~$ ./client a
Response from server:
A
debian-wangyao:~$ ./client b
Response from server:
B
debian-wangyao:~$ ./client c
Response from server:
C
debian-wangyao:~$ netstat -antp | grep CLOSE_WAIT
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 1 0 127.0.0.1:8000 127.0.0.1:58309 CLOSE_WAIT 6979/server
tcp 1 0 127.0.0.1:8000 127.0.0.1:58308 CLOSE_WAIT 6979/server
tcp 1 0 127.0.0.1:8000 127.0.0.1:58307 CLOSE_WAIT 6979/server


解決方法
基本的思想就是要檢測出對方已經關閉的socket,然後關閉它。

1.代碼需要判斷socket,一旦read返回0,斷開連接,read返回負,檢查一下errno,如果不是AGAIN,也斷開連接。(註:在UNP 7.5節的圖7.6中,可以看到使用select能夠檢測出對方發送了FIN,再根據這條規則就可以處理CLOSE_WAIT的連接)
2.給每一個socket設置一個時間戳last_update,每接收或者是發送成功數據,就用當前時間更新這個時間戳。定期檢查所有的時間戳,如果時間戳與當前時間差值超過一定的閾值,就關閉這個socket。
3.使用一個Heart-Beat線程,定期向socket發送指定格式的心跳數據包,如果接收到對方的RST報文,說明對方已經關閉了socket,那麽我們也關閉這個socket。
4.設置SO_KEEPALIVE選項,並修改內核參數

前提是啟用socket的KEEPALIVE機制:
//啟用socket連接的KEEPALIVE
int iKeepAlive = 1;
setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&iKeepAlive, sizeof(iKeepAlive));

tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
The number of seconds between TCP keep-alive probes.

tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
The maximum number of TCP keep-alive probes to send before giving up and killing the connection if no response is obtained from the other end.

tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes. Keep-alives are only sent when the SO_KEEPALIVE socket option is enabled. The default value is 7200 seconds (2 hours). An idle connec‐tion is terminated after approximately an additional 11 minutes (9 probes an interval of 75 seconds apart) when keep-alive is enabled.

echo 120 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 2 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 1 > /proc/sys/net/ipv4/tcp_keepalive_probes

除了修改內核參數外,可以使用setsockopt修改socket參數,參考man 7 socket。
int KeepAliveProbes=1;
int KeepAliveIntvl=2;
int KeepAliveTime=120;
setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, (void *)&KeepAliveProbes, sizeof(KeepAliveProbes));
setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, (void *)&KeepAliveTime, sizeof(KeepAliveTime));
setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, (void *)&KeepAliveIntvl, sizeof(KeepAliveIntvl));


參考:
http://blog.chinaunix.net/u/20146/showart_1217433.html
http://blog.csdn.net/eroswang/archive/2008/03/10/2162986.aspx
http://haka.sharera.com/blog/BlogTopic/32309.htm
http://learn.akae.cn/media/ch37s02.html
http://faq.csdn.net/read/208036.html
http://www.cndw.com/tech/server/2006040430203.asp
http://davidripple.bokee.com/1741575.html
http://doserver.net/post/keepalive-linux-1.php
man 7 tcp 不久前,我的Socket Client程序遇到了一個非常尷尬的錯誤。它本來應該在一個socket長連接上持續不斷地向服務器發送數據,如果socket連接斷開,那麽程序會自動不斷地重試建立連接。
有一天發現程序在不斷嘗試建立連接,但是總是失敗。用netstat查看,這個程序竟然有上千個socket連接處於CLOSE_WAIT狀態,以至於達到了上限,所以無法建立新的socket連接了。
為什麽會這樣呢?
它們為什麽會都處在CLOSE_WAIT狀態呢?
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包呢,難道會在關閉己方連接前有那麽多事情要做嗎?
還有一個問題,為什麽有數千個連接都處於這個狀態呢?難道那段時間內,服務器端總是主動拆除我們的連接嗎?

不管怎麽樣,我們必須防止類似情況再度發生!
首先,我們要防止不斷開辟新的端口,這可以通過設置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;
}
這樣可以嗎?
網絡連接無法釋放—— CLOSE_WAIT
關鍵字: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連接的狀態轉換圖:
技術分享圖片
說明:虛線和實線分別對應服務器端(被連接端)和客戶端端(主動連接端)。
結合上圖使用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. 不過,根本問題還是程序寫的不好,有待提高。

TIME_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要上漲不要下降。

TCP 狀態變化


技術分享圖片

關閉socket分為主動關閉(Active closure)和被動關閉(Passive closure)兩種情況。前者是指有本地主機主動發起的關閉;而後者則是指本地主機檢測到遠程主機發起關閉之後,作出回應,從而關閉整個連接。將關閉部分的狀態轉移摘出來,就得到了下圖:

技術分享圖片

發生原因

通過圖上,我們來分析,什麽情況下,連接處於CLOSE_WAIT狀態呢?

在被動關閉連接情況下,在已經接收到FIN,但是還沒有發送自己的FIN的時刻,連接處於CLOSE_WAIT狀態。

通常來講,CLOSE_WAIT狀態的持續時間應該很短,正如SYN_RCVD狀態。但是在一些特殊情況下,就會出現連接長時間處於CLOSE_WAIT狀態的情況。

出現大量close_wait的現象,主要原因是某種情況下對方關閉了socket鏈接,但是我方忙與讀或者寫,沒有關閉連接。代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno,如果不是AGAIN,就斷開連接。

more:

起初每個socket都是CLOSED狀態,當客戶端初使化一個連接,他發送一個SYN包到服務器,客戶端進入SYN_SENT狀態。服務器接收到SYN包,反饋一個SYN-ACK包,客戶端接收後返饋一個ACK包客戶端變成ESTABLISHED狀態,如果長時間沒收到SYN-ACK包,客戶端超時進入CLOSED狀態。

當服務器綁定並監聽某一端口時,socket的狀態是LISTEN,當客戶企圖建立連接時,服務器收到一個SYN包,並反饋SYN-ACK包。服務器狀態變成SYN_RCVD,當客戶端發送一個ACK包時,服務器socket變成ESTABLISHED狀態。

當一個程序在ESTABLISHED狀態時有兩種圖徑關閉它,第一是主動關閉,第二是被動關閉。如果你要主動關閉的話,發送一個FIN包。當你的程序closesocket或者shutdown(標記),你的程序發送一個FIN包到peer,你的socket變成FIN_WAIT_1狀態。peer反饋一個ACK包,你的socket進入FIN_WAIT_2狀態。如果peer也在關閉連接,那麽它將發送一個FIN包到你的電腦,你反饋一個ACK包,並轉成TIME_WAIT狀態。TIME_WAIT狀態又號2MSL等待狀態。MSL意思是最大段生命周期(Maximum+Segment+Lifetime)表明一個包存在於網絡上到被丟棄之間的時間。每個IP包有一個TTL(time_to_live),當它減到0時則包被丟棄。每個路由器使TTL減一並且傳送該包。當一個程序進入TIME_WAIT狀態時,他有2個MSL的時間,這個充許TCP重發最後的ACK,萬一最後的ACK丟失了,使得FIN被重新傳輸。在2MSL等待狀態完成後,socket進入CLOSED狀態。

被動關閉:當程序收到一個FIN包從peer,並反饋一個ACK包,於是程序的socket轉入CLOSE_WAIT狀態。因為peer已經關閉了,所以不能發任何消息了。但程序還可以。要關閉連接,程序自已發送給自已FIN,使程序的TCP socket狀態變成LAST_ACK狀態,當程序從peer收到ACK包時,程序進入CLOSED狀態。

CLOSE_WAIT狀態的原因與解決方法