Network Programming TCP socket programming concurrent server

In the previous demonstration example of blog network programming (3), the simplest code flow of TCP has been introduced. This chapter expands on this basis:

  1. First, the server can continuously receive messages from the client, and the client can also send messages to the server repeatedly;
  2. After that, continue to improve the server, simply use the thread to process the connection request of each concurrent client for a period of time, and close the current thread after disconnection;
  3. Finally, the address translation correlation function is used to identify the protocol address structure of the opposite end user.

The first point has been explained in the previous blog. There are two points after the completion of the current blog.

1. TCP concurrent server

On the basis of the above, continue to improve the server program so that the server can continuously serve different clients. The simplest way is to directly move the accept code into the while loop, close the currently connected socket after interaction with the client, and wait for a new client connection.

For simplicity, omit the general code and error handling part, and give the body code of the current method:

int main()
{
    ///1. Create socket   
    ///2. Bind to local port
    ///3. Monitoring

    int len;
    char buf[1024];
    while(true) 
    {	// 4. Waiting for connection
	    int sock_id = ::accept(socket_fd, NULL, NULL);  //No peer address structure is required
	    if(sock_id < 0) {
	        break 1;
	    }
	    while(true) {
	        len = read(sock_id, buf, sizeof(buf));
	        if(len < 0) {
	            break;
	        }
	        else if(len == 0) {  // The tcp read length is 0, indicating that the opposite end closes the connection
	            break;
	        }
	        // echo 
	        len = ::write(sock_id, buf, strlen(buf));
	    }
	    ::close(sock_id); // Close the client connected TCP socket
	}
	
	///5. Close the connection
    ::close(socket_fd);
}

The improved server is an iterative server, which is sufficient for a simple TCP connection request (such as obtaining a time). However, when one client of the service takes a long time to request or has a large number of interactions, it is not expected that the whole server will be occupied by a single client for a long time, but that multiple clients will be served at the same time.

Therefore, here we simply use the multithreading method to realize concurrency, create a thread for each customer connection to serve customers, and continuously receive new TCP client connections.
Here, the c++11 thread library is directly used. First add the customer service thread function:

#include <thread>
void client_service(int sock_id)
{
    int len;
    char buf[1024]; 
    while (1)
    {
        len = read(sock_id, buf, sizeof(buf));
        if(len < 0){
            printf("%s: recv failed. %s\n", __func__, strerror(errno));
            break;
        }else if(len == 0){  // The tcp read length is 0, indicating that the opposite end closes the connection
            printf("%s: client socket %d disconnet. recv len = 0.\n", __func__, sock_id);
            break;
        }
        buf[len] = 0; // //Avoid that the received data is shorter than the last time, resulting in output display error

        if (strcmp(buf, "exit\n") == 0)   break;

        printf("recv from socket %d (%d): %s\n", sock_id, len, buf );

        // echo 
        len = ::write(sock_id, buf, strlen(buf));
        if(len < 0){
            printf("%s: send failed. %s\n", __func__, strerror(errno));
            break;
        }
    }
    ::close(sock_id); // Close the client connected TCP socket
}

Then continue to modify the code of the while part to detach the newly created thread and serve the client.

int main()
{
    ///1. Create socket   
    ///2. Bind to local port
    ///3. Monitoring

    int len;
    char buf[1024];
    while(true)
    {
        // 4. Waiting for connection
        sockaddr_storage_in clientaddr;  // It can meet the memory requirements of IPv4 and IPv6
        socklen_t sock_len = sizeof(clientaddr);
        int sock_id = ::accept(socket_fd, (sockaddr*)&clientaddr,&sock_len); 
        if(sock_id < 0){
            printf("%s: accept error. %s\n", __func__, strerror(errno));
             return 1;
        }  
		// Create a thread for the current connection
        std::thread(client_service).detach();
    }
    
	///5. Close the connection
    ::close(socket_fd);
}

So far, the above code has an outline of a typical concurrent server program. The server can accept requests from multiple clients without interference. In the next section, a demo screenshot of multiple client connections and displaying client information at the same time is given.

2. TCP multi client address

In UDP socket programming, it has been given to obtain the address structure information of the opposite end through recvfrom() function. In TCP programming, the server can obtain the client information through the accept() function, and can also obtain the client information by taking the return value of the accept() function - the connected socket of the client as the getpeername() function parameter.
Detailed methods for obtaining address information, See blog in the next chapter.

For the server, we can directly use the getpeername method to obtain the client information and modify the client service thread function void client_service(int sock_id), add code

void client_service(int sock_id)
{
    sockaddr_storage clientaddr; 
    sock_len = sizeof(clientaddr);  // 128
    getpeername(sock_id, (sockaddr*)&clientaddr, &sock_len);

    char client_ip[INET6_ADDRSTRLEN]; // Enough IPv4 and IPv6 addresses
    int port;
    if(sock_len == sizeof(sockaddr_in)){ // 128-> 16
        sockaddr_in *cliAddr = (sockaddr_in *)&clientaddr;
        //char* client_ip = inet_ntoa(cliAddr->sin_addr);
        inet_ntop(AF_INET, &cliAddr->sin_addr, client_ip, sock_len);
        port = ntohs(cliAddr->sin_port);
        printf("new socket id = %d, clent %s:%d\n", sock_id, client_ip, port);
    }
    else if(sock_len == sizeof(sockaddr_in6)){  // 128 -> 28
        sockaddr_in6 *cliAddr = (sockaddr_in6 *)&clientaddr;
        inet_ntop(AF_INET6, &cliAddr->sin6_addr, client_ip, sock_len);
        port = ntohs(cliAddr->sin6_port);
        printf("new socket id = %d, clent %s:%d\n", sock_id, client_ip, port);
    }

    int len;
    char buf[1024];
	//   
	// Omit, leave unchanged
	///
}

For the client, there is no need to obtain the address information of the server. The TCP client does not need it, but after connecting, we can use the connected socket to obtain the server address information with getpeername and obtain the local address information with getsockname. The two function codes use all methods, but the function names are different.
It is abbreviated here to INET_ Take IPv4 protocol as an example.

   int ret = ::connect(socket_fd, (sockaddr*)&servaddr, sizeof(servaddr));

   sockaddr_in localaddr;
   socklen_t   sock_len = sizeof(localaddr);  // Initial value must be given
   // The initial length must be given. getsockname will be re assigned according to the actual situation. Here, 16 - > 16
   // If the initial value is less than 0, an error will be reported and Invalid argument will be prompted
   ret = getsockname(socket_fd, (sockaddr*)&localaddr, &sock_len);
   if(ret == -1){
      printf("%s: getsockname failed. %s\n", __func__, strerror(errno));
   }else{
      printf("%s: local addr: %s:%d\n", 
          __func__, inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
   }

	//  Omit

Run the server respectively, connect multiple clients, send information alternately, and finally disconnect. The demonstration results are as follows:

Tags: socket Concurrent Programming tcp

Posted by Lauj on Sun, 22 May 2022 15:43:46 +0300