I/O多路复用
I/O 多路复用
I/O多路复用支持应用同时在多个文件描述符上阻塞,并在其中某个可以读写的时候收到通知,在设计上遵循以下原则:
- I/O多路复用:当任何一个文件描述符I/O就绪时进行通知
- 都不可用?在有可用的文件描述符之前一直处于睡眠状态
- 唤醒:哪个文件描述符可用
- 处理所有准备就绪的文件描述符,没有阻塞
- 返回第一步,重新开始
select()
select()系统调用提供了一种实现同步I/O多路复用的机制:
1 |
|
在给定的文件描述符I/O准备就绪之前并且还没超出指定的时间限制,select()调用就会阻塞
监视的文件描述符分为3类,分别等待不同的事件:
- readfds:是否有数据可读
- writefds:是否有写操作可以无阻塞完成
- exceptfds:是否发生异常,或者出现带外数据
指定的集合可以是NULL,表示不监听该事件
参数timeout是指向timeval结构体的指针,定义如下:
1 |
|
宏管理
不是直接操作文件描述符,而是同过辅助宏管理
FD_ZERO从指定集合中删除所有文件描述符,每次调用select()之前,都应该调用
FD_SET向指定机中添加一个文件描述符
FD_CLR从指定集中删除一个文件描述符
FD_ISSET检查一个文件描述符是否在给定的集合中,如果在返回非0,不在返回0
返回值和错误码
select()调用成功,返回三个集合中I/O就绪的文件描述符总数。如果给出了超时设置,返回值可能是0.出错时,返回-1,并把errno值设置成如下值之一:
EBADF: 某个集合中存在非法文件描述符
EINTR: 等待时捕获了一个信号,可以重新发起调用
EINVAL: 参数n是负数,或设置超时时间值非法
ENOMEM: 没有足够的内存来完成请求
使用实例
监听stdin的输入,5s超时,虽然这个例子不是I/O复用,但是也能理解select()的用法了
1 |
|
poll()
poll()解决了一些select()的不足,不过select()还是被频繁使用(出于习惯或可移植性的考虑)
1 |
|
select()使用了基于文件描述符的三位掩码的解决方案,其效率不高;和它不同,poll()使用了由nfds个pollfd结构体构成的数组,fds指针指向该数组。pollfd结构体定义如下:
1 |
|
每个pollfd结构体指定一个被监视的文件描述符,可以给poll()传递多个pollfd结构体,使它能够监视多个文件描述符
events和revents
每个结构体的events变量是要监视的文件描述符的时间的位掩码,用户可以设置该变量。revents变量时该文件描述符的结果的事件的位掩码。以下是合法的events值:
变量 | 描述 |
---|---|
POLLIN | 有数据可读 |
POLLRDNORM | 有普通数据可读 |
POLLRDBAND | 有优先数据可读 |
POLLPRI | 有高优先数据可读 |
POLLOUT | 写操作不会阻塞 |
POLLWRNORM | 写普通数据不会阻塞 |
POLLBAND | 写优先数据不会阻塞 |
POLLMSG | 有SIGPOLL消息可用 |
除了场面events的位掩码外,revents还会返回如下事件:
变量 | 描述 |
---|---|
POLLER | 给定的文件描述符出现错误 |
POLLHUP | 给定的文件描述符有挂起事件 |
POLLNVAL | 给定的文件描述符非法 |
例如,设置某个文件描述符是否可读写,需要把events设置成POLLIN | POLLOUT。返回时,会检查revents中是否有相应的标志位。如果设置了POLLIN,文件描述符可非阻塞读。如果设置了POLLOUT,文件描述符可非阻塞写
timeout 参数指定等待的时间长度,单位是毫秒,如果为负数,表示永远等待;如果为0,则立即返回,并给出所有I/O未就绪的文件描述符列表,不会等待更多事件
返回值和错误码
poll()调用成功时,返回revents变量不为0的所有文件描述符个数;如果没有任何事件发生且未超时,返回0.失败时返回-1,并相应设置errno值如下:
EBADF: 某个集合中存在非法文件描述符
EFAULT: fds指针指向额地址超出了进程地址空间
EINTR: 等待时捕获了一个信号,可以重新发起调用
EINVAL: 参数n是负数,或设置超时时间值非法
ENOMEM: 没有足够的内存来完成请求
实列
1 |
|
运行后。结果如下:
1 |
|
在实际应用中,不用每次调用时都重新构建pollfd结构体。该结构体可能会被重复传递多次,内核会在必要时把revents清空。
poll()和select()的区别
虽然poll()和select()完成相同的工作,但poll()调用在很多方面仍然优于select()调用
- poll()不需要用户计算最大文件描述符值加1作为参数传递给它
- poll()对于值很大的文件描述符,效率更高
- select()的文件描述符集合是静态的,需要对大小设置进行权衡:如果值很小,会限制可监视的最大文件描述符值;如果很大,效率会很低
- 对于select()调用,返回时会重新创建文件描述符集,因此每次调用都必须重新初始化。poll()调用会把输入(events)和输出(revents)分离开,支持无需改变数组就可以重新使用
- select()调用的timeout参数在返回时是未定义的,代码要支持可移植,需要重新初始化
不过,select()调用也有些有点:
- select()可移植性更好,因为有些UNIX系统不支持poll()
- select()提供了更高的超时精度:select()支持微秒级,poll()支持毫秒级