ノンブロッキング接続とコード例の紹介

1。概要

ソケットのデフォルト状態はブロッキングです。これは、ソケット呼び出しが開始されると、操作が完了する前にプロセスが待機スリープ状態になることを意味します。
これらのソケット呼び出しは、主に4つのカテゴリに分類されます。

  • 入力操作:read、readv、recv、recvfrom、およびrecvmsg
  • 出力操作:write、writev、send、sendto、sendmsg
  • 外部接続の受信:accpet
  • 外部接続を開始します。connectconnect
    がこの記事の焦点です。TCP接続を確立するには、最初に3ウェイハンドシェイクを実行する必要があり、送信されたSYNに対応するACKを受信するまで接続は戻りません。つまり、接続の確立には少なくとも1つのRTT時間がかかり、これには数秒かかる場合があります。ノンブロッキング接続は異なります。接続をすぐに確立できない場合、接続はEINPROGRESSエラーを返します。このとき、スリーウェイハンドシェイクはまだ進行中です。selectを使用して、接続が確立されているかどうかを確認できます。要約すると、非ブロッキング接続には3つの目的があります。
  1. ブロックして再度接続する必要はありません。この時間を使用して、他のタスクを実行できます。
  2. ブラウザなどのクライアントプログラムなど、複数の接続を同時に確立できます。
  3. selectを使用して、制限時間を柔軟に設定し、接続のタイムアウトを短縮します。
2.例1

ノンブロッキング接続の実現は、主に5つのステップに分かれています。

  1. ソケットを非ブロッキングに設定し、手順5で回復するためにファイル記述子の状態を保持します
  2. 接続がすぐに確立されるかどうかを確認します。connectが0を返す場合は接続が確立されたことを意味し、進行中の場合はEINPROGRESSを返し、そうでない場合はエラーが発生します。ここに特別な点があります。中断された接続は再開できません。
  3. selectを呼び出して、タイムアウト期間を設定します。接続が成功すると、記述子は書き込み可能になります。失敗すると、記述子は読み取りと書き込みが可能になるため、ここでは、書き込み可能なイベントのみが監視されるように簡略化されています。selectが0の場合、タイムアウトを意味し、ETIMEOUTエラーがユーザーに返されます。ここでの選択は、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. チェックは読み取りと書き込みが可能です。接続が成功すると、エラーはエラーとして設定されます(BerkeleyとSolarisのエラーはエラーとして設定され、一方のgetsockoptは0を返し、もう一方は-1を返します)。Getsockoptは比較的単純な方法ですが、唯一の方法ではありません。他の方法もあります。
  • EISCONNに戻るまで接続します。(LINE81-94)。
  • readを呼び出して、長さが0のデータを読み取ります。読み取りが失敗した場合、接続は失敗しています。成功した場合、読み取りは0を返します
  • getockoptの代わりにgetpeernameを使用します。エラー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