摘要:在UNIX系统编程过程中,一个进程往往需要等待多个描述字发生某一事件,如可读、可写或异常等等。进程不能永远地等待其中任何单独一个描述字,它需要同时等待所有描述字,此时就是IO复用技术,系统调用select就是实现这一目标的方式之一。本文详细介绍select函数。
select广泛应用于各种场合,因为select对于任何描述字都有作用,它被应用网络程序,也被应用于终端程序,也被应用其它场合。下面我们先来看看它的函数原型:
#include<sys/select.h>
#include<sys/time.h>
int select(int max_fd_p_1, fd_set *readset, fd_set* writeset, fd_set* exceptset, struct timeval* timeout);
select函数有5个参数,我们会介绍每一个参数。
- 参数max_fd_p_1
- 内核需要一个数字来指定最大的描述字,因为内核使用这个参数来遍历一组描述字。又因为描述字是从0开始计数的,所以max_fd_p_1实现上是最大描述字的加上1的值。
- 参数readset
- select可以测试一组描述字是否可读,用户需要告诉内核所有关心的描述字,readset就是用来指定关心的描述字集合。
- 参数writeset
- 如同readset指定可读描述字集合一样,writeset用来指定关心的可写描述字集合。
- 参数exceptset
- 跟readset、writeset一样,exceptset用来指定所关心的异常描述字集合,也就是当集合中一个描述字出现异常时会得到内核的一个通知。
- 参数timeout
- 用来指定超时的时长,这是一个结构体:
-
struct timeval
{
long tv_sec;//秒数
long tv_usec;//微秒数
}这个结体定义在头文件sys/time.h中。
-
上面我们简单地针对select自身进行了说明。我们注意到,select的参数有4个是指针,如果这些指针取值是空的话会如何?对于指定描述字集合的参数,如果取空的话,它们的意义很直接,就是没有相应的关心的描述字集合。比如readset==NULL,则说明用户不关心任何一个描述字是否可读,对于writeset、exceptset也完全一样。但是对于timeout==NULL的情况就是很直接的,它表示永远等待下去,此时我们不关心需要等待多长时间,我们只要求至少有一个描述字满足用户所关心的。
对于timeout里面两个成员都取值0的时候,相应意义很直接,就是等待0秒0微秒,也就是说不等待任何时间。此时就相当于简单的轮询。
上面我们介绍了最后一个参数的,那么先前的三个参数我需要注意一下,就是它们的类型是fd_set,fd_set是什么样的类型呢?我并不知道,但有一点,就是POSIX标准为我们提供了四个宏,这四个宏可以完成我们需要对fd_set的操作,而fd_set的实际类型留给系统去定义。这四个宏分别是:
void FD_ZERO(fd_set* set);//把set设置为0
void FD_SET(int fd, fd_set* set);//从把fd添加到set
void FD_CLR(int fd, fd_set* set);//从set中删除fd
int FD_ISSET(int fd, fd_set* set);//判断fd是否在set中被设置
select是一相十分复杂的函数,它的返回值如上所说,-1为错误,0为超时,正数为就绪的打字个数。但是事实上打字的个数往往很小,系统会一个限制,一般为1024;这是因为fd_set这个类型的限制,同时select也不应该处理过多的打字,否则话会有性能问题。
select也会设置errno值,最常见的值可能就是EINT,表示一个信号中断了select调用,作为应用程序应该在select返回错误的时候查看errno。