当从一个描述符读,写到另一个描述符时,可以在下列形式的循环中使用阻塞IO:
while ((n = read(STDIN_FILENO, buf, BUFSIZ)) > 0) if (write(STDOUT_FILENO, buf, n) != n) err_sys("write error");这种形式的阻塞IO随处可见,但如果必须从两个描述符读呢?这种方式就可能导致长时间阻塞在其中一个描述符上,而另一个描述符虽然有很多数据却不能及时处理。
一种直观的解决方法是这样的:将两个描述符都设置为非阻塞,对第一个描述符发送read,如果有数据则读取并处理,如果没有则立即返回,对第二个描述符做同样处理。此后等待若干秒,再度去第一个描述符。这种形式的循环称为轮询(polling),缺点非常明显:大部分时间是无数据可读的,但是仍然花费时间反复执行read。
异步阻塞IO基于这样一个想法:先构建一张有关描述符的列表,然后使用统一的阻塞函数,当任何socket有事件通知时跳出阻塞状态。
2. selectselect就是这样一个统一的阻塞函数。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);select()可以监视多个文件描述符,直到其中的一个或一些文件描述符的IO操作准备就绪,所谓就绪是指IO操作的请求不会被阻塞。
2.1 timeout参数这个参数表示愿意阻塞等待的时间。
tvptr == NULL
永远等待,如果捕捉到一个信号则中断,当所指定的描述符中的一个或多个已经准备好或捕捉到一个信号返回,后者返回-1,errno为EINTR
tvptr->tv_sec == 0 && tvptr->tv_usec == 0
完全不等待,退化为轮询polling
tvptr->tv_sec != 0 || tvptr->tv_usec != 0
等待指定时间,当制定的描述符之一已经准备好,或指定的时间值已经超过则返回,后者返回0。与第一种情况一样,可以捕捉信号。
2.2 readfds writefds exceptfds这Sanger参数是指向描述符集的指针,说明了我们关心的可读可写或处于一场条件的各个描述符。这个描述符集使用位图的方式存储每个fd的状态。
这三个参数任意一个或者全部都可以是空指针,表示对相应状态不关心。如果都是空指针,则select提供了比sleep更精确的计时器。
2.3 FD_CLR FD_ISSET FD_SET FD_ZERO这些宏或者参数负责对描述符集的结构做一些操作:清除、测试、置位、全部清除。
2.4 返回值
-1:表示出错,不修改其中任何一个描述符
0: 描述符没有准备好,且超时
正数:表示已经准备好的描述符个数,是三个描述符集中已准备好的描述符之和。
2.5 区别select的阻塞与文件描述符的阻塞注意一个描述符阻塞与否并不影响select是否阻塞,