linux网络编程_connect函数的超时设置方法

在默认情况下,Linux中的connect函数是阻塞的,尤其在网络连接数量大时效率低下。本文介绍了两种设置connect超时的方法:1) 通过信号中断方式,使用alarm()设置超时并用sigaction()处理SIGALARM信号;2) 利用非阻塞方式,将套接字设为非阻塞,结合select()进行超时判断和连接状态检查。

我们都知道connect函数执行TCP协议中的三次握手过程,在默认情况下,connect是阻塞的。
当通过tcp客户端向未知ip发起connect请求时,tcpdump抓包情况:

21:13:15.545917 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553264049 ecr 0,nop,wscale 7], length 0
21:13:16.550200 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553265054 ecr 0,nop,wscale 7], length 0
21:13:18.566704 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553267070 ecr 0,nop,wscale 7], length 0
21:13:22.599110 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553271103 ecr 0,nop,wscale 7], length 0
21:13:30.790961 IP 192.168.237.128.57368 > 144.34.160.165.16clouds.com.12345: Flags [S], seq 287971294, win 29200, options [mss 1460,sackOK,TS val 3553279294 ecr 0,nop,wscale 7], length 0
21:13:36.548172 IP 144.34.160.165.16clouds.com.12345 > 192.168.237.128.57368: Flags [R.], seq 414153142, ack 287971295, win 64240, length 0

可见,在整个connect阻塞过程中,客户端会不断重启SYN报文的发送,发送间隔逐渐增大,大约在20s后connect才超时退出。
当网络连接数量巨大是,阻塞的connect会导致效率非常低下。我们可以采用以下两种方式来设置其超时时间。

信号中断方式

在调用connect()前,调用alarm()设置一个超时值,在指定时间后产生SIGALARM信号,中断可能阻塞的connect。
我们采用sigaction()来实现信号处理函数signal(),设置SIGALARM信号中断的系统调用为不自动重启,这样才能达到超时退出的目的。

typedef void Sigfunc (int);
static void connect_sigfun(int signo)
{ 
    return; 
}

Sigfunc* signal(int signo, Sigfunc * func)
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(signo == SIGALRM)
    {
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT; //设置SIGALARM信号中断的调用不自动重启
#endif
    }
    else
    {
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }
    if(sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

connect函数实现:

int connect_timeo_signal(int sockfd, const sockaddr * sockaddr, socklen_t len, int nsec)
{
    Sigfunc * sigfunc;
    int n;

    sigfunc = signal(SIGALRM, connect_sigfun);
    alarm(nsec);

    if((n = connect(sockfd, sockaddr, len)) < 0)
    {
        close(sockfd);
        if(errno == EINTR)
        {
            errno = ETIMEDOUT;
            cout<<"connect timeout!"<<endl;
        }
    }
    alarm(0);
    signal(SIGALRM, sigfunc);

    return n;
}

非阻塞方式

在非阻塞套接字上调用connect,会立即返回一个EINPROGRESS错误,但三次握手的操作可以继续执行,可以接着采用带超时的select检测连接的建立情况。
connect函数实现:

int connect_timeo_nonb(int sockfd, const sockaddr * sockaddr, socklen_t len, int nsec)
{
    int op, n, err;
    socklen_t elen;
    fd_set rset, wset;
    struct timeval tval;

    if( (op = fcntl(sockfd, F_GETFL)) < 0)
    {
        perror("fcntl error");
        return -1;
    }
    if(fcntl(sockfd, F_SETFL, op | O_NONBLOCK) < 0)
    {
        perror("fcntl error");
        return -1;
    }

    if((n = connect(sockfd, sockaddr, len)) < 0)
        if(errno != EINPROGRESS)
            return -1;
    if(n == 0)
        goto end;
    
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_SET(sockfd, &rset);
    FD_SET(sockfd, &wset);

    tval.tv_sec = nsec;
    tval.tv_usec = 0;

    if ((n = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0)
    {
        close(sockfd);
        errno = ETIMEDOUT;
        return -1;
    }
    if (n < 0)
    {
        perror("select error");
        close(sockfd);
        return -1;
    }
    if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
    {
        elen = sizeof(errno);
        if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &errno, &elen) < 0)
            return -1;
    } 

end:
    if(fcntl(sockfd, F_SETFL, op) < 0)
        perror("fcntl error");
    if(errno)
    {
        close(sockfd);
        errno = err;
        return -1;
    }

    return 0;
}

其中,首先设置套接字描述符为非阻塞;然后connect,并select监听套接字描述符的读写状态,若返回0表示超时;当可写或可读后,调用getsockopt()获取套接字的待处理错误,如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,这个值是错误所对应的errno值。

验证:

fuzhuo@fuzhuo-virtual-machine:~/FZ/code/test/unp$ ./client 144.34.160.165
connect failed: Connection timed out
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值