返回> 网站首页 

[转载]linux下connection非阻塞socket使用

yoours2015-06-23 18:46:28 阅读 1706

简介一边听听音乐,一边写写文章。

用select可以很好地解决这一问题.大致过程是这样的: 

1.将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完成(有的系统用FNEDLAY也可). 
2.发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧在进行还没有完成. 
3.将打开的socket设进被监视的可写(注意不是可读)文件集合用select进行监视;
如果可写,用getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int));来得到error的值,
如果为零,则connect成功. 

缺省状态下的套接字都是阻塞方式的,这意味着一个套接口的调用不能立即完成时,进程将进入睡眠状态,并等待操作完成。对于某些应用,需要及时可控的客户响应,而阻塞的方式可能会导致一个较长的时间段内,连接没有响应。造成套接字阻塞的操作主要有recv, send, accept, connect.
       下面以connect为例,讲讲非阻塞的connect的工作原理。当一个TCP套接字设置为非阻塞后,调用connect,会立刻返回一个EINPROCESS的错误。但TCP的三路握手继续进行,我们将用select函数检查这个连接是否建立成功。建立非阻塞的connect有下面三个用途:
1.       可以在系统做三路握手的时候做些其它事情,这段时间你可以为所欲为。
2.       可以用这个技术同时建立多个连接,在web应用中很普遍。
3.       可以缩短connect的超时时间,多数实现中,connect的超时在75秒到几分钟之间!
虽然非阻塞的conncet实现起来并不复杂,但我们必须注意以下的细节:
         即使套接字是非阻塞的,如果连接的服务器是在同一台主机,connect通常会立刻建立。(connect 返回 0 而不是 EINPROCESS)
         当连接成功建立时,描述字变成可写
         当连接出错时,描述字变成可读可写
 例程:定义一个非阻塞的 connect 函数 connect_nonb
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;
       // 获取当前socket的属性, 并设置 noblocking 属性
       flags = fcntl(sockfd, F_GETFL, 0);
       fcntl(sockfd, F_SETFL, flags | O_NOBLOCK);
       errno = 0;
       if ( (n = connect(sockfd, saptr, salen)) < 0)
              if (errno != EINPROGRESS)
                     return (-1);
       // 可以做任何其它的操作
       if (n == 0)
              goto done; // 一般是同一台主机调用,会返回 0
       FD_ZERO(&rset);
       FD_SET(sockfd, &rset);
       wset = rset;  // 这里会做 block copy
       tval.tv_sec = nsec;
       tval.tv_usec = 0;
       // 如果nsec 为0,将使用缺省的超时时间,即其结构指针为 NULL
       // 如果tval结构中的时间为0,表示不做任何等待,立刻返回
       if ((n = select(sockfd+1, &rset, &west, NULL,nsec ?tval:NULL)) == 0) {
              close(sockfd);
              errno = ETIMEOUT;
              return (-1);
              }
              if(FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &west)) {
                     len = sizeof(error);
                     // 如果连接成功,此调用返回 0
                     if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
                            return (-1);
       }
       else err_quit(“select error: sockfd  not set”);
 done:
       fcntl(sockfd, F_SETFL, flags); // 恢复socket 属性
       if (error) {
              close(sockfd);
              errno = error;
              return (-1);
       }
       Return (0);
}
 
注意事项:
         如果select调用之前,连接已经建立成功,并且有数据发送过来了,这时套接字将是即可读又可写,和连接失败时是一样的。所以我们必须用getsockopt来检查套接字的状态。
         如果我们不能确定套接字可写是成功的唯一情况时,我们可以采用以下的调用
(1)           调用getpeername,如果调用失败,返回ENOTCONN,表示连接失败
(2)           调用read,长度参数为0,如果read失败,表示connect失败。
(3)           再调用connect一次,其应该失败,如果错误是EISCONN,表示套接字已建立而且连接成功。
         如果在一个阻塞的套接字上调用的connect,在TCP三路握手前被中断,如果connect不被自动重启,会返回EINTR。但是我们不能调用connect等待连接完成,这样会返回EADDRINUSE,此时我们必须调用select,和非阻塞的方式一样。

注:
有一个非常有迷惑性的做法是:
u_long has = 1;
ioctl(m_sock, FIONBIO , &has);
这个函数会非常无耻的返回你success,但是它实际上很可能什么也没做。

正确的做法应该是使用fcntl:
int flags = fcntl(m_sock, F_GETFL, 0);
fcntl(m_sock, F_SETFL, flags|O_NONBLOCK);

这真是一个隐蔽的问题,折腾了我两天。线程每每停留在send()调用那里,我始终没怀疑到:用ioctl设置FIONBIO成功之后,socket竟然还是阻塞的。
微信小程序扫码登陆

文章评论

1706人参与,0条评论