我们知道,如果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);
}
}