SELECT超时小结

本文详细介绍了如何使用select()函数设置socket连接超时机制,包括socket的非阻塞模式设置、使用select()函数检测连接状态及超时处理方法。重点展示了通过select()函数监控socket描述符的状态变化,实现对连接操作的超时控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文地址:SELECT超时小结

目前各平台通用的设置socket connect超时的办法是通过select(),具体方法如下

1.建立socket;

2.将该socket设置为非阻塞模式;

3.调用connect();

4.使用select()检查该socket描述符是否可写;

5.根据select()返回的结果判断connect()结果;

6.将socket设回阻塞模式。

 

select,就是用来监视某个或某些句柄的状态变化的,执行I/O多路转换。

select函数原型如下:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为要监视各文件中的最大文件号加一。 
readfds:select监视的可读文件句柄集合。 
writefds: select监视的可写文件句柄集合。 
exceptfds:select监视的异常文件句柄集合。 
timeout:本次select()的超时结束时间

当readfds或writefds中映象的文件可读或可写或超时,本次select()就结束返回。程序员利用一组系统提供的宏在select()结束时便可判断哪一文件可读或可写。对Socket编程特别有用的就是readfds。几只相关的宏解释如下: 
FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。 
FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。 
FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。 
FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否 
                                可读写,>0表示可读写。

函 数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:

struct timeval

           

long    tv_sec;                     

long    tv_usec;                

};
 


第2、 3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。

      这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:

fd_set rdfds;

struct timeval tv;

int ret;

FD_ZERO(&rdfds);

FD_SET(socket, &rdfds);

 

tv.tv_sec = 1;

tv.tv_usec = 500;

 

ret = select(socket + 1, &rdfds, NULL, NULL, &tv);

if(ret < 0)

    perror("select");

else if(ret == 0)

   printf("超时n");

else

{    

    printf("ret=%dn", ret);

      

if(FD_ISSET(socket, &rdfds)) {        

          

    recv(...);   

}

}
 


注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:

 

int sa, sb, sc;

sa = socket(...);

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa, &rdfds);

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);
 


在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:

int maxfd = 0;

if(sa > maxfd)

     maxfd = sa;

if(sb > maxfd)

    maxfd = sb;

if(sc > maxfd)

    maxfd = sc;
 


然后调用select函数:

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv);
 


同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:

FD_ZERO(&rdfds);

FD_SET(0, &rdfds);

tv.tv_sec = 1;

tv.tv_usec = 0;

ret = select(1, &rdfds, NULL, NULL, &tv);

if(ret < 0)

     perror("select");

else if(ret == 0)

     printf("超时n");

else {   

scanf("%s", buf); } 

 

LINUX设置连接超时方法:

在阻塞套接字的一般情况下,connect ()直到客户端对SYN消息的ACK消息到达之前才会返回。使connect()调用具有超时机制的一个方法是让套接字成为非阻塞的套接字体,然后用select()来等待它完成。
s = socket(AF_INET, SOCK_STREAM, 0);
//下面获取套接字的标志
if ((flags = fcntl(s, F_GETFL, 0)) < 0) {
    //错误处理
}

//下面设置套接字为非阻塞
if (fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) {
    //错误处理
}

if ((retcode = connect(s, (struct sockaddr*)&peer, sizeof(peer)) && 
    errno != EINPROGRESS) {
   //因为套接字设为NONBLOCK,通常情况下,连接在connect()返回
   //之前是不会建立的,因此它会返回EINPROGRESS错误,如果返回
   //任何其他错误,则要进行错误处理
}

if (0 == retcode) {  //如果connect()返回0则连接已建立
    //下面恢复套接字阻塞状态
    if (fcntl(s, F_SETFL, flags) < 0) {
        //错误处理
    }

    //下面是连接成功后要执行的代码
    
    exit(0)
}


(要设备send/recv超时只需从此处开始修改相应值,前面不用)
FD_ZERO(&rdevents);
FD_SET(s, &rdevents);  //把先前的套接字加到读集合里面
wrevents = rdevents;   //写集合
exevents = rdevents;   //异常集合

tv.tv_sec = 5;  //设置时间为5秒
tv_tv_usec = 0;

retcode = select(s+1, &rdevents, &wrevents, &exevents, &tv);
if (retcode < 0) {  //select返回错误???
    //错误处理
}
else if (0 == retcode) {  //select 超时???
    //超时处理
}
esle {
    //套接字已经准备好
    if (!FD_ISSET(s, &rdevents) && !FD_ISSET(s, &wrevents)) {
        //connect()失败,进行错处理
    }

    if (getsockopt(s, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
        //getsockopt()失败,进行错处理
    }

    if (err != 0) {
        //connect()失败,进行错处理
    }
(send/recv超时到此为止,返回send()/recv()函数)


    //到这里说明connect()正确返回
    //下面恢复套接字阻塞状态
    if (fcntl(s, F_SETFL, flags) < 0) {
        //错误处理
    }

    //下面是连接成功后要执行的代码      
    exit(0)
http://blogold.chinaunix.net/u2/82556/showart_1678205.html

`select`函数在Unix和Linux系统中用于等待多个套接字(socket)就绪,可以监视一组文件描述符,直到某个文件描述符变得可读、可写或出错。当超时时,如果没有找到任何就绪的连接,`select`会阻塞一段时间,直到超时条件满足。 如果`select`调用没有在指定的时间内返回(即超时),这通常意味着两个情况之一: 1. **所有描述符都没有活动**:所有被监视的文件描述符在给定的时间窗口内都没有发生可读、可写或错误事件。在这种情况下,`select`函数会返回一个空的元组。 2. **超时时间未达到**:你设置了`select`的timeout参数,但是这个时间还没有过去。这时,函数会在预定时间内继续阻塞,直到超时条件满足才会返回。 在处理这种情况时,你需要检查`select`的返回结果,如果是空元组,那就意味着超时了。另外,记得要正确配置超时时间,以防程序陷入长时间的阻塞状态。 ```c int timeout_seconds = 5; // 设置超时时间为5秒 struct timeval tv; tv.tv_sec = timeout_seconds; tv.tv_usec = 0; fd_set read_fds, write_fds; FD_ZERO(&read_fds); FD_SET(sockfd, &read_fds); // 添加要监视的套接字 if (select(sockfd+1, &read_fds, NULL, NULL, &tv) == -1) { if (errno == EINTR) { // 被信号中断,需要重新检查 // 或者处理其他异常 } else if (errno == ETIMEDOUT) { // 超时 // 处理超时逻辑... } else { // 其他错误 // 错误处理... } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值