这个问题之前没有怎么留意过,是最近在面试过程中遇到的一个问题,面了两家公司,两家公司竟然都面到到了这个问题,不得不使我开始关注这个问题。说起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
TCP状态以及握手详解:
1、建立连接协议(三次握手)
相关推荐
关于系统端口出现CLOSE_WAIT状态的解决方案,讲解明确清晰,值得参考
对于服务器挂起中的CLOSE_WAIT & FIN_WAIT2 解决方案。
挖掘鸡 v7.1:修改close_wait状态,忽略扫描域名无效等问题。是否更新。23:30 2009-10-5 挖掘鸡 v7.0:进一步增大扫描范围,改进扫描算法和效率;自动关键词联想,支持超长时间扫描等。建议更新。20:46 2009-7-30 ...
挖掘鸡 v7.1:修改close_wait状态,忽略扫描域名无效等问题。是否更新。23:30 2009-10-5 挖掘鸡 v7.0:进一步增大扫描范围,改进扫描算法和效率;自动关键词联想,支持超长时间扫描等。建议更新。20:46 2009-7-30 ...
挖掘鸡 v7.1:修改close_wait状态,忽略扫描域名无效等问题。是否更新。23:30 2009-10-5 挖掘鸡 v7.0:进一步增大扫描范围,改进扫描算法和效率;自动关键词联想,支持超长时间扫描等。建议更新。20:46 2009-7-30 ...
Python Python深拷贝和浅拷贝的区别 Python的内存管理机制(垃圾回收+内存池) ⼀、引用计数 ⼆、垃圾回收 三、内存池机制 ...S 为什么TIME_WAIT状态需要经过OMSL才能返回到CLOSE状态? TCP VS UDP ⼀、TCP/IP网络
挖掘鸡 v7.1:修改close_wait状态,忽略扫描域名无效等问题。是否更新。23:30 2009-10-5 挖掘鸡 v7.0:进一步增大扫描范围,改进扫描算法和效率;自动关键词联想,支持超长时间扫描等。建议更新。20:46 2009-7-30 ...
挖掘鸡 v7.1:改掉close_wait状态。忽略扫描域名无效等问题。是否更新。23说明:30 2009-10-5 挖掘鸡 v7.0:进一步增大扫描范围。改进扫描算法和效率;这个软件这个软件这个软件工具你本人关键词联想。非常肯定超长...
修改close_wait状态,忽略扫描域名无效等问题。是否更新。 挖掘鸡 v7.0更新20:46 2009-7-30: 进一步增大扫描范围,改进扫描算法和效率;自动关键词联想,支持超长时间扫描等。建议更新。 挖掘鸡 v.6.9更新...
答:启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 40.接口是否可...
软件编程规范培训实例与练习 软件编程规范培训实例与练习 问题分类 1 逻辑类问题(A类)-指设计、编码中出现的计算正确性和一致性、程序逻辑控制等方面出现的问题,在系统中起关键作用,将导致软件死机、功能...
17.3.3 @@FETCH_STATUS全局变量检测FETCH操作的状态 17.3.4 游标的关闭与释放 17.3.5 游标变量 17.3.6 使用系统过程管理游标 17.4 Oracle中游标的使用 17.4.1 显式游标与隐式游标 17.4.2 游标的属性 ...
所以这里我们针对不同的问题给出一些解决方法:(1)如果视频画面显示为花屏,这可能是由于安装摄像头的驱动程序与显卡驱动程序不兼容而造成的,这时需要升级显卡或摄像头的驱动程序。另外也有可能是摄像头与其它正在...
Thread.wait()或是Thread.sleep()。替代的方法是,主线程应该为子线程提供 一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序, 将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件...
(3)PATH命令有三种使用方法: PATH[盘符1:][路径1][盘符2:][路径2]…(设定可执行文件的搜索路径) PATH:(取消所有路径) PATH:(显示目前所设的路径) (六)TREE——显示磁盘目录结构命令 1.功能...
这个问题我会在近期解决.对此造成的问题我也有错,对不起... MaxDOS 5.6S 说明文件 如果您有何问题,请在此跟贴,或者到我论坛中发问。谢谢。 1.加入了10多种新型网卡驱动,以及更新以前一些驱动存在的问题. 2.支持...