Write C + + server by hand (37): tear code by hand -- asynchronous connect, the cornerstone of high concurrency multithreading technology [10000 words long text]

Keywords: C++ Linux Autonomous vehicles

Navigation for this series of articles:   Write C + + server by hand (0): column - Summary navigation [updating] 

The default blocking mode is used when connect is created, but in reality, the connection speed may be slow due to poor network, intermediate proxy server, gateway and other factors. At this time, in blocking mode, the program will block in connect for a long time. Therefore, in actual projects, we generally tend to use asynchronous connect technology to learn how to use IO reuse technology to set asynchronous connect, which can not only lay a foundation for high concurrency and multithreading, but also a necessary knowledge point for back-end development interview.

catalogue

Preparatory knowledge

1. connect function

Function return:

2. getsocketopt method

 getsockopt,setsockopt

Common options:

3. select for IO reuse

Function return

Detailed explanation of parameters

Detailed explanation of important structures

Use process

Official start

1. Code flow

2. Client code

3. Server code

4. Experimental effect

reference resources

Preparatory knowledge

1. connect function

The client uses connect() to establish a connection with the server:

#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen);

The sockfd parameter returns a socket from the socket system call. serv_ The addr parameter is the socket address monitored by the server, and the addrlen parameter specifies the length of this address.

Function return:

0 is returned when connect is successful. Once the connection is successfully established, sockfd uniquely identifies the connection, and the client can communicate with the server by reading and writing sockfd. If connect fails, return - 1 and set errno. Two common errnos are econnreused and ETIMEDOUT. Their meanings are as follows:

  • Econnreused, the target port does not exist, and the connection is rejected.
  • ETIMEDOUT, connection timeout.

2. getsocketopt method

 getsockopt,setsockopt

Read and set the properties and methods of the file descriptor.

#include <sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t* restrict option_len);

level: specify attributes, such as IPv4, IPv6, TCP, etc

Specific options and parameter meanings: getsockopt

#define SOL_IP 0

#define SOL_IPX 256

#define SOL_AX25 257

#define SOL_ATALK 258

#define SOL_NETROM 259

#define SOL_TCP 6

#define SOL_UDP 17

#define SOL_SOCKET 0xffff

Common options:

1,SO_REUSEADDR

When the TCP connection is in time_ In the wait state, SO_REUSEADDR to force the use of time_ socket address occupied by connection in wait state. Enables the address to be reused immediately.

2,SO_RCVBUF

TCP receive buffer size

3,SO_SNDBUF

TCP send buffer size

4,SO_RCVLOWAT

The TCP receive buffer low water mark is called by the I/O multiplexing system to determine whether the socket is writable.

5,SO_SNDLOWAT

The TCP send buffer low water mark is called by the I/O multiplexing system to determine whether the socket is writable.

6,SO_LINGER

Controls the behavior of the close system call when closing a TCP connection

3. select for IO reuse

select is used to listen for readable, writable, exception and other events on the file descriptor of interest to the user within a specified period of time. The function prototype is as follows:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);

Function return

  • Returns the total number of ready file descriptors when select is successful;
  • If no file descriptor is ready within the timeout period, select will return 0;
  • If select fails, return - 1 and set errno.;
  • If the program receives a signal during the select wait, select immediately returns - 1 and sets errno to EINTR.

Detailed explanation of parameters

  • nfds: Specifies the total number of listened file descriptors. It is usually set to select to listen to the maximum value + 1 in all file descriptors.
  • readfds: set of file descriptors corresponding to readable events.
  • writefds: set of file descriptors corresponding to writable events.
  • exceptfds: set of file descriptors corresponding to exception events.
  • Timeout: sets the select timeout.

Detailed explanation of important structures

readfds, writefds and exceptfds are all fd_set structure. timeout is the timeval structure. Here we will explain these two structures in detail.

1,fd_set

fd_ The definition of set structure is complex, involving in place operation. So we usually use macros to access FD_ Bit in set.

#include <sys/select.h>
FD_ZERO(fd_set* fdset);    // Clear all bits in fdset
FD_SET(int fd, fd_set* fdset); // Set bit in fdset
FD_CLR(int fd, fd_set* fdset); // Clear bits in fdset
int FD_ISSET(int fd, fd_set* fdset);  // Test whether the bit fd of fdset is set
  • FD_ZERO is used to empty the file descriptor group. It needs to be emptied before each call to select.
  • FD_SET adds a file descriptor to the group, FD_CLR moves a file descriptor out of the group.
  • FD_ISSET detects whether a file descriptor is in the group. We use this to detect which file descriptors can perform IO operations after a select call.

2,timeval

struct timeval {
    long tv_sec; // Seconds
    long tv_usec; // Subtle number
};

Use process

To sum up, our general use process is:

  1. Preparation - define readfds, timeval, etc
  2. Use FD_ZERO is cleared and FD is used_ Set sets the file descriptor. Because the set of file descriptors will be modified by the kernel after the event occurs.
  3. Call select
  4. Use FD_ISSET detects whether the file descriptor is in the group

Official start

1. Code flow

  1. Apply the general framework of socket.
  2. Set the created socket to non blocking mode.
  3. Connect to the server.
  4. Call select to listen for writable events on the socket where the connection failed.
  5. Call getsockopt to read the error code and clear the error on the socket.

2. Client code

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <iostream>
#include <fcntl.h>

// #define SEND_DATA "NO BLOCKING MODEL"
#define SEND_DATA "hello"

using namespace std;

// Set the file descriptor to non blocking mode
int setnoblocking(int fd) {
    // Gets the old status flag of the file descriptor
    int old_option = fcntl(fd, F_GETFL);
    // Set non blocking flag
    int new_option = old_option | O_NONBLOCK;
    // Set non blocking mode
    if (fcntl(fd, F_SETFL, new_option) == -1) {
        // std::cout << "set no blocking model is error" << std::endl;
        return -1;
    }
    // Returns the old status flag of the file descriptor for later recovery
    return old_option;
}

// Set non blocking connect
int set_unblock_connect(int port, int time = 10) {
    //Create socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (clientfd == -1) {
        std::cout << "create client error" << std::endl;
        return -1;
    }
    //Make a request to the server (specific IP and port)
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //Each byte is filled with 0
    serv_addr.sin_family = AF_INET;  //Use IPv4 address
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //Specific IP address
    serv_addr.sin_port = htons(port);  //port

    // Set clientfd to non blocking mode
    int oldconnectfd = setnoblocking(clientfd);
    if (oldconnectfd == -1) {
        std::cout << "set no block model is error" << std::endl;
        close(clientfd);
        return -1;
    }

    int connectfd = connect(clientfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if (connectfd == 0) {   // Connection succeeded
        std::cout << "connect success!" << std::endl;
        // Restore clientfd properties
        fcntl(clientfd, F_SETFL, oldconnectfd);
        return clientfd;
    } else if (errno != EINPROGRESS) {
        // Only the EINPROGRESS mode indicates that the connection is still in progress
        std::cout << "connect is error!" << std::endl;
        return -1;
    }
    fd_set readfds;
    fd_set writefds;
    struct timeval timeout;
    FD_ZERO(&readfds);
    FD_SET(clientfd, &writefds);

    timeout.tv_sec = time;
    timeout.tv_usec = 0;

    int ret = select(clientfd + 1, nullptr, &writefds, nullptr, &timeout);
    if (ret <= 0) {
        // select setting error or timeout
        std::cout << "set select is error" << std::endl;
        close(clientfd);
        return -1;
    }
    if (!FD_ISSET(clientfd, &writefds)) {
        std::cout << "no events on clientfd found!" << std::endl;
        close(clientfd);
        return -1;
    }

    int error = 0;
    socklen_t length = static_cast<socklen_t>(sizeof error);
    // Use getsockopt to get and clear the error above sockfd
    if(getsockopt(clientfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0) {
        std::cout << "get socket option is error" << std::endl;
        close(clientfd);
        return -1;
    }
    if (error != 0) {
        std::cout << "connect is fail after select with error " << error << std:: endl;
        close(clientfd);
        return -1;
    }
    // Connection succeeded
    std::cout << "connect is success after select!" << std::endl;
    fcntl(clientfd, F_SETFL, oldconnectfd);
    return clientfd;

}

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

    if (argc <= 1) {
        printf("error! please input port!\n");
        return 1;
    }

    // The first input parameter is the port
    int port = atoi(argv[1]);

    // Set non blocking connect
    int clientfd = set_unblock_connect(port, 10);
    if (clientfd < 0) {
        std::cout << "setting unblock connect is error" << std::endl;
        return 0;
    }

    // while (true) {
    //     char recvbuf[32] = {0};
    //     //In non blocking mode, the program will not be blocked whether there is data or not
    //     int ret = recv(clientfd, recvbuf, 32, 0);
    //     if (ret > 0) {
    //         std::cout << "recv data successfully!" << std::endl;
    //     } else if(ret == 0) {
    //         std::cout << "socket is closed!" << std::endl;
    //         break;
    //     } else {
    //         if (errno == EWOULDBLOCK) {
    //             std::cout << "no data avaliable" << std::endl;
    //         } else if (errno == EINTR) {
    //             std::cout << "recv data interruptered by signal" << std::endl;
    //         } else {
    //             break;
    //         }
    //     }
    // }
    close(clientfd);
    return 0;
}

3. Server code

// Set non blocking mode
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <iostream>

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

    if (argc <= 1) {
        printf("error! please input port!\n");
        return 1;
    }

    // Create listening socket
    int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(listenfd);
    int port = atoi(argv[1]);
 
    // Bind socket to IP and port
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //Each byte is filled with 0
    serv_addr.sin_family = AF_INET;  //Use IPv4 address
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //Specific IP address
    serv_addr.sin_port = htons(port);  //port
    if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        std::cout << "bind listen socket is error" << std::endl;
        close(listenfd);
        return -1;
    }
 
    // Enter the listening state and wait for the user to initiate a request
    if (listen(listenfd, SOMAXCONN) == -1) {
        std::cout << "listen error" << std::endl;
        close(listenfd);
        return -1;
    }
 
    // Receive client requests
    while (true) {
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);
        int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);
        if (clientfd != -1) {
            std::cout << "accept a client connection" << std::endl;
        } else {
            std::cout << "accept connection error!" << std::endl;
        }
    }

    close(listenfd);
    return 0;
}

4. Experimental effect

reference resources

Posted by pkSML on Wed, 29 Sep 2021 11:52:43 -0700