最近遇到的一个关于socket.close的问题,在某个应用服务器出现的情况(执行netstat -np | grep tcp): java
tcp 0 0 10.224.122.16:50158 10.224.112.58:8788 CLOSE_WAITlinux
tcp 0 0 10.224.122.16:37655 10.224.112.58:8788 CLOSE_WAITubuntu
tcp 1 0 127.0.0.1:32713 127.0.0.1:8080 CLOSE_WAITwindows
tcp 38 0 10.224.122.16:34538 10.224.125.42:443 CLOSE_WAIT浏览器
tcp 38 0 10.224.122.16:33394 10.224.125.42:443 CLOSE_WAITtomcat
tcp 1 0 10.224.122.16:18882 10.224.125.10:80 CLOSE_WAIT服务器
tcp 1 0 10.224.122.16:18637 10.224.125.10:80 CLOSE_WAIT网络
tcp 1 0 10.224.122.16:19655 10.224.125.12:80 CLOSE_WAIT架构
........................................并发
总共出现了200个CLOSE_WAIT的socket.并且这些socket长时间得不到释放.下面咱们来看看为何会出现这种大量socket的CLOSE_WAIT状况
首先咱们要搞清楚的是,这个socket是谁发起的,咱们能够看到122.16这台机器开了不少端口,并且端口号都很大,125.12 或者125.10上的端口都是很常见服务器端口,因此122.16上这么多CLOSE_WAIT
的socket是由122.16开启的,换句话说这台机器是传统的客户端,它会主动的请求其余机器的服务端口.
要搞清楚为何会出现CLOSE_WAIT,那么首先咱们必需要清楚CLOSE_WAIT的机制和原理.
假设咱们有一个client, 一个server.
当client主动发起一个socket.close()这个时候对应TCP来讲,会发生什么事情呢?以下图所示.

client首先发送一个FIN信号给server, 这个时候client变成了FIN_WAIT_1的状态, server端收到FIN以后,返回ACK,而后server端的状态变成了CLOSE_WAIT.
接着server端须要发送一个FIN给client,而后server端的状态变成了LAST_ACK,接着client返回一个ACK,而后server端的socket就被成功的关闭了.
从这里能够看到,若是由客户端主动关闭一连接,那么客户端是不会出现CLOSE_WAIT状态的.客户端主动关闭连接,那么Server端将会出现CLOSE_WAIT的状态.
而咱们的服务器上,是客户端socket出现了CLOSE_WAIT,因而可知这个是因为server主动关闭了server上的socket.
那么当server主动发起一个socket.close(),这个时候又发生了一些什么事情呢.

从图中咱们能够看到,若是是server主动关闭连接,那么Client则有可能进入CLOSE_WAIT,若是Client不发送FIN包,那么client就一直会处在CLOSE_WAIT状态(后面咱们能够看到有参数能够调整这个时间).
那么如今咱们要搞清楚的是,在第二中场景中,为何Client不发送FIN包给server.要搞清楚这个问题,咱们首先要搞清楚server是怎么发FIN包给client的,其实server就是调用了
socket.close方法而已,也就是说若是要client发送FIN包,那么client就必须调用socket.close,不然就client就一直会处在CLOSE_WAIT(但事实上不一样操做系统这点的实现还不同,
在ahuaxuan(ahuaxuan.iteye.com)的例子中也出现了这样的case).
下面咱们来作几个实验
实验一:
环境:
服务器端:win7+tomcat,tomcat的keep-alive的时间为默认的15s.
客户端:mac os
实验步骤:服务器启动后,客户端向服务器发送一个get请求,而后客户端阻塞,等待服务器端的socket超时.经过netstat -np tcp能够看到的状况是发送get请求时,服务器和客户端连接是ESTABLISHED, 15s以后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也在咱们的预料之中,而这个时候因为客户端线程阻塞,客户 端socket空置在那里,不作任何操做,2分钟事后,这个连接不论是在win7上,仍是在mac os都看不到了.可见,FIN_WAIT_2或者CLOSE_WAIT有一个timeout.在后面的实验,能够证实,在这个例子中,实际上是 FIN_WAIT_2有一个超时,一旦过了2分钟,那么win7会发一个RST给mac os要求关闭双方的socket.
实验二
服务器端:ubuntu9.10+tomcat,tomcat的keep-alive的时间为默认的15s.
客户端:mac os
实验步骤:服务器启动后,客户端向服务器发送一个get请求,而后客户端阻塞,等待服务器端的socket超时.经过netstat -np tcp(ubuntu使用netstat -np|grep tcp)能够看到的状况是发送get请求时,服务器和客户端连接是ESTABLISHED, 15s以后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也也在咱们的预料之中,而这个时候因为客户端线程阻塞,客 户端socket空置在那里,不作任何操做,1分钟事后,ubuntu上的那个socket不见了,可是mac os上的socket还在,并且仍是CLOSE_WAIT,这说明,FIN_WAIT_2确实有一个超时时间,win7上的超时操做能够关闭mac os上的socket,而ubuntu上的FIN_WAIT_2超时操做却不能关闭mac os上的socket(其状一直是CLOSE_WAIT).
实验三
服务器端:mac os+tomcat,tomcat的keep-alive的时间为默认的15s.
客户端:mac os
实验步骤:服务器启动后,客户端向服务器发送一个get请求,而后客户端阻塞,等待服务器端的socket超时.经过netstat -np tcp能够看到的状况是发送get请求时,服务器和客户端连接是ESTABLISHED, 15s以后,客户端变成了CLOSE_WAIT,而服务器端变成了FIN_WAIT_2.这一点也在咱们的预料之中,而这个时候因为客户端线程阻塞,客户 端socket空置在那里,不作任何操做,4分钟事后,mac os服务器端上的那个socket不见了,可是mac os客户端上的socket还在,并且仍是CLOSE_WAIT,这说明,FIN_WAIT_2确实有一个超时时间,win7上的超时操做能够关闭mac os上的socket,而ubuntu和mac os上的FIN_WAIT_2超时操做却不能关闭mac os上的socket.
总结, 当服务器的内核不同上FIN_WAIT_2的超时时间和操做是不同的.
经查:控制FIN_WAIT_2的参数为:
/proc/sys/net/ipv4/tcp_fin_timeout
如 果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端能够出错并永远不关闭链接,甚至意外当机。缺省值是60秒。2.2 内核的一般值是180秒,你能够按这个设置,但要记住的是,即便你的机器是一个轻载的WEB服务器,也有由于大量的死套接字而内存溢出的风险,FIN- WAIT-2的危险性比FIN-WAIT-1要小,由于它最多只能吃掉1.5K内存,可是它们的生存期长些。参见tcp_max_orphans。
实验四
服务器端:ubuntu9.10+tomcat,tomcat的keep-alive的时间为默认的15s.
客户端:mac os
实验步骤:服务器启动后,客户端向服务器发送一个get请求,而后关闭客户端关闭socket.经过netstat -np tcp能够看到的状况是发送get请求时,服务器和客户端连接是ESTABLISHED, 客户端拿到数据以后,客户端变成了TIME_WAIT,而服务器端变成了已经看不到这个socket了.这一点也也在咱们的预料之中,谁主动关闭连接,那 么谁就须要进入TIME_WAIT状态(除非他的FIN_WAIT_2超时了),大约1分钟以后这个socket在客户端也消失了.
实验证实TIME_WAIT的状态会存在一段时间,并且在这个时间端里,这个FD是不能被回收的.
可是咱们的问题是客户端有不少CLOSE_WAIT,并且咱们的服务器不是windows,而是linux,因此CLOSE_WAIT有没有超时时间呢,确定有,并且默认状况下这个超时时间应该是比较大的.不然不会一会儿看到两百个CLOSE_WAIT的状态.
客户端解决方案:
1.因为socket.close()会致使FIN信号,而client的socket CLOSE_WAIT就是由于该socket该关的时候,咱们没有关,因此咱们须要一个线程池来检查空闲链接中哪些进入了超时状态(idleTIME),但进入超时
的socket未必是CLOSE_WAIT的状态的.不过若是咱们把空闲超时的socket关闭,那么CLOSE_WAIT的状态就会消失.(问 题:像HttpClient这样的工具包中,若是要检查连接池,那么则须要锁定整个池,而这个时候,用户请求获取connection的操做只能等待,在 高并发的时候会形成程序响应速度降低,具体参考IdleConnectionTimeoutThread.java(HttpClient3.1))
2.经查,其实有参数能够调整CLOSE_WAIT的持续时间,若是咱们改变这个时间,那么可让CLOSE_WAIT只保持很短的时间(固然这个参数不仅做用在CLOSE_WAIT上,缩短这个时间可能会带来其余的影响).在客户端机器上修改以下:
sysctl -w net.ipv4.tcp_keepalive_time=60(缺省是2小时,如今改为了60秒)
sysctl -w net.ipv4.tcp_keepalive_probes=2
sysctl -w net.ipv4.tcp_keepalive_intvl=2
咱们将CLOSE_WAIT的检查时间设置为30s,这样一个CLOSE_WAIT只会存在30S.
3. 固然,最重要的是咱们要检查客户端连接的空闲时间,空闲时间能够由客户端自行定义,好比idleTimeout,也可由服务器来决定,服务器只须要每次在 response.header中加入一个头信息,好比说名字叫作timeout头,固然通常状况下咱们会用keep-alive这个头字段, 若是服务器设置了该字段,那么客户端拿到这个属性以后,就知道本身的connection最大的空闲时间,这样不会因为服务器关闭socket,而致使客 户端socket一直close_wait在那里.
服务器端解决方案
4.前面讲到客户端出现CLOSE_WAIT是因为服务器端Socket的读超时,也是TOMCAT中的keep-alive参数.那么若是咱们把这个超时时间设置的长点,会有什么影响?
若是咱们的tomcat既服务于浏览器,又服务于其余的 APP,并且咱们把connection的keep-alive时间设置为10分钟,那么带来的后果是浏览器打开一个页面,而后这个页面一直不关闭,那么 服务器上的socket也不能关闭,它所占用的FD也不能服务于其余请求.若是并发一高,很快服务器的资源将会被耗尽.新的请求再也进不来. 那么若是把keep-alive的时间设置的短一点呢,好比15s? 那么其余的APP来访问这个服务器的时候,一旦这个socket, 15s以内没有新的请求,那么客户端APP的socket将出现大量的CLOSE_WAIT状态.
因此若是出现这种状况,建议将你的server分开部署,服务于browser的部署到单独的JVM实例上,保持keep-alive为15s,而服务于架构中其余应用的功能部署到另外的JVM实例中,而且将keep-alive的时间设置的更
长,好比说1个小时.这样客户端APP创建的connection,若是在一个小时以内都没有重用这条connection,那么客户端的 socket才会进入CLOSE_WAIT的状态.针对不一样的应用场景来设置不一样的keep-alive时间,能够帮助咱们提升程序的性能.
5.若是咱们的应用既服务于浏览器,又服务于其余的APP,那么咱们还有一个终极解决方案.
那就是配置多个connector, 以下:
<!-- for browser -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- for other APP -->
<Connector port="8081" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" keepAliveTimeout="330000" />
访问的时候,浏览器使用8080端口,其余的APP使用8081端口.这样能够保证浏览器请求的socket在15s以内若是没有再次使用,那么 tomcat会主动关闭该socket,而其余APP请求的socket在330s以内没有使用,才关闭该socket,这样作能够大大减小其余APP上 出现CLOSE_WAIT的概率.
你必定会问,若是我不设置keepAliveTimeout又怎么样呢,反正客户端有idleTimeout,客户端的close_wait不会持 续太长时间,请注意看上图中标红的地方,一个是close_wait,还有一个是time_wait状态,也就是说谁主动发起请求,那么它将会最终进入 time_wait状态,听说windows上这个time_wait将持续4分钟,我在linux上的测试代表,linux上它大概是60s左右,也就 是说高并发下,也就是服务器也须要过60s左右才能真正的释放这个FD.因此咱们若是提供http服务给其余APP,那么咱们最好让客户端优先关闭 socket,也就是将客户端的idleTimeout设置的比server的keepalivetimeout小一点.这样保证time_wait出现 在客户端. 而不是资源较为紧张的服务器端.
总结:
本文中ahuaxuan给你们揭示了TCP层client和server端socket关闭的通常流程,而且指出异常状况下client和server端 各自会发生的状况,包含了在不一样平台上出现了的不一样状况, 同时说明了在应用层上咱们能够作什么样的逻辑来保证socket关闭时对server端带来最小的影响.
下面是网上找到的一些资料:
写道
/proc/sys/net/ipv4/tcp_keepalive_time 当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。 /proc/sys/net/ipv4/tcp_keepalive_intvl 当探测没有确认时,从新发送探测的频度。缺省是75秒。 /proc /sys/net/ipv4/tcp_keepalive_probes 在认定链接失效以前,发送多少个TCP的keepalive探测包。缺省值是 9。这个值乘以tcp_keepalive_intvl以后决定了,一个链接发送了keepalive以后能够有多少时间没有回应。/proc/sys/net/ipv4/tcp_max_orphans系 统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。若是超过这个数字,孤儿链接将即刻被复位并打印出警告信息。这个限制仅仅是为了防止简单 的DoS攻击,你绝对不能过度依靠它或者人为地减少这个值,更应该增长这个值(若是增长了内存以后)。This limit exists only to prevent simple DoS attacks, you _must_ not rely on this or lower the limit artificially, but rather increase it (probably, after increasing installed memory), if network conditions require more than default value, and tune network services to linger and kill such states more aggressively. 让我再次提醒你:每一个孤儿套接字最多可以吃掉你64K不可交换的内存。/proc/sys/net/ipv4/tcp_orphan_retries本端试图关闭TCP链接以前重试多少次。缺省值是7,至关于50秒~16分钟(取决于RTO)。若是你的机器是一个重载的WEB服务器,你应该考虑减低这个值,由于这样的套接字会消耗不少重要的资源。参见tcp_max_orphans。/proc/sys/net/ipv4/tcp_max_syn_backlog记 录的那些还没有收到客户端确认信息的链接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128。若是服务器不堪重负,试 试提升这个值。注意!若是你设置这个值大于1024,最好同时调整include/net/tcp.h中的TCP_SYNQ_HSIZE,以保证 TCP_SYNQ_HSIZE*16 ≤tcp_max_syn_backlo,而后从新编译内核。/proc/sys/net/ipv4/tcp_max_tw_buckets系 统同时保持timewait套接字的最大数量。若是超过这个数字,time-wait套接字将马上被清除并打印警告信息。这个限制仅仅是为了防止简单的 DoS攻击,你绝对不能过度依靠它或者人为地减少这个值,若是网络实际须要大于缺省值,更应该增长这个值(若是增长了内存以后)。