三组I/O复用函数的比较
这三种函数都通过某种结构体变量告诉内核该监听那些fd上的哪些事件,并用该结果提参数来获得内核处理结果。
select
事件集合
select没有将fd和事件进行绑定,通过三个参数分别传入可读、可写及异常等事件,告诉内核监听fd上的哪些事件。内核通过对这些参数的在线修改反馈其中的就绪事件,(不能同时处理更多事件类型,每次三个)所以每次调用select都要重穿参数
实现原理和工作效率
select使用轮询的方式,即每次调用都要扫描整个文件描述符集合fd_sets,所以算法时间复杂度为O(n)
工作模式
LT
最大支持文件描述符的个数(文件描述符集合的大小)
有限
poll
事件集合
每次传入用户感兴趣的事件(事件集,统一处理所有事件类型(内核同时监听fd上的感兴趣的所有事件类型)),内核通过修改pollfd.revents反馈就绪事件
实现原理和工作效率
poll使用轮询的方式,即每次调用都要扫描整个文件描述符集合,所以算法时间复杂度为O(n)
工作模式
LT
最大支持文件描述符的个数(文件描述符集合的大小)
65535
epoll
事件集合
内核通过一个事件表直接管理用户所有感兴趣的事件,所以每次调用无需反复传入用户感兴趣的事件。调用epoll_wait反馈就绪事件events
实现原理和工作效率
epoll是采用回调的方式,内核检测到就绪的fd触发回调,回调将就绪的事件插入事件就绪队列,内核在适当的时机将就绪队列拷贝到用户空间,算法复杂度O(1)
工作模式
ET/LT
最大支持文件描述符的个数(文件描述符集合的大小)
65535
#总结
对比项 |
select |
poll |
epoll |
---|---|---|---|
事件集合 |
通过三个参数分别传入可读、可写、异常事件,不绑定 fd 与事件,每次都要重新设置 |
每次传入统一事件集合,支持所有事件类型,通过 |
内核维护事件表,注册一次即可,无需重复传入, |
实现原理与效率 |
每次都要扫描整个 |
每次都要遍历 |
内核通过回调机制将就绪事件插入就绪队列,再拷贝到用户空间,O(1) |
工作模式 |
水平触发(LT) |
水平触发(LT) |
支持水平触发(LT)和边缘触发(ET) |
fd 与事件绑定 |
不绑定,每次调用都要重新设置 |
绑定事件与 fd |
内核注册后自动跟踪 fd 的状态 |
重复传参需求 |
每次调用都需要重新设置 fd_set |
每次调用都需要重新设置 |
只需首次注册,后续 |
最大支持的文件描述符数 |
通常受限于 |
通常为 65535(理论值),由系统资源限制 |
通常为 65535(理论值),由内核资源限制 |
ET和LT
模式类型 |
通知机制 |
应用处理方式 |
是否需要非阻塞 I/O |
特点与说明 |
---|---|---|---|---|
LT(Level Trigger) |
只要缓冲区有数据就会不断通知 |
可分多次读取 |
否(推荐使用) |
容错性高,适合初学者,但效率略低 |
ET(Edge Trigger) |
状态从“无”变为“有”时才通知一次 |
一次性读/写完或读空循环处理 |
必须使用 |
高效但易出错,必须配合非阻塞 I/O 否则可能“卡死” |