Linux 非阻塞connect

21 篇文章 0 订阅

套接字执行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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值