Introduction to non-blocking connect and code examples

1 Overview

The default state of the socket is blocking, which means that once a socket call is initiated, the process will be in a waiting sleep state before the operation is completed.
These socket calls are mainly divided into four categories:

  • Input operations: read, readv, recv, recvfrom, and recvmsg
  • Output operations: write, writev, send, sendto and sendmsg
  • Receiving external connections: accpet
  • Initiate an external connection: connect
    connect is the focus of this article. To establish a TCP connection, it must go through a three-way handshake first, and connect will not return until it receives the ACK corresponding to the SYN sent. That is to say, it takes at least one RTT time to establish a connection, which can be several seconds. Non-blocking connect is different. If the connection cannot be established immediately, connect will return an EINPROGRESS error. At this time, the three-way handshake is still in progress. You can use select to check whether the connection is established. In summary, non-blocking connect has three purposes:
  1. You don't have to block and connect again, you can use this time to perform other tasks.
  2. Multiple connections can be established at the same time, such as client programs such as browsers.
  3. Use select to set the time limit flexibly and shorten the timeout of connect.
2. Example 1

The realization of a non-blocking connect is mainly divided into 5 steps

  1. Set the socket to be non-blocking and retain the file descriptor state for the recovery in step 5
  2. Check whether the connection is established immediately, if connect returns 0, it means the connection has been established, if it is in progress, it returns EINPROGRESS, otherwise an error occurs. Here is a special point, the interrupted connect cannot be restarted.
  3. Call select to set the timeout period. When the connection is successful, the descriptor is writable; when it fails, the descriptor is readable and writable, so it is simplified here that only writable events are monitored. If select is 0, it means timeout and an ETIMEOUT error will be returned to the user. The select here can also be replaced by poll or epoll, take poll as an example.
    struct pollfd pfd;
    pfd.fd = fd;
    pfd.events = POLLOUT | POLLERR;

    ret = poll(&pfd, 1, timeout * 1000);
    if (ret == 1 && pfd.revents == POLLOUT) {
    
    
        cout << "connect successfully" << endl;
        goto END;
    }
  1. The check is readable and writable. If the connection is successful, the error is set as an error (error in Berkeley and Solaris is set as an error, one getsockopt returns 0 and the other returns -1). getsockopt is a relatively simple method but not the only method, there are other methods
  • Connect until it returns to EISCONN. (LINE81-94).
  • Call read to read data with a length of 0. If the read fails, the connect has failed. If successful, read returns 0
  • Use getpeername instead of getsockopt. If it returns an error ENOTCONN, it fails, but then the SO_ERROR parameter calls getsockopt to get the pending error on the socket.
getpeername(connfd, (struct sockaddr *)&add, &len); //获取对端地址
  1. Restore the file state of the socket and return

code show as below

bool connect_nonblock(int fd, struct sockaddr_in srvAddr, int timeout = 3) {
    
    
    timeval tv = {
    
    timeout, 0};
    int ret = 0;

    // 1. 设置套接字为非阻塞
    int flag = fcntl(fd, F_GETFL, 0);
    if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) == -1) {
    
    
        cout << "fcntl failed" << endl;
        goto END;
    }

    // 2. 检查连接是否立即建立
    ret = connect(fd, (sockaddr*)&srvAddr, sizeof(struct sockaddr_in));
    if (ret == 0) {
    
    
        cout << "connect successfully" << endl;
        goto END;
    } else if (errno == EINTR) {
    
    
        cout << "signal interrupt" << endl;
        goto END;
    } else if (errno != EINPROGRESS) {
    
    
        cout << "can not connect to server, errno: " << errno << endl;
        goto END;
    } 

    // 3. 调用select, 可设置超时
    fd_set wfds;
    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);

#if 1
    if (select(fd + 1, nullptr, &wfds, nullptr, &tv) <= 0) {
    
    
        cout << "can not connect to server" << endl;
        goto END;
    }

    // 4. 检查可读可写,若连接成功,getsockopt返回0
    if (FD_ISSET(fd, &wfds)) {
    
    
        int error;
        socklen_t error_len = sizeof(int);
        ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &error_len);
        if (ret < 0 || error != 0) {
    
    
            cout << "getsockopt connect failed, errno: " << error << endl;
            goto END;
        }
        cout << "connect successfully" << endl;
    }
#else
    while (1) {
    
    
        ret = select(fd + 1, nullptr, &wfds, nullptr, &tv);
        if (ret <= 0) {
    
    
            cout << "can not connect to server" << endl;
            goto END;
        }

        if (FD_ISSET(fd, &wfds)) {
    
    
            ret = connect(fd, (sockaddr*)&addr, sizeof(sockaddr_in));
            if (errno == EISCONN) {
    
    
                cout << "connect successfully" << endl;
                goto END;
            } else {
    
    
                cout << "continue , errno:" << errno << endl;
            }
        }
    }
#endif

    // 5. 恢复套接字的文件状态并返回
END:
    fcntl(fd, F_SETFL, flag);
    if (ret != 0) {
    
    
        cout << "can not connect to server, errno: " << errno << endl;
    }
    return ret == 0;
}
3. Test code
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>

using namespace std;
bool connect_nonblock(int fd, struct sockaddr_in srvAddr, int timeout = 3);

int main() {
    
    
    // 1. 建立套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
    
    
        cout << "create socket failed" << endl;
        return false;
    }

    // 2. 连接
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(9090);
    addr.sin_family = AF_INET;

    bool ret = nonblockingConnect(fd, addr);
    if (!ret) {
    
    
        return -1;
    }

    // 3. 测试收发数据
    while (1) {
    
    
        int send_len = 0;
        const char buf[] = "hello\n";

        if ((send_len = send(fd, buf, sizeof(buf), 0)) == -1) {
    
    
            cout << "send failed";
            return false;
        }
        cout << "send len: " << send_len << endl;
        sleep(3);
    }

    // 4. 关闭
    close(fd);
    return 0;
}

Attached error code table

124 EMEDIUMTYPEWrong medium type
123 ENOMEDIUM_No medium found
122 EDQUOT__Disk quota exceeded
121 EREMOTEIO_Remote I/O error
120 EISNAM__Is a named type file
119 ENAVAIL__No XENIX semaphores available
118 ENOTNAM__Not a XENIX named type file
117 EUCLEAN__Structure needs cleaning
116 ESTALE__Stale NFS file handle
115 EINPROGRESS  +Operation now in progress
114 EALREADY_Operation already in progress
113 EHOSTUNREACH  No route to host
112 EHOSTDOWN_Host is down
111 ECONNREFUSED  Connection refused
110 ETIMEDOUT+Connection timed out
109 ETOOMANYREFS  Too many references: cannot splice
108 ESHUTDOWN_Cannot send after transport endpoint shutdown
107 ENOTCONN_Transport endpoint is not connected
106 EISCONN__Transport endpoint is already connected
105 ENOBUFS__No buffer space available
104 ECONNRESETConnection reset by peer
103 ECONNABORTED  Software caused connection abort
102 ENETRESET_Network dropped connection on reset
101 ENETUNREACHNetwork is unreachable
100 ENETDOWN_Network is down
99 EADDRNOTAVAIL Cannot assign requested address
98 EADDRINUSEAddress already in use
97 EAFNOSUPPORT  Address family not supported by protocol
96 EPFNOSUPPORT  Protocol family not supported
95 EOPNOTSUPPOperation not supported
94 ESOCKTNOSUPPORT Socket type not supported
93 EPROTONOSUPPORT Protocol not supported
92 ENOPROTOOPTProtocol not available
91 EPROTOTYPEProtocol wrong type for socket
90 EMSGSIZE_+Message too long
89 EDESTADDRREQ  Destination address required
88 ENOTSOCK_Socket operation on non-socket
87 EUSERS__Too many users
86 ESTRPIPE_Streams pipe error
85 ERESTART_Interrupted system call should be restarted
84 EILSEQ__Invalid or incomplete multibyte or wide character
83 ELIBEXEC_Cannot exec a shared library directly
82 ELIBMAX__Attempting to link in too many shared libraries
81 ELIBSCN__.lib section in a.out corrupted
80 ELIBBAD__Accessing a corrupted shared library
79 ELIBACC__Can not access a needed shared library
78 EREMCHG__Remote address changed
77 EBADFD__File descriptor in bad state
76 ENOTUNIQ_Name not unique on network
75 EOVERFLOW_Value too large for defined data type
74 EBADMSG_+Bad message
73 EDOTDOT__RFS specific error
72 EMULTIHOP_Multihop attempted
71 EPROTO__Protocol error
70 ECOMM___Communication error on send
69 ESRMNT__Srmount error
68 EADV___Advertise error
67 ENOLINK__Link has been severed
66 EREMOTE__Object is remote
65 ENOPKG__Package not installed
64 ENONET__Machine is not on the network
63 ENOSR___Out of streams resources
62 ETIME___Timer expired
61 ENODATA__No data available
60 ENOSTR__Device not a stream
59 EBFONT__Bad font file format
57 EBADSLT__Invalid slot
56 EBADRQC__Invalid request code
55 ENOANO__No anode
54 EXFULL__Exchange full
53 EBADR___Invalid request descriptor
52 EBADE___Invalid exchange
51 EL2HLT__Level 2 halted
50 ENOCSI__No CSI structure available
49 EUNATCH__Protocol driver not attached
48 ELNRNG__Link number out of range
47 EL3RST__Level 3 reset
46 EL3HLT__Level 3 halted
45 EL2NSYNC_Level 2 not synchronized
44 ECHRNG__Channel number out of range
43 EIDRM___Identifier removed
42 ENOMSG__No message of desired type
40 ELOOP___Too many levels of symbolic links
39 ENOTEMPTY+Directory not empty
38 ENOSYS__+Function not implemented
37 ENOLCK__+No locks available
36 ENAMETOOLONG +File name too long
35 EDEADLK_+Resource deadlock avoided
34 ERANGE__+Numerical result out of range
33 EDOM___+Numerical argument out of domain
32 EPIPE__+Broken pipe
31 EMLINK__+Too many links
30 EROFS__+Read-only file system
29 ESPIPE__+Illegal seek
28 ENOSPC__+No space left on device
27 EFBIG__+File too large
26 ETXTBSY__Text file busy
25 ENOTTY__+Inappropriate ioctl for device
24 EMFILE__+Too many open files
23 ENFILE__+Too many open files in system
22 EINVAL__+Invalid argument
21 EISDIR__+Is a directory
20 ENOTDIR_+Not a directory
19 ENODEV__+No such device
18 EXDEV__+Invalid cross-device link
17 EEXIST__+File exists
16 EBUSY__+Device or resource busy
15 ENOTBLK__Block device required
14 EFAULT__+Bad address
13 EACCES__+Permission denied
12 ENOMEM__+Cannot allocate memory
11 EAGAIN__+Resource temporarily unavailable
10 ECHILD__+No child processes
9 EBADF__+Bad file descriptor
8 ENOEXEC_+Exec format error
7 E2BIG__+Argument list too long
6 ENXIO__+No such device or address
5 EIO___+Input/output error
4 EINTR__+Interrupted system call
3 ESRCH__+No such process
2 ENOENT__+No such file or directory
1 EPERM__+Operation not permitted

Guess you like

Origin blog.csdn.net/niu91/article/details/114679755