[原]为套接字设置超时

刘生玺 18/08/17 22:38:16

  unp上讲述了以下三种方法:

1.调用alarm,它在指定超时期满时将产生SIGALRM信号。

2. 使用select为函数设置超时

3.使用SO_RCVTIMEO套接字选项为函数设置超时

(1.1).使用 SIGALRM 信号为 connect设置超时

static void connect_alarm(int);

int
connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
    Sigfunc *sigfunc; 
    int     n;

    sigfunc = Signal(SIGALRM, connect_alarm);//设置新的信号处理函数
    if (alarm(nsec) != 0)//检测以前有没有设置过时钟,如果有就会覆盖
        err_msg("connect_timeo: alarm was already set");

    if ( (n = connect(sockfd, saptr, salen)) < 0) {
        close(sockfd);
        if (errno == EINTR)
            errno = ETIMEDOUT;
    }
    alarm(0);                   /* turn off the alarm */
    Signal(SIGALRM, sigfunc);   /* 恢复先前的处理函数 */

    return(n);
}

static void
connect_alarm(int signo)
{
    return;     /* just interrupt the connect() */
}
/* end connect_timeo */

void
Connect_timeo(int fd, const SA *sa, socklen_t salen, int sec)
{
    if (connect_timeo(fd, sa, salen, sec) < 0)
        err_sys("connect_timeo error");
}

注意事项:

  1. EINTR 错误

1.产生原因:

   在阻塞于该系统调用上的时候,该进程收到一个信号并且信号处理函数返回之后,该系统调用就会返回一个EINTR错误。如上例,在客户端,我们设置了信号处理机制,当阻塞于connect时,收到信号并且信号处理函数返回,那么connect就会产生一个EINTR错误。

2.如何解决?

   可以对该系统调用进行重启,当然了,要注意的是:对于有一些系统调用是不能够重启的。例如:connect函数。若connetct函数返回一个EINTR错误的时候,则不能再次调用它,否则将返回一个错误。其他的(accept、read、wiret、seletc、ioctl和open之类的函数)都是可以进行重启的。

(1.2)使用 SIGALRMrecvfrom设置超时

static void sig_alrm(int);

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int n;
    char    sendline[MAXLINE], recvline[MAXLINE + 1];

    Signal(SIGALRM, sig_alrm);

    while (Fgets(sendline, MAXLINE, fp) != NULL) {

        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        alarm(5);
        if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
            if (errno == EINTR) //输出信息并继续执行
                fprintf(stderr, "socket timeout\n");
            else
                err_sys("recvfrom error");
        } else {
            alarm(0);
            recvline[n] = 0;    /* null terminate */
            Fputs(recvline, stdout);
        }
    }
}

static void
sig_alrm(int signo)
{
    return;         /* just interrupt the recvfrom() */
}

其实这就是一个对于可以重启的系统调用的例子而已

(2).使用selectrecvfrom设置超时

int
readable_timeo(int fd, int sec)
{
    fd_set          rset;
    struct timeval  tv;

    FD_ZERO(&rset);
    FD_SET(fd, &rset);

    tv.tv_sec = sec;
    tv.tv_usec = 0;

    return(select(fd+1, &rset, NULL, NULL, &tv));
    /*出错返回-1,超时返回0,否则返回已就绪的描述符数目
    只是等待给定的描述符变为可读,因此适用于任何类型套接字,TCP/UDP*/
}
/* end readable_timeo */

(3).使用SO_RCVTIMEO套接字选项为recvfrom设置超时

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int             n;
    char            sendline[MAXLINE], recvline[MAXLINE + 1];
    struct timeval  tv;

    tv.tv_sec = 5;
    tv.tv_usec = 0;
    Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    while (Fgets(sendline, MAXLINE, fp) != NULL) {

        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        if (n < 0) {
            if (errno == EWOULDBLOCK) {
                fprintf(stderr, "socket timeout\n");
                continue;
            } else
                err_sys("recvfrom error");
        }

        recvline[n] = 0;    /* null terminate */
        Fputs(recvline, stdout);
    }
}

1.优势就在于一次性设置即可,前面的两种操作都是需要在设置时间限制操作之前做些工作的。SO_RCVTIMEO只应用于读操作。SO_SNDTIMEO用于写操作,( UNP上说两个都不能为connect设置超时 ,但是经过查询后发现,其实是可以的,可以自己try一下哦,点击这里查看详情)

2.超时函数会(在这里是recvfrom)返回EWOULDBLOCK错误。

比较:

一:第一种方法只适用于单线程或者未线程化的程序中。很难适用于多线程化的程序中。(或者说编程时需要注意很多)

二: 一般(我估计的)就是使用第三种方法

附录:

connect函数返回错误的情况,常见的有下面三种:

  • 返回TIMEOUT,即SYN_RECV(未完成连接队列)都满了,对于客户端发来的三次握手第一次的SYN都没有办法响应,这时候TCP会隔6s,24s重发,直到75s,如果还是没有被接受,最后返回TIMEOUT错误。
  • 返回ECONNREFUSED错误,表示服务器主机没有在相应的端口开启监听。
  • 返回EHOSTUNREACHENETUNREACH,表示在某个中间路由节点返回了ICMP错误,这个错误被内核先保存,之后继续按照6s,24s重发,直到75s,如果还是没有响应,就返回EHOSTUNREACHENETUNREACH错误。

Just a question :

1:如果服务端在listen之后没有accept,那客户端的connect会返回吗?为什么? 
2:此时调用send发数据会怎么样?

   其实第一个问题只要你懂得listen,accept,connect的作用和 连接过程很容易就能想明白。ok,那我们来说说:

首先,来讲listen函数:

   #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>

   int listen(int sockfd, int backlog);

功能:——–》两个

一:把一个未连接的套接字转换为一个被动套接字,指示内核应该接受指向该套接字的请求。状态从CLOSED变为LISTEN状态。

二:第二个参数指定了为该套接字排队的最大连接个数。(目前仍然没有正式定义)
摘自man 手册(4.17.12-100.fc27.x86_64 #1 SMP Fri Aug 3 15:00:33 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux)
“`c++

The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a con‐
nection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if
the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection suc‐
ceeds.
/翻译/

backlog参数定义sockfd的挂起连接队列可能增长的最大长度。如果一个
当队列已满时,请求到达,客户端可能会收到带有ECONNREFUSED指示的错误,或者,如果基础协议支持重传,可以忽略该请求,以便稍后重新连接成功。

“`

必须认识到的一点:内核会为任何一个给定的监听套接字维护两个队列。

1.一个未完成队列:客户端主动打开套接字,发来SYN分节,在到达服务器这边时,等待服务器发送(SYN + ACK)分节确认。这些套接字处于SYN_RCVD状态。

2.一个已完成连接队列:按字面意思理解既可。这些套接字处于ESTABLISHED状态。(也就是说服务端已经发送了SYN+ACK,并且收到客户端发来的ACK之后,立马就会将未完成连接队列中的该套接字拿到已连接套接字队列中)

– - - - - - - - - – - - - - - - – - - - - - - – - - - - - - [ 持 续 更 新 ]

作者:liushengxi_root 发表于 2018/08/17 22:38:16 原文链接 https://blog.csdn.net/liushengxi_root/article/details/81776733
阅读:58