close_wait状态和time_wait状态 (TCP链接)

不久前,个人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发送FINServerClient就置为LAST_ACK态。

        Server ---> ACK ---> Client

Server回应了ACK,那么Client的套接字才会真正置为CLOSED状态。

image

 

咱们的程序处于CLOSE_WAIT状态,而不是LAST_ACK,说明尚未发FINServer,那么多是在关闭链接以前还有许多数据要发送或者其余事要作,致使没有发这个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套接字选项的是1SO_LINGER选项是,linger{l_onoff0l_linger0}

若是在发送数据的过程当中(send()没有完成,还有数据没发送)而调用了closesocket(),之前咱们通常采起的措施是“从容关闭”:

由于在退出服务或者每次从新创建socket以前,我都会先调用

/// 先将双向的通信关闭

     shutdown(sockConnected, SD_BOTH);

     /// 安全起见,每次创建Socket链接前,先把这个旧链接关闭

closesocket(sockConnected);

 

咱们此次要这么作:

设置SO_LINGER为零(亦即linger结构中的l_onoff域设为非零,但l_linger0,便不用担忧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链接的状态转换图:



image

< 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. 不过,根本问题仍是程序写的很差,有待提升。


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要上涨不要降低。