红联Linux门户
Linux帮助

Linux非阻塞connect

发布时间:2016-08-19 16:47:17来源:linux网站作者:詆調
套接字执行I/O操作有阻塞和非阻塞两种模式。在阻塞模式下,在I/O操作完成前,执行操作的函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回-1,而不管I/O是否完成,该函数所在的线程会继续运行。
客户端调用connect()发起对服务端的socket连接,如果客户端的socket描述符为阻塞模式,则connect()会阻塞到连接建立成功或连接建立超时(linux内核中对connect的超时时间限制是75s, Soliris 9是几分钟,因此通常认为是75s到几分钟不等)。如果为非阻塞模式,则调用connect()后函数立即返回,如果连接不能马上建立成功(返回-1),则errno设置为EINPROGRESS,此时TCP三次握手仍在继续。此时可以调用select()检测非阻塞connect是否完成。select指定的超时时间可以比connect的超时时间短,因此可以防止连接线程长时间阻塞在connect处。
 
select 判断规则:
1)如果 select 返回 -1,表示 select 出错,可以关闭 socket 套接字,重新发起连接过程;
2)如果 select 返回 0,表示在 select 超时,超时时间内未能成功建立连接,也可以再次执行 select 进行检测,如若多次超时,需返回超时错误给用户;
3)如果 select 返回大于 0 的值,则说明检测到可读或可写的套接字描述符。源自 Berkeley 的实现有两条与 select 和非阻塞 I/O 相关的规则:
当套接字连接建立成功时,套接口描述符变成 可写(连接建立时,写缓冲区空闲,所以可写);
当套接字连接建立出错时,套接口描述符变成 既可读又可写(由于有未决的错误,从而可读又可写);
因此,当发现套接口描述符可读或可写时,可进一步判断是连接成功还是出错。这里必须将 第二条规则和另外一种连接正常的情况区分开,就是连接建立好了之后,服务器端发送了数据给客户端,此时 select 同样会返回非阻塞 socket 描述符既可读又可写。
对于 Unix 环境,可通过调用 getsockopt 来检测描述符集合是连接成功还是出错,但是该方法在 Linux 环境上测试是无效的。因为在 Linux 下无论网络是否发生错误,getsockopt 始终返回 0,不返回-1。若采用 getsockopt 来检查:
如果连接建立是成功的,则通过 getsockopt(sockfd,SOL_SOCKET,SO_ERROR, &error,&len) 获取的 error 值将是 0;
如果建立连接时遇到错误,则 errno 的值是连接错误所对应的 errno 值,比如ECONNREFUSED,ETIMEDOUT 等;
在 Linux 环境下可以使用以下方法进行测试连接是成功还是出错:再次调用connect,相应返回失败,如果错误 errno 是EISCONN,表示 socket 连接已经建立,否则认为连接失败。即在一次 select 调用之后,若发现此时套接口描述字可读或可写,则再次执行 connect 调用,此时 errno 始终仍为 EINPROGRESS,则再次执行 select 和 connect 函数,直到 errno 被置为EISCONN,表示 connect 成功。
 
非阻塞 connect 编程步骤:
第一步:调用 socket 创建套接字,并使用 fcntl 函数使该套接字变为非阻塞式;
第二步:调用 connect 函数请求建立连接,并判断连接是否成功建立;
若 connect 调用返回 0,则表示连接请求成功建立;
若 connect 调用返回 -1,首先检查 errno 错误类型,若不为 EINPROGRESS 错误,则直接退出,否则只是当前连接不能立即建立,但是已经发起的 TCP 连接请求三次握手过程会继续进行,此时调用 select 函数判断连接是否建立成功:
若 select 调用返回 0,则表示 select 超时期限内不能成功建立连接,则此时返回一个超时错误,且关闭该链接,以防止 TCP 连接的三次握手过程继续进行;
若 select 调用返回正值,则表示在超时期限内检查到套接字可读或可写或异常,若可读或可写,在 Unix 系统中,此时通过调用 getsockopt 函数检查连接状态,若连接成功,则该值为 0,若连接建立发生错误,则该值是对应连接错误的 errno 值;
假设在调用 select 函数之前连接已经建立,并服务器发送的数据已到达客户端,此时非阻塞套接字处于即可读又可写状态。然而由 select 函数调用返回大于 0 值时,使用 getsockopt 检查到连接出错时,非阻塞套接字也是 既可读又可写。这样就会导致移植性问题,我们可以使用下列方法代替 getsockopt 调用:
调用 getpeername 代替 getsockopt。若 getpeername 以 ENOTCONN 错误失败返回,则表示连接建立失败,紧接着必须以 SO_ERROR 调用 getsockopt 取得套接字上待处理的错误;
以值为 0 的长度参数调用 read 函数。若 read 调用失败,表示 connect 连接失败,read 返回的 errno 给出连接失败的原因,若连接建立成功,则 read 返回 0;
再一次调用 connect 函数。如果返回错误 EISCONN,表示套接字已经连接,即连接建立成功;
 
示例
//非阻塞connect连接
int tcp_conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
int flags, error;
int ret = -1;
fd_set rset,wset;
struct timeval tval;
//设置非阻塞
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
errno = 0;
ret = connect(sockfd,saptr,salen);
if(ret != 0)
{
if(errno != EINPROGRESS)
{
LOG_PRINT("%s\n",strerror(errno));
}
else
{
int res;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
//如果nsec为0,将使用缺省的超时时间,即其结构指针为NULL
//如果过tval结构中的时间为0,表示不做任何等待,立即返回
res = select(sockfd+1, &rset, &wset,NULL, nsec ? &tval : NULL);
if (res == 0)   //超时
{
LOG_PRINT("%s\n",strerror(errno));
}
else if( res < 0 )
{
 LOG_PRINT("%s\n",strerror(errno));
}
else if( res == 1)
{
if(FD_ISSET(sockfd,&wset)||FD_ISSET(sockfd,&rset))
{
len = sizeof(error);
res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
LOG_PRINT("getsockopt %d %d\n",res,error);
if(error == 0)
ret = 0;
else
ret = -1;
}
}
else
{
LOG_PRINT("%s",strerror(errno));
}
}
}   
fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */
return ret;
}
 
本文永久更新地址://m.ajphoenix.com/linux/23451.html