我们知道,如果socket为TCP套接字,则connect函数会激发TCP的三次握手过程,而三次握手是需要一些时间的,内核中对connect的超时限制是75秒,就是说如果超过75秒则connect会由于超时而返回失败。但是如果对端服务器由于某些问题无法连接,那么每一个客户端发起的connect都会要等待75才会返回,因为socket默认是阻塞的。对于一些线上服务来说,假设某些对端服务器出问题了,在这种情况下就有可能引发严重的后果。或者在有些时候,我们不希望在调用connect的时候阻塞住,有一些额外的任务需要处理。

这种场景下,我们就可以将socket设置为非阻塞,如下代码:

int flags = fcntl(c_fd, F_GETFL, 0);
if(flags < 0) {
    return 0;
}
fcntl(c_fd, F_SETFL, flags | O_NONBLOCK);

当我们将socket设置为NONBLOCK后,在调用connect的时候,如果操作不能马上完成,那connect便会立即返回,此时connect有可能返回-1, 此时需要根据相应的错误码errno,来判断连接是否在继续进行。比较完整的做法如下:

    int sockfd = sockets::createNonblockingOrDie(_serverAddr.family());
    int ret = sockets::connect(sockfd, _serverAddr.getSockAddr());
    int savedErrno = (ret == 0) ? 0 : errno;
    switch (savedErrno) {
    case 0:
    case EINPROGRESS:
    case EINTR:
    case EISCONN:
        connecting(sockfd);
        break;
    case EAGAIN:
    case EADDRINUSE:
    case EADDRNOTAVAIL:
    case ECONNREFUSED:
    case ENETUNREACH:
        retry(sockfd);
        break;
    case EACCES:
    case EPERM:
    case EAFNOSUPPORT:
    case EALREADY:
    case EBADF:
    case EFAULT:
    case ENOTSOCK:
        LOG_ERROR << "connect error in Connector::startInLoop " << savedErrno;
        sockets::close(sockfd);
        break;
    default:
        LOG_ERROR << "Unexpected error in Connector::startInLoop " << savedErrno;
        sockets::close(sockfd);
        break;
    }

使用非阻塞 connect 需要注意的问题是:1. 很可能 调用 connect 时会立即建立连接(比如,客户端和服务端在同一台机子上),必须处理这种情况。2. Posix 定义了两条与 select 和 非阻塞 connect 相关的规定:- 连接成功建立时,socket 描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)- 连接建立失败时,socket 描述字既可读又可写。 (由于有未决的错误,从而可读又可写)具体代码如下:

void Connector::handleWrite() {
    if (_state == CONNECTING) {
        int sockfd = removeAndResetChannel();
        int err = sockets::getSocketError(sockfd);
        if (err) {
            LOG_WARN << "Connector::handleWrite - SO_ERROR = "
                << err << " " << strerror_tl(err);
            retry(sockfd);
        } else if (sockets::isSelfConnect(sockfd)) {
            LOG_WARN << "Connector::handleWrite - Self connect";
            retry(sockfd);
        } else {
             setState(CONNECTED);
             if (_connected) {
                _newConnectionCallback(sockfd);
             } else {
                 sockets::close(sockfd);
             }
        }
    } else {
        assert(_state == DISCONNECTED);
    }
}

原文:大专栏  非阻塞socket调用connect


01-23 15:28