I/O复用
-
select
#include<sys/select.h> int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout); // 被监听的文件描述符的总数,可读、可写、异常事件对应文件描述符,select函数的超时时间
socket可读事件:
- socket内核接收缓冲区字节数大于等于其低水平标记
- socket通信对方关闭连接
- 监听socket有新的连接请求
- socket上有未处理的错误
socket可写事件:
- socket内核发送缓冲区可用字节数大于等于其低水平标记
- socket写操作被关闭
- socket使用非阻塞connect连接成功或失败之后
- socket上有未处理的错误
select可处理异常事件:
- socket上接收到带外数据
-
poll
和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
#include<poll.h> int poll(struct pollfd* fds, nfds_t nfds, int timeout); // 文件描述符事件集合,被监听事件集合fds的大小,超时值 struct pollfd { int fd; // 文件描述符 short events; // 注册的事件 short revents; // 实际发生的事件,由内核填充 }
-
epoll
在内核中维护一个事件表,并提供一个独立的系统调用epoll_ctl来控制往其中添加、删除、修改事件。这样每次epoll_wait调用都直接从该内核事件表中取得用户注册的事件,而无需反复从用户空间读入这些事件。
#include<sys/epoll.h> int epoll_create(int size); int epoll_ctl(int epfd, int op, int fs, struct epoll_event* event); int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout);
电平触发(LT):采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,应用程序下次调用epoll_wait时,epoll_wait还会再次通告,直至该事件被处理。
边沿触发(ET):采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率比LT高。
EPOLLONESHOT事件:对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读可写或异常事件且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。
-
对比
select和poll都只能工作在相对低效的LT工作模式,而epoll可以工作在ET高效模式下,并且epoll还支持EPOLLONESHOT事件,该事件能进一步减少可读、可写、异常等事件触发的次数。
从实现原理上来说,select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此它们检测就绪时间的算法的时间复杂度为O(n)。epoll_wait采用的是回调的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入到内核就绪事件队列,内核最后在适当的时机将该就绪队列中的内容拷贝到用户空间,时间复杂度为O(1)。但是,当活动较多的时候,回调函数被触发的过于频繁,epoll_wait效率未必比select、poll效率高。所以,epoll_wait适用于连接数量多,但活动连接较少的情况。