The Tcp server always sleep s, and the client keeps sending data

Question: "a tcp server and a tcp client. After the client establishes a connection with the server, the server always sleep s, and then the client always sends data".

Before answering this question, let's think about the characteristics of tcp and the general process of tcp sending data:

First of all, tcp is a reliable transmission protocol with links. The so-called reliability means that the data sent by the client can be received by the server in order. Then for the above problems, there can be no data discarding. So what if the client keeps sending more and more data? Let's analyze the transmission process of tcp.

Figure 1

As shown in Figure 1, when sending data:

(1) The data is first copied from the application buffer to the socket sending buffer (located in the kernel) at the sending end. Note that this process is completed by a function similar to the write function. Some people usually think that the data is sent to the opposite host when they see the write success. In fact, this is wrong. The write success only means that the data is successfully copied from the application process buffer to the socket send buffer.

(2) Then, the kernel protocol stack sends the data in the socket sending buffer to the opposite host. Note that this process is not controlled by the application program, but completed by the sender kernel protocol stack, including the use of sliding window, game control and other functions.

(3) The data reaches the socket receiving buffer of the host at the receiving end. Note that the receiving process is not controlled by the application program, but completed by the kernel protocol stack at the receiving end, including sending ack confirmation, etc.

(4) The data is copied from the socket receiving buffer to the receiving end application buffer. Note that this process is completed by functions such as read.

Recommended Videos:

tcpip, accept, 11 states, details and secrets, what else do you not know

Write a user state network protocol stack to instantly improve your network skills

Learning address: c/c++ linux server development / background architect

1. Blocking mode

After knowing this process, let's take a look at the working mode of write and other functions by default (socket blocking mode): output operation, including five functions: write, writev, send, sendto and sendmsg. For a TCP socket, the kernel copies data from the buffer of the application process to the send buffer of the socket. For a blocked socket, if there is no space in its send buffer, the process will be put into sleep until there is space—— UNPv1

In this way, we can infer the result: in the blocking mode, if the server does not receive data all the time and the client keeps writing, that is, we can only perform the first three steps in the above process, so the final result must be that the socket receiving buffer at the receiving end and the socket sending buffer at the sending end are filled, In this way, write cannot continue to copy data from the application to the socket send buffer at the sending end, so that the process goes to sleep.

Verification examples are as follows.

Client code:

l tcpClient.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define PORT 9999
#define Buflen 1024
int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr;
 int n,count=0;
    int sockfd;
    char sendline[Buflen];
    sockfd= socket(AF_INET,SOCK_STREAM,0);
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
 

 //Communicate with the server
    memset(sendline,'a',sizeof(Buflen));
 
    while ( (n=write(sockfd,sendline,Buflen))>0 )
    {
      count++;
      printf("already write %d bytes -- %d\n",n,count);
    }

    if(n<0)
       perror("write error");
    close(sockfd);
}

Each time the client writes successfully, the counter count is incremented by 1, and the number of bytes successfully written this time is output. Count the number of times the client write was saved successfully.

Server code:

l tcpServer.c

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 9999 / / define the communication port
#define BACKLOG 5 / / define the listening queue length
#define buflen 1024

int listenfd,connfd; 
int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr; //Storage server socket address structure
    struct sockaddr_in client_addr; //Storage client socket address structure
    pid_t pid; 
    listenfd = socket(AF_INET,SOCK_STREAM,0); 
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET; //protocol family
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //Local address
    server_addr.sin_port = htons(PORT);
    bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    listen(listenfd,BACKLOG); 
 for(;;)
 {
        socklen_t addrlen = sizeof(client_addr);
        connfd = accept(listenfd,(struct sockaddr *)&client_addr,&addrlen); 
 if(connfd<0)
            perror("accept error");
        printf("receive connection\n"); 
 if((pid = fork()) == 0) 
 {
          close(listenfd);
          sleep(1000);//Child process does not receive data, sleep 1000 seconds
          exit(0);
 }
 else
 {
            close(connfd);
 }
 }
}

First compile and run the server, and then start the client. The running results are shown in Figure 2.

Figure 2

It can be seen that the client is blocked after 377 successful writes. Note that this does not mean that the socket send buffer at the sender is full, but only that the available space of the socket send buffer is less than the number of writes requested by write - 1024.

Add: when the server sleep s to 1000, the current connection will be closed. At this time, the write of the client in blocking will return an error, as shown in Figure 3.

Figure 3

[article benefits] learning materials and groups of C/C++ Linux server architects are required 812855908 (data include C/C + +, Linux, golang technology, kernel, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, collaboration, DPDK, ffmpeg, etc.)

2 non blocking mode

Let's take a look at the working mode of write in the case of non blocking sockets: for a non blocking TCP socket, if there is no space in the send buffer, the output function will immediately return an EWOULDBLOCK error. If there is some space in the send buffer, the return value will be the number of bytes that the kernel can copy to the buffer. This number of bytes is also called "under count".

In this way, we can know the effect that the server always sleep s and the client always writes data under non blocking conditions: the client writes successfully. With the client writes, the socket receiving buffer at the receiving end and the socket sending buffer at the sending end will be filled. When the available space of the socket sending buffer at the sending end is less than the number of bytes written by the write request, write immediately returns - 1 and sets errno to EWOULDBLOCK.

The verification example code is as follows.

l the server is the same as the blocking situation (omitted).

Client (non blocking mode):

l tcpClientNonBlock.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
#define PORT 9999
#define Buflen 1024

int main(int argc,char *argv[])
{
    struct sockaddr_in server_addr;
 int n,flags,count=0;
    int sockfd;
    char sendline[Buflen];
    sockfd= socket(AF_INET,SOCK_STREAM,0);
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    flags=fcntl(sockfd,F_GETFL,0); //Set the connected socket to non blocking mode
    fcntl(sockfd,F_SETFL,flags|O_NONBLOCK);
    memset(sendline,'a',sizeof(Buflen));
 

    while ( (n=write(sockfd,sendline,Buflen))>0 )
   {
     count++;
     printf("already write %d bytes -- %d\n",n,count);
   }
 

   if(n<0)
  {
    if(errno!=EWOULDBLOCK)
      perror("write error");
    else
       printf("EWOULDBLOCK ERROR\n"); 
  }
   close(sockfd);
}

First compile and run the server, and then start the client. The running results are shown in Figure 4 below.

Figure 4

You can see that after the client successfully writes 185 times, the socket send buffer space is insufficient, thus returning EWOULDBLOCK error. We have noticed that the same number of bytes (1024) can be written successfully 377 times in blocking mode. Why is it less in non blocking mode? This is because in blocking mode, blocking occurs until both the socket receive buffer at the receiving end and the socket send buffer at the sending end are full. In the non blocking mode, the second step of the sending process at the sender may be slow, causing the socket sending buffer at the sender to be filled quickly, while the socket receiving buffer at the receiver is not full. In this way, write will return an error only because the socket sending buffer at the sender is full (to be exact, the available space of the socket send buffer is less than the number of bytes written by the write request). By comparison, 377 is about twice that of 185. Therefore, it can be inferred that due to the delay in the second step of the sending process, it is likely that the socket send buffer at the sending end is full and the socket receive buffer at the receiving end is empty.

3. Supplementary information

There is no real send buffer for UDP sockets. The kernel just copies the application process data and transfers it down the protocol stack, gradually crowned with UDP header and IP header. Therefore, for a blocked UDP socket (the default setting), the output function call will not be blocked for the same reason as the TCP socket, but it may be blocked for other reasons.

Keywords: Linux network Network Protocol TCP/IP

Added by djbuddhi on Thu, 04 Nov 2021 13:30:13 +0200