前因
最近遇到一个问题:在存在防火墙的情况下,某些客户端在某个时间段连接服务器时会出现连接不上的问题,在客户端侧用wireshark抓包发现,在进行tcp三次握手过程中,对于客户端的第一个SYN,服务器端总以RST响应。
分析
年少不懂事的我,一开始以为是防火墙的问题,但是该同一源地址的客户端便会出现这种情况,应该也就排除了这种情况,防火墙的规则基本上是不会更改的。此路不通,便想到了是不是服务端监听的那一路连接是不是主动发送了FIN,进入到TIME_WAIT的状态。此时其他的客户端再进行连接时,就会收到服务端的RST响应。为了证实这种情况下,是符合的这种现象,构造了下面简单的demo程序,由于只是简单的程序,很多异常并没有考虑。
客户端程序:
/**
* client.c
**/
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
int n, sockfd;
char *pBuf = "HI,SERVER";
struct sockaddr_in stServer;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
printf("socket failed,reason[%s]\n", strerror(errno));
return -1;
}
memset(&stServer, 0, sizeof(stServer));
stServer.sin_family = AF_INET;
stServer.sin_port = htons(9999);
inet_pton(AF_INET, argv[1], &stServer.sin_addr);
if( -1 == (connect(sockfd, (struct sockaddr *)&stServer, sizeof(stServer))))
{
printf("connect failed,reason[%s]\n", strerror(errno));
return -1;
}
n = write(sockfd, pBuf, strlen(pBuf));
/**
* 等待server发送FIN后再FIN
**/
sleep(5);
return 0;
}
服务端程序:
/**
* server.c
**/
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
int main()
{
size_t len;
int sockfd, acceptfd, n;
char buf[1024] = {0};
struct sockaddr_in stServer,stCliet;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
printf("socket failed,reason[%s]\n", strerror(errno));
return -1;
}
memset(&stServer, 0, sizeof(stServer));
stServer.sin_family = AF_INET;
stServer.sin_port = htons(9999);
stServer.sin_addr.s_addr = htonl(INADDR_ANY);
if(-1 == bind(sockfd, (struct sockaddr *)&stServer, sizeof(stServer)))
{
printf("bind failed,reason[%s]\n", strerror(errno));
return -1;
}
if(-1 == listen(sockfd, 8))
{
printf("listen failed,reason[%s]\n", strerror(errno));
return -1;
}
memset(&stCliet, 0, sizeof(stCliet));
len = sizeof(stCliet);
acceptfd = accept(sockfd, (struct sockaddr *)&stCliet, &len);
if(-1 == acceptfd)
{
printf("acceptfd failed,reason[%s]\n", strerror(errno));
return -1;
}
n = read(acceptfd, buf, 1023);
if(0 < n)
{
printf("read %d bytes:%s\n", n, buf);
}
/**
* 主动发送FIN
**/
close(sockfd);
return 0;
}
这里做的一个处理是:客户端在发送数据后,等待一段时间,等待服务端的FIN,使其服务端在四次挥手结束后进入到TIME_WAIT状态。
编译程序后,首先运行服务端,再运行客户端,此时一路tcp连接建立,并使得服务端进入到了TIME_WAIT的状态,在此时,再运行一次客户端程序,此时收到错误输出:Connection refused。
通过抓的包分析,可以看到,在第一路连接释放后,在服务端进入到TIME_WAIT状态时,第二路的客户端进行连接时,SYN对应的响应为RST,详细的包信息见下图:
这个现象与之前碰到的问题现象是基本一致的,根据这个思路,尝试去分析了是否是服务端出现了某些异常的情况主动释放了tcp连接,因此在某段时间内(TIME_WAIT的时间一般为:2MSL),对于所有的客户端都无法连接上。由于其他事情,这个问题也暂时放了放,目前还未找到确切的原因。
结束语
在整个网络编程中,其实TIME_WAIT的状态是需要特别注意的。网络编程虽然坑多,但是只要我们结合原理一步步分析,整个过程都是其乐无穷的。(逃。。。。。