CLOSE_WAIT状态的原因与解决⽅法(转载留⾃⼰看)
这个问题之前没有怎么留意过,是最近在⾯试过程中遇到的⼀个问题,⾯了两家公司,两家公司竟然都⾯到到了这个问题,不得不使我开始关注这个问题。说起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];
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);
怎么查自己的ip}
}
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];
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));
参考:
blog.chinaunix/u/20146/showart_1217433.html
blog.csdn/eroswang/archive/2008/03/10/2162986.aspx
haka.sharera/blog/BlogTopic/32309.htm
learn.akae/media/ch37s02.html
faq.csdn/read/208036.html
wwwdw/tech/server/2006040430203.asp
davidripple.bokee/1741575.html
doserver/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连接前,先把这个旧连接关闭
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论