导航
导航
文章目录
  1. select 函数剖析
  2. 代码实例
  3. select 的缺点

浅谈 select

《Unix 网络 I/O 模型》 这篇文章中,大概介绍了一下 I/O 复用模型,这篇文章主要是 I/O 复用 select 函数的工作机制。下文提到的描述符,套接字描述符,事件描述符,均为文件描述符。

select 函数剖析

select 函数可以将多个文件描述符集中到一起统一监视。函数定义如下:

1
2
3
4
#include <sys.select.h>
#include <sys/time.h>

int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

返回:若有就绪描述符则为其数目,若超时则为 0, 若出错则为 -1.

具体使用过程如下:

  1. 设置文件描述符;
  2. 指定监视事件;
  3. 设置超时时间;
  4. 调用 select 函数;
  5. 查看调用结果。

readset 表示读事件描述符集合,writeset 表示写事件描述符集合,exceptset 表示有异常事件描述符集合。timeout 表示超时时间。maxfdp1 后面再讲。

上述三个事件描述符集合都存在数据类型 fd_set 当中,该数据结构可以理解为存有 0 和 1的位数组。

如上图,最左端的位表示文件描述符 0. 如果该位设置为 1, 则表示该文件描述符是监视对象。注册或更改描述符到 fd_set 变量都是由下面的宏完成的。

1
2
3
4
FD_ZERO(fd_set *fdset);   // 将 fdset 变量的所有位初始化为 0.
FD_SET(int fd, fd_set *fdset); // 在参数 fdset 指向的变量中注册文件描述符 fd 的信息。
FD_CLR(int fd, fd_set *fdset); // 从参数 fdset 指向的变量中清除文件描述符 fd 的信息。
FD_ISSET(int fd, fd_set *fdset); // 若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回 true. 用此函数来验证 select 的调用结果。

头文件 <sys/select.h> 中定义的 FD_SETSIZE 常量是数据类型 fd_set 中的描述符总数,这个值通常是 1024, 在 select 诞生的那个年代,对于大多数应用程序来说,这个值很大,因为很多应用程序只是用 3 ~ 10 个描述符。 这个常量值也是可以修改的, 修改之后,必须重新编译内核才能够生效。

maxfdp1 参数表示指定待测试的描述符的个数,这个值是待测试的最大描述符加 1. 因为描述符的值是从 0 开始的。

上述的 readset, writeset, exceptset 都是值-结果参数。调用 select 之后,如果返回正整数,我们可以从三个描述符集合参数中查看哪些描述符发生了变化。如下图。

原来为 1 的所有位均变为 0, 但发生变化的文件描述符对应的位除外。1 表示该位置上的文件描述符发生了变化。之后我们可以用 FD_ISSET 来判断是哪个文件描述符发生了变化。

代码实例

下面用 select 实现一个 echo 服务器:
echo 服务器

第 43, 44 行:初始化 fd_set 变量,并将服务端 socket 描述符注册到 reads 数组中。
第 48, 51 行:把 reads 变量的内容复制到 copy_reads 中。因为调用 select 之后,除了发生变化的文件描述符对应位之外,剩下的所有位将初始化为 0. 因此,为了记住初始值,必须要把复制之后的变量传递给 select 函数。
第 63, 65 行:用 FD_ISSET 循环查找状态发生变化的文件描述符。

select 的缺点

select 有如下缺点:

  • 监视的文件描述符数量少,默认是 1024.
  • 每次调用 select 都需要将三个 fd_set 数据结构从用户态拷贝到内核态,如果 fd 集合的数量很多,这个开销会很大。
  • 调用 select 之后要对所有的文件描述符进行轮询查找发生变化的描述符。
支持一下
请 xdd1874 喝一杯咖啡?
  • 微信扫一扫