第十六章 非阻塞connect初识

非阻塞connect
    当在一个非阻塞的TCP套接字上调用connect时,connect将立即返回一个EINPROGRESS错误,不过已经发起的三路握手将继续进行。我们接着使用select检测这个连接或成功或失败的已建立条件。
    非阻塞的connect有三个用途:
     1、我们可以把三路握手叠加在其他处理上。完成一个connect要花一个RTT时间,而RTT不稳定,这段时间内可能有我们想要执行的其他处理工作要执行
    2、我们可以使用这个技术同时建立多个连接。此技术现在已随着web浏览器变得流行起来
    3、既然使用select等待连接的建立,我们可以给select指定一个时间的限制,使我们缩短connect的超时(许多实现有75秒甚至更多的超时时间,但我们想要缩短,方法之一就是使用非阻塞connect)


    非阻塞connect要处理的一些细节:    
        1、尽管套接字是非阻塞的,如果连接的服务器在同一主机上,那我们调用connect时,连接通常立即建立,我们需要处理这种情形
        2、两个规则:
             当连接建立成功时,描述符变为可写(若select调用发现有数据来了,当然也会变成可读的)
            当连接建立遇到错误时,描述符既可读也可写(这就可能会造成迷惑,那我们如何发现错误呢?下面程序就通过 getsockopt来解决了这个问题)


    以下就贴出非阻塞connect函数的实现:

#include "unp.h"

int connect_nonb(int sockfd, struct sockaddr *saptr, socklen_t alen, int nsec)
{
    int flag, rl, n, error;
    fd_set rset, wset;
    struct timeval tval;

    //set up O_NONBLOCK
    flag = fcntl(sockfd, F_GETFL);
    fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);

    error = 0;
    
    //excute connect
    if((rl=connect(sockfd, saptr, alen)) < 0)
        if(errno != EINPROGRESS)        //EINPROGRESS is the expected signal once we use "unblocked connect"
            return -1;
    if( rl != 0 )                //if rl is "0", it means that client and service connect immediately (maybe they are in the same  computer)
    {
        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)        //time out
        {
            close(sockfd);
            errno = ETIMEDOUT;
            return -1;
        }

        //    if connection is established and data hasn't arrived, sockfd can be written
        //    if connection is established and data has arrived and can be read, sockfd can be written and read
        //    However, if errors occur, sockfd can be read and written too.
        //    So , we have to use "getsockopt " to judge wheather something goes wrong or not
        if(FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&wset))
        {
            len = sizeof(error);
            if(getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) < 0)
            /* 如果发生错误,Solaris实现的getsockopt返回-1,   
             * 把pending error设置给errno. Berkeley实现的   
             * getsockopt返回0, pending error返回给error.    
             * 我们需要处理这两种情况
             * 因此在下方 if(error) 中处理了
             */
                return -1;
        }
        else
            oops("socket not set");
    }

    fcntl(sockfd,F_SETFL,flag);

    //为了可移植性,我们需要这么做
    if(error)
    {
        close(sockfd);
        errno = error;
        return -1;
    }

    return 0;
}



    既然我们发现了套接字可写(而不可读)条件并不是select返回套接字操作成功条件的唯一方法(之前也提到了,在connect和select之间可能有数据来临导致sockfd可读),下一个移植性问题就是怎样判断建立连接是否成功。以下一些方法:
    1、调用getpeername代替getsockopt。 如果getpeername以ENOTCONN错误返回,那么连接就是失败的,我们必须接着以SO_ERROR调用getsockopt来取得错误
    2、以值为0的长度参数调用read。如果read失败,那么connect调用失败,read返回的errno给出了连接失败的原因。如果连接是成功的,那么该是返回0的
    3、再调用connect。它应该失败,如果错误是EISCONN,那么套接字已连接,即之前已连接成功

    
        这里提一下,之前看UDP的时候我也记得有再次调用connect这个概念,现在把它贴在这里,是关于UDP调用connect部分里的:
            拥有一个已连接的UDP套接字的进程可以为以下两个目的之一再次调用connect
                    1、指定新的IP地址和端口号(对于TCP,connect只能调用一次)
                       2、断开套接字(只要在再次调用时把地址族改为 AF_UNSPEC)
    


被中断的connect
    对于一个正常的阻塞connect,若正在三路握手的TCP,如果在完成之前被中断,(譬如捕捉到某个信号了),那么假设中断的connect不由内核重启,那么它将返回EINTR。我们不能再调用connect等待未完成的连接继续完成,因为这样将导致EADDRINUSE错误
     此时我们只能调用select,像对待这里的非阻塞connect一样,连接建立成功时候select返回套接字可写条件,失败时返回既可读也可写条件
    (connect的问题,当connect遇到EINTR错误时,不能向上面那样重新进入循环处理,原因是,【connect的请求已经发送向对方,正在等待对方回应,这是如果重新调用connect,而对方已经接受了上次的connect请求,这一次的connect就会被拒绝】,因此,需要使用select或poll调用来检查socket的状态,如果socket的状态就绪,则connect已经成功,否则,视错误原因,做对应的处理。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值