服务器开发之大量time_wait 和 close_wait现象

一.tcp状态转换图

今天正好帮助同事定位一个服务器上大量close_wait状态的bug,为了更清晰的定位到这个bug。

因为time_wait和close_wait状态都是在tcp四次挥手状态下触发的,所以小伙伴们直接看下图

服务器开发之大量time_wait 和 close_wait现象_第1张图片

服务器开发之大量time_wait 和 close_wait现象_第2张图片

 

状态变化的解释过程:

从客户端来看:

1.客户端主动断开连接时,会先发送FIN包,客户端此时进入FIN_WAIT_1状态;

 

2.客户端收到服务器的ACK包(对步骤1中FIN包的应答)后,客户端进入FIN_WAIT_2状态;

3.客户端接收到服务器的FIN包并回复ACK包给服务端,然后客户端进入TIME_WAIT状态,此时会等待2个MSL的时间,

确保发送的ACK包是否达到了对端。

4.客户端在等待了2个MSL的时间没有收到服务器重传的FIN包,就默认ACK数据包已经抵达了对端。

从服务端来看:

1.服务器收到客户端发送的FIN数据包后,回复ACK包给客户端,此时服务器进入CLOSE_WAIT状态

2.等待服务器将剩余的数据全部发送给客户端时,然后执行断开操作,(老夫把该做的事都做了,然后再给这小子发送FIN包来结束,哈哈,姜还是老的辣!)

服务器向客户端发送出FIN包后,服务器端进入LAST_ACK状态,等待最后一个ACK确认包。

3.服务端收到客户端发送的ACK包后,从LAST_ACK状态转为CLOSED状态,服务器正式关闭了

二、close_wait产生原因实验剖析

 

CLOSE_WAIT状态:

被动断开连接的一方在发送完ACK分节之后就会进入CLOSE_WAIT状态.

它需要服务器在发送完剩余数据之后,就调用close来关闭连接.此时服务器从CLOSE_WAIT状态变为LAST_ACK状态.

小伙伴我们先来看下示例代码

client端代码如下

#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define MAXLINE 80
#define SERV_PORT 8000


int main(int argc, char *argv[])
{
    struct sockaddr_in servaddr;
    char str[MAXLINE] = "test ";
    int sockfd, n;


	while(1)
	{
		sockfd = socket(AF_INET, SOCK_STREAM, 0);


	    bzero(&servaddr, sizeof(servaddr));
	    servaddr.sin_family = AF_INET;
	    inet_pton(AF_INET, "192.168.254.26", &servaddr.sin_addr);
	    servaddr.sin_port = htons(SERV_PORT);


		connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
		write(sockfd, str, strlen(str));


		close(sockfd);
		sleep(2);
	}


    return 0;
}	while(1)
	{
		sockfd = socket(AF_INET, SOCK_STREAM, 0);


	    bzero(&servaddr, sizeof(servaddr));
	    servaddr.sin_family = AF_INET;
	    inet_pton(AF_INET, "192.168.254.26", &servaddr.sin_addr);
	    servaddr.sin_port = htons(SERV_PORT);


		connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
		write(sockfd, str, strlen(str));


		close(sockfd);
		sleep(2);
	}


    return 0;
}

server端代码

#include 
#include 
#include 
#include 


#include 
#include         /* For mode constants */
#include            /* For O_* constants */


#include 
#include 


using namespace std;


#define LENGTH 128
#include "netinet/in.h"
#define MAXLINE 80
#define SERV_PORT 8000


int main(int argc,char** argv)
{
	struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    //int i, n;
    int  n;


	//创建socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);


	//设置端口重用
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));


    //fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);


    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;


	inet_pton(AF_INET,"192.168.254.26",&(servaddr.sin_addr.s_addr));
    //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);
        int connfd = accept(listenfd,
                (struct sockaddr *)&cliaddr, &cliaddr_len);


		//while(1)
		{
	        n = recv(connfd, buf, MAXLINE,0);
	        if (n == 0)
			{
				//对端主动关闭
	            printf("the other side has been closed.\n");
	            //break;
	        }
	        printf("received from %s at PORT %d len = %d\n",
	               inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
	               ntohs(cliaddr.sin_port),n);
		}
        //测试:模拟CLOSE_WAIT状态时,将close(connfd);这句代码注释

        close(connfd);
    }


 	return 0;
}
	struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    //int i, n;
    int  n;


	//创建socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);


	//设置端口重用
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));


    //fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);


    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;


	inet_pton(AF_INET,"192.168.254.26",&(servaddr.sin_addr.s_addr));
    //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);
        int connfd = accept(listenfd,
                (struct sockaddr *)&cliaddr, &cliaddr_len);


		//while(1)
		{
	        n = recv(connfd, buf, MAXLINE,0);
	        if (n == 0)
			{
				//对端主动关闭
	            printf("the other side has been closed.\n");
	            //break;
	        }
	        printf("received from %s at PORT %d len = %d\n",
	               inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
	               ntohs(cliaddr.sin_port),n);
		}
        //测试:模拟CLOSE_WAIT状态时,将close(connfd);这句代码注释

        close(connfd);
    }


 	return 0;
}

测试代码中,当recv的返回值为0时(对端主动关闭连接),会跳出while(1)循环,此时正确做法是调用close关闭tcp连接

此处我们为了测试,故意将close(connfd)这句代码注释掉,注释后服务器对于客户端发送的FIN包不会做回应,一直保持close_wait状态。

运行截图

服务器端出现CLOSE_WAIT状态。

三、time_wait存在是否必要?

程序运行时的截图如下:

 

 

3.1 该状态用来防止最后一个ACK的丢失.

如果主动关闭连接的一端发送的最后一个ACK,在网络中延迟或丢失,被动关闭那么服务器将会重复发送FIN数据包,如果客户端不保留TIME_WAIT状态的话,客户端在发送完ack包后会进入closed状态,此时的状态再收到被动关闭连接一方的fin包,主动关闭方将发送一个RST分节,但是服务器将该分节解释为一个错误.

3.2 防止上一次连接中的分段延迟到达后影响新连接。

TCP连接由五元组(协议,源IP,源端口,目的IP,目的端口)唯一标识。假设没有TIME_WAIT状态,一个连接关闭后,可能使用相同的五元组的新连接被建立,这时若前一个原连接上的TCP分段因为网络延时刚刚到达,且它的序列号刚好在新连接的接收窗口,则会令新连接接收的数据混乱。尽管每次建立连接使用的序列号都是随机产生的,但是序列号的长度只有32位,在高速网络上可能很快出现序列号循环。TIME_WAIT状态持续2MSL后,原连接的数据包都已经在网络上消失,不会再干扰新连接。

如果服务器或客户端存在大量的TIME_WAIT状态,这是一种可能是正常的情况,主动断开连接的一方会进入TIME_WAIT状态.

主动连接端会占用本地端口,大量的TIME_WAIT状态的socket,会占用大量的本地端口,当本地端口不足时,tcp连接不能建立成功。可以通过以下两种方式来解决上述问题

1.调整参数net.ipv4.ip_local_port_range来增加本地端口的选择范围,但这样效果有限。

2.启用net.ipv4.tcp_tw_reuse参数来重用TIME_WAIT状态的socket。

3.linux api设置socket套接字的”端口重用“属性

通常情况下,客户端的端口资源比较充足,应该让客户端主动断开连接,但在某些场景下,如tcp连接长时间没有IO操作,应该将此空闲tcp连接踢除,否则空闲tcp会占有系统各个资源却不干事,太浪费了

参考资料

1.TCP网络关闭的状态变换时序图

https://coolshell.cn/articles/1484.html

2.tcp状态实验分析

http://www.just4coding.com/blog/2017/11/09/timewait/

你可能感兴趣的:(服务器开发之大量time_wait 和 close_wait现象)