关于select函数中timeval和fd_set重新设置的问题

select原型: int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

 

和select模型紧密结合的四个宏:

FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);


理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空

基于上面的讨论,可以轻松得出select模型的特点:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。对调整fd_set的大小可参考http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中的模型2,可以有效突破select可监控的文件描述符上限。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有事件发生)。

 

另外,如果select调用中设置了等待时间,那么每次调用时都需要重新对这个时间赋值么?就像对fd_set处理一样。
例如:
fd_set readfd;
struct timval tv;
while(1) {
  FD_ZERO(&readfd);
  FD_SET(fd, &readfd);
  tv.tv_sec = 2;
  tv.tv_usec = 0;
  select(maxfd+1, &readfd, NULL, NULL, &tv);
  ......;
}

如上代码,对fd_set需要每次调用都要重新设置,那么对tv来说是否也是一样呢?能不能把对tv的赋值放在while外面?

答案是不行,如果将时间的初始化放在外边,时间初始化为2秒,假设在1秒后发上了事件,则select将会返回并将tv的时间变成上次阻塞的剩余时间,即1秒,然后再进行监视套接字。这是因为linux系统对select()的实现中会修改参数tv为剩余时间。所以对于select函数中的最后一个参数,需要在循环中设置,每次循环要重新设置。如果设在循环外面,当循环执行起来后,每次循环select都会修改tv的值,tv的值越来越小,导致最后会产生select函数这tv时间内收不到有效时间,而返回-1,造成错误。

 

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在使用 `select` 函数进行 I/O 多路复用时,需要对返回值进行判断以确定有哪些文件描述符就绪。`select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则说明在超时时间内没有文件描述符就绪;如果返回值为 -1,则说明 `select` 函数调用失败。 在判断就绪的文件描述符时,可以使用 `FD_ISSET` 函数对每个文件描述符进行判断。`FD_ISSET` 函数返回值为非零表示该文件描述符存在于集合,为零表示不存在于集合。 如果需要统计有多少个文件描述符就绪,可以遍历文件描述符集合,对每个文件描述符调用 `FD_ISSET` 函数进行判断,并累加计数。 下面是一个简单的示例代码,用于统计有多少个文件描述符就绪: ```c #include <stdio.h> #include <stdlib.h> #include <sys/select.h> #include <unistd.h> int main() { fd_set rfds; // 用于存储文件描述符集合的 fd_set 结构体 int maxfd = STDIN_FILENO + 1; // 待监视的最大文件描述符值加一 struct timeval timeout = {5, 0}; // 超时时间为 5 秒 while (1) { FD_ZERO(&rfds); // 将读文件描述符集合清零 FD_SET(STDIN_FILENO, &rfds); // 将标准输入加入到读文件描述符集合 // 调用 select 函数进行 I/O 多路复用 int ret = select(maxfd, &rfds, NULL, NULL, &timeout); if (ret < 0) { perror("select error"); exit(1); } else if (ret == 0) { printf("timeout\n"); } else { int count = 0; // 遍历文件描述符集合,统计有多少个文件描述符就绪 for (int fd = 0; fd < maxfd; fd++) { if (FD_ISSET(fd, &rfds)) { count++; } } printf("%d file descriptor(s) are ready\n", count); // 如果标准输入可读,进行读操作 if (FD_ISSET(STDIN_FILENO, &rfds)) { char buf[1024]; int n = read(STDIN_FILENO, buf, sizeof(buf)); if (n < 0) { perror("read error"); exit(1); } else if (n == 0) { printf("EOF\n"); break; } else { printf("read: %s", buf); } } } } return 0; } ``` 在该程序,当 `select` 函数返回时,程序首先遍历文件描述符集合,统计有多少个文件描述符就绪,然后通过 `FD_ISSET` 函数判断标准输入是否可读,如果可读则进行读操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值