服务器编程入门(13) Linux套接字设置超时的三种方法

摘要:

    本文介绍在套接字的I/O操作上设置超时的三种方法。


图片可能有点宽,看不到的童鞋可以点击图片查看完整图片。。


1 调用alarm

使用SIGALRM为connect设置超时

设置方法:

  1. 监听SIGALRM信号,
  2. 设置sig_alrm处理函数,
  3. 在阻塞函数前调用alarm函数设置超时时间,
  4. 正常返回后,重置超时事件为0
void handle_msg(int sockfd) {

    char sendbuf[BUFSIZE];
    char recvbuf[BUFSIZE];
    
    signal(SIGALRM, sig_alrm);    //监听SIGALRM信号
 
    while(1) {
        memset( sendbuf, \0, BUFSIZE );
        memset( recvbuf, \0, BUFSIZE );
        
        printf("%s", "send msg:");
        gets(sendbuf);

        if (strlen(sendbuf) > 0)
            send(sockfd,sendbuf,strlen(sendbuf),0);

        if ( !strcmp(sendbuf, "exit"))
            break;

        alarm(5);       //设置超时事件为5s,同时设置服务器回射前sleep 10秒,以让recv函数超时
        if (recv(sockfd,recvbuf,BUFSIZE,0) > 0) {
            alarm(0);
            printf("recv back:%s\n\n", recvbuf);
        }
        else {
            if (errno == EINTR)
                fprintf(stderr,
                        "socket timeout\n");
            else
                fprintf(stderr,
                        "receive error\n");
        }
    }
    close( sockfd );
    return;
}


static void sig_alrm(int signo) {
    fprintf(stderr,
            "recv SIGALRM, return.\n");
    return;
}

运行截图:

虽然设置了SIGALRM信号处理函数,但是如图所示,本例依然可以等待读取回射信息,因为信号处理函数里只是return。


2 使用select阻塞等待I/O

设置方法:

使用select的内置时间限制,阻塞在select代替recv函数的阻塞。

void handle_msg(int sockfd) {

    char sendbuf[BUFSIZE];
    char recvbuf[BUFSIZE];

    while(1) {
        memset( sendbuf, \0, BUFSIZE );
        memset( recvbuf, \0, BUFSIZE );
        
        printf("%s", "send msg:");
        gets(sendbuf);

        if (strlen(sendbuf) > 0)
             send(sockfd,sendbuf,strlen(sendbuf),0);

        if ( !strcmp(sendbuf, "exit"))
            break;
        
        if (readable_timeo(sockfd, 5) == 0) {
            fprintf(stderr,
                    "socket timeout\n");
        }
        else{
            recv(sockfd,recvbuf,BUFSIZE,0);
            printf("recv back:%s\n\n", recvbuf);
        }
    }
    close( sockfd );
    return;
}

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);
}

 

运行截图:

由运行截图可以看到,超时警告运行正常。

需要注意一点的是,虽然客户端在超时之后继续发送消息,但是服务器回射的消息(hello world)依然被接收,这导致我们再次发送消息时,从缓冲区中读出了延迟收到的hello world。

这里只是演示超时技术,因此对此并不做进一步处理。


3 使用SO_RCVTIMEO套接字选项

使用SO_RCVTIMEO套接字选项为recv设置超时

设置方法:

  • 使用setsockopt函数对套接字进行设置
  • 一旦设置了某个描述符,其超时设置将应用于该描述符上的所有读操作
  • SO_RCVTIMEO仅用于读操作,SO_SNDTIMEO仅用于写操作,两者都不能用于为connect设置超时
  • 如果套接字超时,被阻塞的函数将返回一个EWOULDBLOCK错误
void handle_msg(int sockfd) {

    char sendbuf[BUFSIZE];
    char recvbuf[BUFSIZE + 1];
    int n;
    struct timeval tv;

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

    while(1) {
         memset( sendbuf, \0, BUFSIZE );
        memset( recvbuf, \0, BUFSIZE );
        
        printf("%s", "send msg:");
        gets(sendbuf);

        if (strlen(sendbuf) > 0)
             send(sockfd,sendbuf,strlen(sendbuf),0);

        if ( !strcmp(sendbuf, "exit"))
            break;

        if ( (n=recv(sockfd,recvbuf,BUFSIZE,0)) < 0 ) {
            if (errno == EWOULDBLOCK) {
                fprintf(stderr,
                        "socket timeout\n");
                continue;
            }
            else
                fprintf(stderr,
                        "recv error");
        }
        else{
            printf("recv back:%s\n\n", recvbuf);
        }
    }
    close( sockfd );
    return;
}

运行截图:

可以看到,超时警报成功运行,但依然有上一例中的延迟接收的情况发生,不作处理。


 

示例源码上传到了github上,地址:https://github.com/zs634134578/UNP/tree/tryTimeout

 

 

参考资料:

《UNIX网络编程 卷1:套接字联网API(第3版)》

服务器编程入门(13) Linux套接字设置超时的三种方法,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。