套接字状态默认为阻塞的。对于非阻塞的套接字,如果相应的操作没有满足,就会立即返回EWOULDBLOCK错误,使用比较多即TCP的connect函数。
阻塞的connect要等到3次握手完成才能返回,因此要完成阻塞的connect,需要一个RTT时间,这个RTT时间波动比较大,在局域网中还好,如果在广域网中可能达到几秒,我们可以再这段时间内继续建立其他链接,或者处理其他工作。这就要使用非阻塞connect。
非阻塞的connec三个用途:
1. 在三次的握手的期间,我们可以处理其他的工作。
2. 我们可以同时建立多个连接,浏览器就是通过这样发起多个连接。
3. 可以通过select,缩短connect的超时时间。
connect是非阻塞的,那我们如何处理连接错误呢?需要处理的细节:
1. POSIX有关于select和非阻塞connect有两个规则;
(1)当连接成功时,描述符变为可写;
(2)当连接出错时,描述符变为可写可读。
2. 如果我们在调用select监听connect套接字时,如果套接字已经连接,那也是可写可读的状态,和连接出错一样的状态,所以要处理在select之前套接字已经连接的情况(一般只会出现在客户端和服务器端在同一台机器上)。
一下是UNP上的非阻塞connect,能够很好的看出,如何解决非阻塞情况下一些问题。
int connect_nonb(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //将套接字设置为非阻塞
error = 0;
if ( (n = connect(sockfd, saptr, salen)) < 0)
if (errno != EINPROGRESS) //期望的错误为EINPROGRESS,如果不是该错误,那连接肯定出错了。
return(-1);
/* Do whatever we want while the connect is taking place. */
if (n == 0)
goto done; //处理在select之前,连接就已经建立的情况。
//设置select所要监听的套接字
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = Select(sockfd+1, &rset, &wset, NULL,
nsec ? &tval : NULL)) == 0) {
close(sockfd); //如果超时,select返回0,那就关闭套接字,设置error为ETIMEOUT
errno = ETIMEDOUT;
return(-1);
}
//如果套接子可读或者可写,需要检查套接字带处理的错误,这里有一个移植性的问题,getsockopt根据不同的实现会返回0或者-1
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
//Solaris返回-1,并把error置为待处理错误
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return(-1); /* Solaris pending error */
} else
//返回0,基于Berkeley
err_quit("select error: sockfd not set");
done:
Fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
//正确返回
return(0);
}