How to implement asynchronous connect

Posted by Skawn on Thu, 28 Oct 2021 04:38:04 +0200

Students who have written network programs should all know the connect function. Before the socket starts reading and writing operations, it is necessary to connect, that is, the TCP three-time handshake. This process is completed in the connect function. The connect function itself is blocked. The asynchronous connect function can be realized by setting the socket options and calling the select/poll function

socket is in blocking mode by default. When it is in blocking mode, after calling the connect function, it will wait until the connection result is returned. It will either succeed or fail. When the connect function returns 0, it will succeed and return - 1 failure

In LAN, calling connect function basically returns results immediately. When the server is abroad, connect function will block for a period of time, about a few seconds, the specific time depends on the network condition at that time.

Why use asynchronous connect

The default timeout of connect under Linux is about one minute (slightly different from different Linux versions). In actual development, this time seems a little long

For the server, it needs to serve many clients and minimize blocking. Therefore, asynchronous connect technology is generally adopted

For every student who writes a network program, asynchronous connect should be the basic skill that must be mastered

Asynchronous connect steps

(1) establish socket,call fcntl The function sets it to non blocking 

(2) call connect Function, returning 0 indicates that the connection is successful, and -1,Need to check the error code

    If the error code is EINPROGRESS,Indicates that a connection is being established
    
    If the error code is EINTR Indicates that a system interrupt has occurred. At this time, you can continue to execute the connection
    
    If it is another error code, call close(fd) Function close socket, connection failed

(3) take socket join select/poll And set the timeout

(4) judge select/poll Return value of function
    
    Less than or equal to 0 indicates failure
    
    Others, indicating socket Writable, call getsockopt Function capture socket Error message for
            

The specific codes are as follows:

/*
    Asynchronous connect test code, test_connect.cpp
*/
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <poll.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <poll.h>
#include <limits.h>
#include <iostream>
using namespace std;

int32_t main(int32_t argc, char *argv[])
{
    if(argc < 3)
    {
        std::cout << "argc < 3..." << std::endl;
        return -1;
    }
    std::string strip = argv[1];
    uint32_t port = atoi(argv[2]);
    //Create socket
    int32_t fd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == fd)
    {
        std::cout << "create socket error:" << errno << std::endl;
        return -1;
    }
    //Set socket to non blocking
    int32_t flag = fcntl(fd, F_GETFL, 0);
    flag |= O_NONBLOCK;
    if(-1 == fcntl(fd, F_SETFL, flag))
    {
        std::cout << " set socket nonblock error:" << errno << std::endl;
        close(fd);
        return -1;
    }
    //server address
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(strip.c_str());
    //
    for(; ;)
    {
        //Connect server
        int32_t ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr) );
        if(-1 == ret)
        {
            int32_t err = errno;
            if(EINTR == err)
            {
                //connect was interrupted. Continue to retry
                //If EINTR errors are not handled, the connect logic may not be put into the for loop
                continue;
            }
            if(EINPROGRESS != err)
            {
                std::cout << "connect err:" << errno << ", str:" << strerror(errno) <<  std::endl;
                goto exit;
            }
            //Connecting
            std::cout << "connecting..." << std::endl;
            //Processing results
            int32_t result = -1;
    #if 1
            //Add socket to writable set of poll
            struct pollfd wfd[1];
            wfd[0].fd = fd;
            wfd[0].events = POLLOUT;
            //Check whether the socket is writable
            result = poll(wfd, 1, 3000);
    #elif 0
            //Set timeout
            struct timeval tval;
            tval.tv_sec = 3;
            tval.tv_usec = 0;
            //Add socket to the writable set of select
            fd_set wfds;
            FD_ZERO(&wfds);
            FD_SET(fd,&wfds);
            //Check whether the socket is writable
            result = select(fd + 1, nullptr, &wfds, nullptr,&tval);
    #endif
            std::cout << "async connect result:" << result << std::endl;
            // fail
            if(result <= 0 )
            { 
                std::cout << "async connect err:" << errno << ", str:" << strerror(errno) << std::endl;
                goto exit;
            }
            //Check socket error information
            int32_t temperr = 0;
            socklen_t temperrlen = sizeof(temperr);
            if(-1 == getsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&temperr, &temperrlen) )
            {
                std::cout << "async connect...getsockopt err:" << errno << ", str:" << strerror(errno) <<  std::endl;
                goto exit;
            }
            if(0 != temperr)
            {
                std::cout << "async connect...getsockopt temperr:" << temperr << ", str:" << strerror(temperr) << std::endl;
                goto exit;
            }
            //success
            std::cout << "async connect success..." << std::endl;
            goto exit;
        }
        else
        {
             //Connection succeeded
            std::cout << "connect success..." << std::endl;
            goto exit;          
        }
    } // end of  for(; ;)
exit:
    std::cout << "quit...." << std::endl;
    close(fd);
    return 0;
}

  • Code description

If the EINTR error is not handled, the connect function and the following logic can not be put into the for loop

Check whether the socket is writable by calling select or poll function. The poll function is used in the above code. Change #if 1 to #if 0 and #elif 0 to #elif 1 in the code, that is, use the select function to check whether the socket is writable

test

Execute the nc -l -v -k 192.168.70.20 5000 command on another machine to start a server program

Execute G + + - G - wall - STD = C + + 11 - O test on the current machine_ connect test_ Compile with connect.cpp

Execute. / test_connect 192.168.70.20 5000, and the results are shown in the figure below

At this time, the server program is displayed as follows:

Pass test_ The screenshot of the connect program can be seen that after calling the connect function, the EINPROGRESS error code is returned, then the select/poll function is returned to 1, indicating that socket is writable, and then the getsockopt function is called to check the socket error information. By printing the information, socket knows no error information, that is, the connection is successful.

We press CTRL + C on the server machine to stop the server program, and then close test_connect program and execute. / test again_ Connect 192.168.70.20 5000, and the results are as follows:

As can be seen from the above figure, even if the server program has exited, the socket is returned writable after calling select/poll. When you continue to call getsockopt function to check the socket error code, the error code is 111, indicating that the connection is rejected, that is, the connection fails



A very important point to note here is that on Linux, even if the socket is not connected successfully, when you call select/poll, it still returns that the socket is writable. Therefore, in addition to calling select/poll to check that the socket is writable, you also need to call getsockopt function to check the error code of the socket. An error code of 0 indicates that the connection is successful, and others indicate that the connection fails

Topics: network