如何在套接字IO操作上设置超时机制

主要有三种方案:
(1)调用Alarm,它在指定超时期满时产生SIGALRM信号,此方法涉及信号处理,而信号处理在不同的实现上存在差异,而且可能干扰进程中现有的ALARM调用。
(2)在SELECT,即多路复用中阻塞等待IO,因为select有内置的时间限制,以此代替直接阻塞在read或write上的调用。
(3)使用较新的SO_RCVTIMEO和SO_SNDTIMEO套接字选项,这个方法的问题在于并非所有的实现都支持这两个套接字选项。
 
一、首先看一个如何调用Alarm,它主要是通过对慢系统调用产生中断信号来完成。
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("Alarm has already been set!");
    
    if( ( n = connect(sockfd, saptr, salen)) < 0){    //如果超过nsec秒数,会被中断产生EINTR
        close(sockfd);    //关闭此套接字,防止三次握手继续进行
        if(errno == EINTR)    //慢系统调用产生中断信号
            errno = ETIMEDOUT;
    }
    alarm(0);
    Signal(SIGALRM, sigfunc);
    return n;
}
 
static void connect_alarm(int signo){
    return;
}

  

 
 
二、再来看一Select多路复用是如何做到这一点的,它主要是通过第五个参数来设置所阻塞的描述符超时情况。
int readable_timeo(int fd, int sec){
    fd_set rset;
    struct timeval tv;    //主要是设置此结构体,之后将该结构体以参数形式传给Select函数
    FD_ZERO(&rset);
    FD_SET(fd, &rset);
    
    tv.tv_sec = sec;
    tv.tv_usec = 0;
    
    return (select(fd+1, &rset, NULL, NULL, &tv));    
}

  

这样,在每次调用read之前就可以先调用此函数来设置超时秒数。如下:
int readable_timeo(int fd, int sec){
    fd_set rset;
    struct timeval tv;    //主要是设置此结构体,之后将该结构体以参数形式传给Select函数
    FD_ZERO(&rset);
    FD_SET(fd, &rset);
    
    tv.tv_sec = sec;
    tv.tv_usec = 0;
    
    return (select(fd+1, &rset, NULL, NULL, &tv));    
}

  

 
三、使用SO_RCVTIMEO套接字选项为recvfrom设置超时
    本选项一旦设置到某个描述符上,其超时设置将应用于该描述符上的所有读操作,本方法的优势在于一次设置,一直生效,而前面两种方法需要在每个操作发生之前设置。另外,本套接字选项仅用于读操作,类似SO_SNDTIMEO仅用于写操作,但两者均不能用于为connect设置超时,从名字就可以看出,一个是接收,一个是发送。
   
struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
    Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));    //设置超时,通过设定套接字选项
    while(fgets(sendline, MAXLINE, stdin) != 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 read timeout!");
                continue;
            }
            else
                err_sys("recvfrom error!");
        }
        
        recvline[n] = 0;
        fputs(recvline, stdout);
    }

  

  

posted @ 2012-03-17 23:51  杨少宁  阅读(2279)  评论(0编辑  收藏  举报