非阻塞connect的作用及代码示例

connect 函数的调用涉及到TCP连接的三次握手过程,通常阻塞的connect 函数会等待三次握手成功或失败后返回,0成功,-1失败。如果对方未响应,要隔6s,重发尝试,可能要等待75s的尝试并最终返回超时,才得知连接失败。即使是一次尝试成功,也会等待几毫秒到几秒的时间,如果此期间有其他事务要处理,则会白白浪费时间,而用非阻塞的connect 则可以做到并行,提高效率。

而通常,非阻塞的connect 函数与 select 函数配合使用:在一个TCP套接口被设置为非阻塞之后调用connect,connect (函数本身返回-1)会立即返回EINPROGRESS或EWOULDBLOCK错误,表示连接操作正在进行中,但是仍未完成;同时TCP的三路握手操作继续进行;在这之后,我们可以调用select来检查这个链接是否建立成功。

若建立连接成功,select的结果中该描述符可写;若失败,则可写可读,此时可以使用getsockopt获取错误信息。

正常的三次握手时序

在这里插入图片描述

(以下内容转自http://blog.163.com/li_xiang1102/blog/static/60714076201110298170975/)

非阻塞connect有三种用途

  • 我们可以在三路握手的同时做一些其它的处理。connect 操作要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网,在这段时间内我们可能有一些其他的处理想要执行;
  • 可以用这种技术同时建立多个连接.在Web浏览器中很普遍;
  • 由于我们使用select 来等待连接的完成,因此我们可以给select设置一个时间限制,从而缩短connect 的超时时间。在大多数实现中,connect 的超时时间在75秒到几分钟之间。有时候应用程序想要一个更短的超时时间,使用非阻塞connect 就是一种方法。

非阻塞connect 听起来虽然简单,但是仍然有一些细节问题要处理:

  • 即使套接口是非阻塞的。如果连接的服务器在同一台主机上,那么在调用connect 建立连接时,连接通常会立即建立成功,我们必须处理这种情况。
  • 源自Berkeley的实现(和Posix.1g)有两条与select 和非阻塞IO相关的规则:
    A. 当连接建立成功时,套接口描述符变成可写;
    B. 当连接出错时,套接口描述符变成既可读又可写。

    所以,套接字可读并不一定是连接以建立的标志。

处理非阻塞connect的步骤(重点):

  • 创建socket,返回套接口描述符;
  • 调用fcntl 把套接口描述符设置成非阻塞;
  • 调用connect 开始建立连接;
  • 判断连接是否成功建立。

判断连接是否成功建立:

  • 如果connect 返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况);
  • 调用select 来等待连接建立成功完成;

如果select 返回0,则表示建立连接超时。我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去。如果select 返回大于0的值,则需要检查套接口描述符是否可写,如果套接口描述符可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR)。如果连接建立成功,这个错误值将是0;如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。

代码示例

int conn_nonb(int sockfd, const struct sockaddr_in *saptr, socklen_t salen, int nsec)
{
    int flags, n, error, code;
    socklen_t len;
    fd_set 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) {
        goto done;
    } else if (n < 0 && errno != EINPROGRESS){
        return (-1);
    }
 
    /* Do whatever we want while the connect is taking place */
 
    FD_ZERO(&wset);
    FD_SET(sockfd, &wset);
    tval.tv_sec = nsec;
    tval.tv_usec = 0;
 
    if ((n = select(sockfd+1, NULL, &wset, 
                    NULL, nsec ? &tval : NULL)) == 0) {
        close(sockfd);  /* timeout */
        errno = ETIMEDOUT;
        return (-1);
    }
 
    if (FD_ISSET(sockfd, &wset)) {
        len = sizeof(error);
        code = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
        /* 如果发生错误,Solaris实现的getsockopt返回-1,
         * 把pending error设置给errno. Berkeley实现的
         * getsockopt返回0, pending error返回给error. 
         * 我们需要处理这两种情况 */
        if (code < 0 || error) {
            close(sockfd);
            if (error) 
                errno = error;
            return (-1);
        }
    } else {
        fprintf(stderr, "select error: sockfd not set");
        exit(0);
    }
 
done:
    fcntl(sockfd, F_SETFL, flags);  /* restore file status flags */
    return (0);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值