Can TCP wave four times become three waves?

Author: Kobayashi coding

Computer stereotyped essay website: https://xiaolincoding.com

Hello everyone, I am Xiaolin.

Although when we are learning TCP waving, what we learn is that it takes four times to complete TCP waving, but in some cases, TCP four waving can become TCP three waving .

insert image description here

And when using the wireshark tool to capture packets, we often see that the TCP wave process is three times instead of four times, as shown in the following figure:

insert image description here

Let's first answer why it takes four times to define the TCP waving process in the RFC document?

Let me answer under what circumstances, under what circumstances will there be three waved hands?

TCP four waves

The process of TCP four-way wave is as follows:

insert image description here

Specific process:

  • The client actively calls the function to close the connection, so it will send a FIN message. This FIN message means that the client will no longer send data and enter the FIN_WAIT_1 state;
  • After receiving the FIN message, the server immediately replies with an ACK confirmation message, and the server enters the CLOSE_WAIT state. When receiving a FIN packet, the TCP protocol stack will insert an end-of-file character EOF into the receiving buffer for the FIN packet, and the server application can perceive this FIN packet by calling read, and this EOF will be placed in the queued After waiting for other received data , it is necessary to continue to read the received data in the receive buffer;
  • Then, when the server is reading data, it will naturally read EOF at the end, and then read() will return 0. At this time, if the server application has data to send, it will call to close the connection after sending the data. If the server application has no data to send, it can directly call the function to close the connection. At this time, the server will send a FIN packet. This FIN packet means that the server will not send any more data, and then it will be in LAST_ACK state;
  • The client receives the FIN packet from the server and sends an ACK confirmation packet to the server, at this point the client will enter the TIME_WAIT state;
  • After the server receives the ACK confirmation packet, it enters the final CLOSE state;
  • After the client passes the 2MSL time, it also enters the CLOSE state;

As you can see, each direction requires a FIN and an ACK , so it's often called four waves .

Why does TCP need to wave four times?

When the server receives the FIN message from the client, the kernel will immediately return an ACK response message, but the server application may still have data to send, so it cannot send the FIN message immediately, but will send the control of the FIN message Authorization to the server application :

  • If the server application has data to send, it will call the function to close the connection after sending the data;
  • If the server application has no data to send, it can directly call the function to close the connection,

From the above process, it can be seen that the control of whether to send the third wave is not in the kernel, but in the application of the passive closing party (the server in the above figure), because the application may still have data to send, and it is up to the application Decide when to call the function to close the connection. When the function to close the connection is called, the kernel will send a FIN message. **So the ACK and FIN of the server are generally sent separately.

Does the FIN message have to call the function to close the connection before it is sent?

uncertain.

If the process exits, no matter whether it is a normal exit or an abnormal exit (such as a process crash), the kernel will send a FIN message and wave with the other party four times.

Rough shutdown vs Graceful shutdown

When I introduced the TCP wave four times earlier, I did not introduce the function of closing the connection in detail. In fact, there are two functions of the function of closing the connection:

  • close function, and the socket closes the sending direction and reading direction at the same time, that is, the socket no longer has the ability to send and receive data;
  • The shutdown function can specify that the socket only closes the sending direction but not the reading direction, that is, the socket no longer has the ability to send data, but still has the ability to receive data;

If the client uses the close function to close the connection, then during the TCP four-way wave, if the data sent by the server is received, since the client no longer has the ability to send and receive data, the client's kernel will return RST The message is sent to the server, and then the kernel will release the connection. At this time, there will be no four waves of completed TCP, so we often say that calling close is a rough shutdown.

insert image description here

When the server receives the RST, the kernel will release the connection, and when the server application initiates a read or write operation again, it will be aware that the connection has been released:

  • If it is a read operation, it will return an RST error, which is our common Connection reset by peer.
  • If it is a write operation, the program will generate a SIGPIPE signal, and the application layer code can capture and process the signal. If not, the process will terminate by default and exit abnormally.

In contrast, the shutdown function can specify that only the sending direction is closed but not the reading direction, so even if the data sent by the server is received during the TCP four-way wave, the client can read the data normally. Then it will experience a complete four wave of TCP, so we often say that calling shutdown is an elegant shutdown.

Close gracefully.drawio.png

But note that the shutdown function can also specify "only close the reading direction, but not the sending direction", but at this time the kernel will not send a FIN message, because sending a FIN message means that we will no longer send any Data, and if shutdown specifies "do not close the sending direction", it means that the socket still has the ability to send data, so the kernel will not send FIN.

What happens when you wave your hands three times?

When the passive closing party (the server in the picture above) "has no data to send" and "enables the TCP delayed confirmation mechanism" during the TCP wave , then the second and third waves will be combined for transmission, thus appearing Three waves.

insert image description here

Then, because the TCP delayed acknowledgment mechanism is enabled by default, when we capture packets, we see more waved hands three times than four times.

What is TCP delayed acknowledgment mechanism?

When sending an ACK that does not carry data, its network efficiency is also very low, because it also has 40 bytes of IP header and TCP header, but it does not carry data packets. In order to solve the problem of low ACK transmission efficiency, TCP delayed confirmation
is derived . The strategy of TCP delayed confirmation:

  • When there is response data to be sent, ACK will be sent to the other party immediately along with the response data
  • When there is no response data to send, the ACK will be delayed for a period of time to wait for response data to be sent together
  • If the other party's second data message arrives again during the delay waiting to send ACK, then ACK will be sent immediately

The delay waiting time is defined in the Linux kernel, as shown in the figure below:

The key is the value of HZ. HZ is related to the clock frequency of the system. Each operating system is different. In my Linux system, the HZ size is 1000, as shown in the following figure:

Knowing the size of HZ, then you can calculate:

  • The maximum delayed confirmation time is 200 ms (1000/5)
  • Minimum delay acknowledgment time is 40 ms (1000/25)

How to turn off the TCP delayed confirmation mechanism?

If you want to turn off the TCP delayed acknowledgment mechanism, you can enable TCP_QUICKACK in the Socket settings.

// 1 表示开启 TCP_QUICKACK,即关闭 TCP 延迟确认机制
int value = 1;
setsockopt(socketfd, IPPROTO_TCP, TCP_QUICKACK, (char*)& value, sizeof(int));

Experimental verification

experiment one

Next, let’s do an experiment for everyone to verify this conclusion:

When the passive closing party (the server in the picture above) "has no data to send" and "enables the TCP delayed confirmation mechanism" during the TCP wave , then the second and third waves will be combined for transmission, thus appearing Three waves.

The code of the server is as follows, the thing to do is very simple, just read the data, and when the read returns 0, immediately call close to close the connection. Because the TCP delayed acknowledgment mechanism is enabled by default, no special settings are required.

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/tcp.h>

#define MAXLINE 1024

int main(int argc, char *argv[])
{
    
    

    // 1. 创建一个监听 socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
    
    
        fprintf(stderr, "socket error : %s\n", strerror(errno));
        return -1;
    }

    // 2. 初始化服务器地址和端口
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(8888);

    // 3. 绑定地址+端口
    if(bind(listenfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) < 0)
    {
    
    
        fprintf(stderr,"bind error:%s\n", strerror(errno));
        return -1;
    }

    printf("begin listen....\n");

    // 4. 开始监听
    if(listen(listenfd, 128))
    {
    
    
        fprintf(stderr, "listen error:%s\n\a", strerror(errno));
        exit(1);
    }


    // 5. 获取已连接的socket
    struct sockaddr_in client_addr;
    socklen_t client_addrlen = sizeof(client_addr);
    int clientfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addrlen);
    if(clientfd < 0) {
    
    
        fprintf(stderr, "accept error:%s\n\a", strerror(errno));
        exit(1);
    }

    printf("accept success\n");

    char message[MAXLINE] = {
    
    0};
    
    while(1) {
    
    
        //6. 读取客户端发送的数据
        int n = read(clientfd, message, MAXLINE);
        if(n < 0) {
    
     // 读取错误
            fprintf(stderr, "read error:%s\n\a", strerror(errno));
            break;
        } else if(n == 0) {
    
      // 返回 0 ,代表读到 FIN 报文
            fprintf(stderr, "client closed \n");
            close(clientfd); // 没有数据要发送,立马关闭连接
            break;
        }

        message[n] = 0; 
        printf("received %d bytes: %s\n", n, message);
    }
	
    close(listenfd);
    return 0;
}

The client code is as follows, and what it does is very simple. After successfully connecting with the server, it sends data to the server, and after sleeping for a second, it calls close to close the connection, so the client is the one that actively closes:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main(int argc, char *argv[])
{
    
    

    // 1. 创建一个监听 socket
    int connectfd = socket(AF_INET, SOCK_STREAM, 0);
    if(connectfd < 0)
    {
    
    
        fprintf(stderr, "socket error : %s\n", strerror(errno));
        return -1;
    }

    // 2. 初始化服务器地址和端口
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(8888);
    
    // 3. 连接服务器
    if(connect(connectfd, (struct sockaddr *)(&server_addr), sizeof(server_addr)) < 0)
    {
    
    
        fprintf(stderr,"connect error:%s\n", strerror(errno));
        return -1;
    }

    printf("connect success\n");


    char sendline[64] = "hello, i am xiaolin";

    //4. 发送数据
    int ret = send(connectfd, sendline, strlen(sendline), 0);
    if(ret != strlen(sendline)) {
    
    
        fprintf(stderr,"send data error:%s\n", strerror(errno));
        return -1;
    }

    printf("already send %d bytes\n", ret);

    sleep(1);

    //5. 关闭连接
    close(connectfd);
    return 0;
}

Compile the server and client code:

insert image description here

First enable the server:

insert image description here

Then use the tcpdump tool to start capturing packets, the command is as follows:

tcpdump -i lo tcp and port 8888 -s0 -w /home/tcp_close.pcap

Then enable the client, you can see that after successfully connecting with the server, it exits after sending the data.

insert image description here

At this point, the server output:

insert image description here

Next, let's take a look at the results of packet capture.

insert image description here

It can be seen that the number of TCP waves is 3 times.

Therefore, the following conclusion is correct.

Conclusion: When the passive closing party (the server in the above picture) " has no data to send" and "enables the TCP delayed confirmation mechanism (it will be enabled by default)" during the TCP wave , then the second and third wave will be The transfers are merged so that there are three waves.

Experiment 2

Let's do another experiment to see if the TCP delayed confirmation mechanism is turned off, will there be four waved hands?

The client code remains the same, the server code needs to add a little something.

In the above server code, the code that opens the TCP_QUICKACK (quick response) mechanism is added, as follows:

insert image description here

After compiling the server code, start running the server and client codes, and use tcpdump to capture packets at the same time.

The result of packet capture is as follows, you can see that there are four waved hands.
insert image description here

Therefore, when the passive closing party (the server in the above picture) "has no data to send" during the TCP wave, and at the same time "turns off the TCP delay confirmation mechanism", then it will wave four times.

Why should the code for setting TCP_QUICKACK be placed after read returns 0?

I also found out through many experiments that setting TCP_QUICKACK before bind does not take effect. Only when read returns 0, setting TCP_QUICKACK will cause four waves.

I checked the information on the Internet and said that setting TCP_QUICKACK is not permanent, so every time you read data, if you want to return ACK immediately, you have to reset TCP_QUICKACK after each data read.

The purpose of my experiment here is to return an ACK message immediately after receiving the FIN message from the client (the first wave). So when read returns 0, set TCP_QUICKACK. Of course, in practical applications, no one will set TCP_QUICKACK at this position, because the operating system helps us optimize four waved hands into three waved hands through the TCP delayed confirmation mechanism.

Summarize

When the passive closing party is in the process of TCP waving, if "there is no data to send" and "TCP_QUICKACK is not enabled (by default, it is not enabled, TCP_QUICKACK is not enabled, which means that it is using the TCP delayed confirmation mechanism)", then the second and second Three waves will merge the transmissions, so there will be three waves.

Therefore, the phenomenon of waving hands three times is caused by the delayed confirmation mechanism of TCP.

over!

Guess you like

Origin blog.csdn.net/qq_34827674/article/details/126561006