linux编程中的超时设置

http://blog.csdn.net/simba888888/article/details/9073963
http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29075379&id=3895865

一、使用alarm 函数设置超时

C++ Code

void handler( int sig)
{
}
signal(SIGALRM, handler);

alarm( 5 );
int ret = read(fd, buf, sizeof (buf));
if (ret == - 1 && errno == EINTR)
errno = ETIMEOUT;
else if (ret >= 0 )
alarm( 0 );
……………..

程序大概框架如上所示,如果read在5s内被SIGALRM信号中断而返回,则表示超时,否则未超时已读取到数据,取消闹钟。但这种方法不常用,因为有时可能在其他地方使用了alarm会造成混乱。

二、使用套接字选项SO_SNDTIMEO、SO_RCVTIMEO

C++ Code

setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, 5 );

int ret = read(sock, buf, sizeof (buf));
if (ret == - 1 && errno == EWOULDBLOCK)
errno = ETIMEOUT;
……….

即使用setsockopt 函数进行设置,但这种方法可移植性比较差,不是每种系统实现都有这些选项。

三、使用select 实现超时

下面程序包含read_timeout、write_timeout、accept_timeout、connect_timeout 四个函数封装

C++ Code
/***************************************************************

File Name: sysutil.c
Author: Simba
Mail: [email protected]
Created Time: Sat 02 Mar 2013 10:53:06 PM CST
**************************************************************/

include “sysutil.h”

/* read_timeout - 读超时检测函数,不含读操作
* fd:文件描述符
* wait_seconds:等待超时秒数, 如果为0表示不检测超时;
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout( int fd, unsigned int wait_seconds)
{
int ret = 0 ;
if (wait_seconds > 0 )
{

    fd_set read_fdset; 
     struct  timeval timeout; 

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

    timeout.tv_sec = wait_seconds; 
    timeout.tv_usec =  0 ; 

     do 
    { 
        ret = select(fd +  1 , &read_fdset,  NULL ,  NULL , &timeout);  //select会阻塞直到检测到事件或者超时 
         // 如果select检测到可读事件发送,则此时调用read不会阻塞 
    } 
     while  (ret <  0  && errno == EINTR); 

     if  (ret ==  0 ) 
    { 
        ret = - 1 ; 
        errno = ETIMEDOUT; 
    } 
     else   if  (ret ==  1 ) 
         return   0 ; 

} 

 return  ret; 

}

/* write_timeout - 写超时检测函数,不含写操作
* fd:文件描述符
* wait_seconds:等待超时秒数, 如果为0表示不检测超时;
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout( int fd, unsigned int wait_seconds)
{
int ret = 0 ;
if (wait_seconds > 0 )
{

    fd_set write_fdset; 
     struct  timeval timeout; 

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

    timeout.tv_sec = wait_seconds; 
    timeout.tv_usec =  0 ; 

     do 
    { 
        ret = select(fd +  1 ,  NULL , &write_fdset,  NULL , &timeout); 
    } 
     while  (ret <  0  && errno == EINTR); 

     if  (ret ==  0 ) 
    { 
        ret = - 1 ; 
        errno = ETIMEDOUT; 
    } 
     else   if  (ret ==  1 ) 
         return   0 ; 

} 

 return  ret; 

}

/* accept_timeout - 带超时的accept
* fd: 套接字
* addr: 输出参数,返回对方地址
* wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout( int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof ( struct sockaddr_in);

 if  (wait_seconds >  0 ) 
{ 

    fd_set accept_fdset; 
     struct  timeval timeout; 
    FD_ZERO(&accept_fdset); 
    FD_SET(fd, &accept_fdset); 

    timeout.tv_sec = wait_seconds; 
    timeout.tv_usec =  0 ; 

     do 
    { 
        ret = select(fd +  1 , &accept_fdset,  NULL ,  NULL , &timeout); 
    } 
     while  (ret <  0  && errno == EINTR); 

     if  (ret == - 1 ) 
         return  - 1 ; 
     else   if  (ret ==  0 ) 
    { 
        errno = ETIMEDOUT; 
         return  - 1 ; 
    } 
} 

 if  (addr !=  NULL ) 
    ret = accept(fd, ( struct  sockaddr *)addr, &addrlen); 
 else 
    ret = accept(fd,  NULL ,  NULL ); 
 if  (ret == - 1 ) 
    ERR_EXIT( "accpet error" ); 

 return  ret; 

}

/* activate_nonblock - 设置IO为非阻塞模式
* fd: 文件描述符
*/
void activate_nonblock( int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == - 1 )
ERR_EXIT( “fcntl error” );

flags |= O_NONBLOCK; 
ret = fcntl(fd, F_SETFL, flags); 
 if  (ret == - 1 ) 
    ERR_EXIT( "fcntl error" ); 

}

/* deactivate_nonblock - 设置IO为阻塞模式
* fd: 文件描述符
*/
void deactivate_nonblock( int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == - 1 )
ERR_EXIT( “fcntl error” );

flags &= ~O_NONBLOCK; 
ret = fcntl(fd, F_SETFL, flags); 
 if  (ret == - 1 ) 
    ERR_EXIT( "fcntl error" ); 

}

/* connect_timeout - 带超时的connect
* fd: 套接字
* addr: 输出参数,返回对方地址
* wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout( int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof ( struct sockaddr_in);

 if  (wait_seconds >  0 ) 
    activate_nonblock(fd); 

ret = connect(fd, ( struct  sockaddr *)addr, addrlen); 
 if  (ret <  0  && errno == EINPROGRESS) 
{ 

    fd_set connect_fdset; 
     struct  timeval timeout; 
    FD_ZERO(&connect_fdset); 
    FD_SET(fd, &connect_fdset); 

    timeout.tv_sec = wait_seconds; 
    timeout.tv_usec =  0 ; 

     do 
    { 
         /* 一旦连接建立,套接字就可写 */ 
        ret = select(fd +  1 ,  NULL , &connect_fdset,  NULL , &timeout); 
    } 
     while  (ret <  0  && errno == EINTR); 

     if  (ret ==  0 ) 
    { 
        errno = ETIMEDOUT; 
         return  - 1 ; 
    } 
     else   if  (ret <  0 ) 
         return  - 1 ; 

     else   if  (ret ==  1 ) 
    { 
         /* ret返回为1,可能有两种情况,一种是连接建立成功,一种是套接字产生错误 
         * 此时错误信息不会保存至errno变量中(select没出错),因此,需要调用 
         * getsockopt来获取 */ 
         int  err; 
        socklen_t socklen =  sizeof (err); 
         int  sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen); 
         if  (sockoptret == - 1 ) 
             return  - 1 ; 
         if  (err ==  0 ) 
            ret =  0 ; 
         else 
        { 
            errno = err; 
            ret = - 1 ; 
        } 
    } 
} 

 if  (wait_seconds >  0 ) 
    deactivate_nonblock(fd); 

 return  ret; 

}
下面来解析一下这些函数的封装:
1、read_timeout :如注释所写,这只是读超时检测函数,并不包含读操作,如果从此函数成功返回,则此时调用read将不再阻塞,测试代码可以这样写:
C++ Code

int ret;
ret = read_timeout(fd, 5 );
if (ret == 0 )
read(fd, buf, sizeof (buf));
else if (ret == - 1 && errno == ETIMEOUT)
printf( “timeout…\n” );
else
ERR_EXIT( “read_timeout” );

如果 read_timeout(fd, 0); 则表示不检测超时,函数直接返回为0,此时再调用read 将会阻塞。

当wait_seconds 参数大于0,则进入if 括号执行,将超时时间设置为select函数的超时时间结构体,select会阻塞直到检测到事件发生或者超时。如果select返回-1且errno 为EINTR,说明是被信号中断,需要重启select;如果select返回0表示超时;如果select返回1表示检测到可读事件;否则select返回-1 表示出错。

2、write_timeout :此函数跟read_timeout 函数类似,只是select 关心的是可写事件,不再赘述。

3、accept_timeout :此函数是带超时的accept 函数,如果能从if (wait_seconds > 0) 括号执行后向下执行,说明select 返回为1,检测到已连接队列不为空,此时再调用accept 不再阻塞,当然如果wait_seconds == 0 则像正常模式一样,accept 阻塞等待,注意,accept 返回的是已连接套接字。

4、connect_timeout :在调用connect前需要使用fcntl 函数将套接字标志设置为非阻塞,如果网络环境很好,则connect立即返回0,不进入if 大括号执行;如果网络环境拥塞,则connect返回-1且errno == EINPROGRESS,表示正在处理。此后调用select与前面3个函数类似,但这里关注的是可写事件,因为一旦连接建立,套接字就可写。还需要注意的是当select 返回1,可能有两种情况,一种是连接成功,一种是套接字产生错误,由 这里 可知,这两种情况都会产生可写事件,所以需要使用getsockopt来获取一下。退出之前还需重新将套接字设置为阻塞。

我们可以写个小程序测试一下connect_timeout 函数,客户端程序如下:

C++ Code

include “sysutil.h”

int main( void )
{
int sock;
if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 )
ERR_EXIT( “socket” );

 struct  sockaddr_in servaddr; 
memset(&servaddr,  0 ,  sizeof (servaddr)); 
servaddr.sin_family = AF_INET; 
servaddr.sin_port = htons( 5188 ); 
servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" ); 

 int  ret = connect_timeout(sock, &servaddr,  5 ); 
 if  (ret == - 1  && errno == ETIMEDOUT) 
{ 
    printf( "timeout...\n" ); 
     return   1 ; 
} 
 else   if  (ret == - 1 ) 
    ERR_EXIT( "connect_timeout" ); 

 struct  sockaddr_in localaddr; 
socklen_t addrlen =  sizeof (localaddr); 
 if  (getsockname(sock, ( struct  sockaddr *)&localaddr, &addrlen) <  0 ) 
    ERR_EXIT( "getsockname" ); 

printf( "ip=%s port=%d\n" , inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); 

 return   0 ; 

}

因为是在本机上测试,所以不会出现超时的情况,但出错的情况还是可以看到的,比如不要启动服务器端程序,而直接启动客户端程序,输出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./echocli_timeout
connect_timeout: Connection refused

很明显是connect_timeout 函数返回了-1,我们也可以推算出connect_timeout 函数中,select返回1,但却是套接字发生错误的情况,errno = ECONNREFUSED,所以打印出Connection refused。

四、利用alarm实现,直接上例子

void u_alarm_handler()
{
inet_socket_fd = -1;
fprintf(stderr, “server not connect err\n”);
}
///////////////////////////////////////////////////////////////////////
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(sock_fd < 0){
perror(“sock”);
return -1;
}
memset(&addr_serv,0,sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_port = htons(port);
addr_serv.sin_addr.s_addr = inet_addr(ip);

sigset(SIGALRM, u_alarm_handler);
alarm(TIME_OUT_TIME);

if(connect(sock_fd,(struct sockaddr *)&addr_serv, sizeof(struct sockaddr_in)) < 0){
perror(“connect”);
//printf(“connect (%d)\n”,errno);
//return -1;
inet_socket_fd = -1;
}
alarm(0);
inet_socket_fd = sock_fd;
sigrelse(SIGALRM);

你可能感兴趣的:(Linux编程)