这个问题之前没有怎么留意过,是最近在面试过程中遇到的一个问题,面了两家公司,两家公司竟然都面到到了这个问题,不得不使我开始关注这个问题。说起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、建立连接协议(三次握手)
相关推荐
2. CLOSE_WAIT状态的解决方法 3. TCP连接的结束流程 4. 使用netstat -na命令查看TCP连接状态 5. 编程的重要性在于确保正确关闭连接 延伸阅读: * CLOSE_WAIT状态的详细解释 * TCP连接的结束流程详解 * 使用netstat...
"TCP 状态迁移,CLOSE_WAIT & FIN_WAIT2 的问题解决" TCP 状态迁移是 TCP 协议中的一种机制,它用于描述 TCP 连接的不同状态。在 TCP 连接中,客户端和服务器端都可以处于不同的状态,例如 ESTABLISHED、CLOSE_WAIT...
4. **长时间无数据交换**:如果服务器在CLOSE_WAIT状态下长时间没有与客户端交互,可能导致连接占用资源过多。 **解决CLOSE_WAIT错误的策略** 1. **优化代码**:确保服务器端的程序在接收FIN标志后,及时发送FIN...
1. 调整TCP连接超时设置:增加TIME_WAIT和CLOSE_WAIT状态的超时时间,允许服务器有更多时间处理剩余的数据。 2. 优化应用程序:确保程序在完成数据传输后立即关闭连接。 3. 监控和限制并发连接:设置上限以防止过多...
当TCP连接长时间停留在Close_Wait状态,可能的原因有: - 服务器程序逻辑错误,导致未能及时发送FIN报文。 - 服务器上存在大量未处理的请求或数据,无法及时关闭连接。 - 网络延迟或丢包,使得服务器无法接收到...
下面将详细讲解Close_Wait状态、产生原因、诊断方法及解决方案。 1. **TCP连接生命周期**: - TCP连接有建立(SYN/SYN+ACK/ACK)、数据传输、关闭等阶段,其中"四次挥手"用于关闭连接。Close_Wait是在四次挥手的第...
大量CLOSE_WAIT状态可能意味着应用程序存在错误,没有及时关闭不再使用的连接,或者由于CPU过载或其他原因导致应用无法及时处理关闭操作。 总结: 系统调优时,理解和处理TIME_WAIT和CLOSE_WAIT状态是关键。TIME_...
标题 "tomcat-timewait-closewait.zip" 暗示了这个压缩包可能包含与Tomcat服务器在处理TCP连接时遇到的“Time_wait”和“Close_wait”状态相关的问题和解决方案。这两个术语是TCP/IP协议栈中的关键概念,尤其在高...
总的来说,Socket编程中的CLOSE_WAIT状态是网络通信中的常见问题,理解其含义并掌握处理方法对于开发稳定、高效的网络服务至关重要。通过阅读并分析【Socket]CLOSEWAIT.mht】文件,你应该能够找到具体问题的解决方案...
最后,被动关闭的一方会由 CLOSE_WAIT 状态转移到 LAST_ACK 状态,发送 FIN 给对方,同时在接收到 ACK 时进入 CLOSED 状态。 TIME_WAIT 状态是 TCP 连接状态中最复杂的状态。它可以由多个状态转移到达,包括 FIN_...
例如,可以通过修改 tcp_keepalive_*系列参数来解决 CLOSE_WAIT 状态的问题。此外,还可以通过修改 inet_peer_gc_maxtime、inet_peer_gc_mintime 等参数来调整 INET peer storage 的大小和时间间隔。 Linux 下 TCP ...
当一方发送了最后一个确认(ACK)之后,它必须进入TIME_WAIT状态,以防万一这个ACK丢失导致对方重新发送FIN,此时可以再次发送ACK。 - **TIME_WAIT状态为何需维持2MSL?** - MSL(Maximum Segment Lifetime)是指...
- **TIME_WAIT**:客户端接收到服务器的FIN后,发送ACK确认,并进入TIME_WAIT状态,等待2MSL时间后返回CLOSED状态。 - **CLOSED**:连接完全关闭。 ##### 2. 服务器TCP状态迁移 - **CLOSED**:初始状态,没有连接...
CLOSE_WAIT状态是TCP连接中的一个状态,表示服务器端的连接已经关闭,而客户端还没有关闭连接。这可能是由于服务器端的资源问题或网络问题引起的。在这个案例中,作者怀疑是GC导致了STW(Stop The World),但实际上...
在`CLOSE_WAIT`状态下,服务器接收到客户端的关闭请求但并未立即关闭,而是继续接收数据。这可能是因为服务器还有未处理的数据或者等待应用程序调用close()函数。在服务器端程序设计时,需要注意处理这个状态,避免...
收到FIN包的一方在发送ACK后进入CLOSE_WAIT状态,表示它已经接收到了关闭请求,但还可以发送数据。当该方完成数据发送后,也会发送FIN包,进入LAST_ACK状态。 10. **最终关闭(Fully Closed)** 最初发送FIN的...
值得注意的是,CLOSE_WAIT状态的解决通常需要服务端程序员检查代码逻辑,确保在应用程序中正确地关闭了连接。另外,使用HTTP的keep-alive特性也可以让服务端主动断开连接,减少 CLOSE_WAIT状态的出现。 TCP协议是...
这可能导致大量TCP连接处于CLOSE_WAIT状态,如果这些连接没有被及时重用,就会占用系统资源,引发性能问题。为了解决这个问题,可以在设置选项时使用`CURLOPT_FORBID_REUSE`,将其设为1,这样libcurl会在完成任务后...