非阻塞connect简介及代码示例

1.概述

套接字默认状态是阻塞的,意思就是一旦发起某个套接字调用时,在操作没有完成之前,进程将处于等待睡眠状态。
这些套接字调用主要分为四类:

  • 输入操作:read、readv、recv、recvfrom和recvmsg
  • 输出操作:write、writev、send、sendto和sendmsg
  • 接收外部连接:accpet
  • 发起外部连接:connect
    connect是本文重点要讲述的。建立一个TCP连接,首先要经过三次握手,connect要等到收到发出的SYN对应的ACK才能返回。也就是说建立连接至少要用一个RTT的时间,多则数秒。非阻塞的connect则不同,如果连接不能立即建立吗,connect会返回一个EINPROGRESS的错误,这时候三次握手还在进行中,可以使用select来检测这个连接是否建立。总结下,非阻塞的connect有三个用途:
  1. 不必阻塞再connect上,可以利用这段时间时间来执行其他任务。
  2. 可以同时建立多个连接,例如浏览器这类客户端程序。
  3. 使用select可以灵活的设定时间限制,能够缩短connect的超时。
2. 示例1

实现一个非阻塞的connect主要分5步

  1. 设置套接字为非阻塞,并保留文件描述符状态,供第5步恢复使用
  2. 检查连接是否立即建立,如果connect返回为0,则表示连接已经建立, 若在进行中则返回EINPROGRESS,否则出错。这里特别说明一点,被中断的connect不能重启。
  3. 调用select, 可设置超时时间。当连接成功时,描述符可写;失败时,描述符可读可写,所以这里简化了下只监听了可写事件。如果select为0,则表示超时,会返回一个ETIMEOUT的错误给用户。这里的select也可以用poll或epoll来替代,以poll为例。
    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. 检查可读可写,若连接成功,error置为错误,(Berkeley和Solaris中error都置错,getsockopt一个返回0,一个返回-1)。getsockopt是比较简单的方法但不是唯一,其他方法还有
  • connect直到返回EISCONN。(LINE81-94)。
  • 调用read读取长度为0的数据,如果read失败,connect已经失败。如果成功,read返回0
  • 使用getpeername替代getsockopt。如果返回错误ENOTCONN则失败,不过接下来还是得以SO_ERROR参数调用getsockopt获取套接字上的待处理错误。
getpeername(connfd, (struct sockaddr *)&add, &len); //获取对端地址
  1. 恢复套接字的文件状态并返回

代码如下

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. 测试代码
#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;
}

附带错误码表

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

猜你喜欢

转载自blog.csdn.net/niu91/article/details/114679755