目录
前言
select系统调用
poll系统调用
epoll系统调用
epoll_create
epoll_ctl
epoll_wait
LT和ET模式
EPOLLONESHOT事件
epoll和select/poll的区别
事件集处理方式
实现原理和效率
其他区别
在第三章中我们大概地讲解了什么是I/O复用,即:I/O复用技术即使用select,poll,epoll等系统调用,让主线程能同时监听多个文件描述符,提高服务器运行的效率。
PS: 虽然I/O复用能同时监听多个文件描述符,但是其本身是阻塞的,当主线程已经监听到多个文件描述符时,如果不采用并发技术,那么程序也只能按顺序一个一个去处理这些文件描述符。
在本章中,我们将依次介绍select,poll,epoll系统调用,基于简单且重要的原则,本章不会详细介绍select和poll,但他们依然是面试高频考点,希望读者能自行去了解一下。本章将重点介绍epoll,同时介绍epoll中两种重要的触发模式LT水平触发模式和ET边沿触发模式,这些内容是第三章中提到的是I/O处理单元中的核心内容,需要读者理解且记忆。
#include
int select(int nfds,fd_set* readfds, fd_set* writefds,
fd_set* exceptfds,struct timeval* timeout);
其中fd_set结构指针类型和struct timeval指针类型感兴趣可以自行搜索。
select系统调用中的文件描述符只有在可读,可写或异常的条件下才被计入就绪文件描述符,而这些具体条件感兴趣的读者自行搜索。
#include
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
struct pollfd{
int fd;/*文件描述符*/
short events;/*注册的事件*/
short revents;/*实际发生的事件,由内核填充*/
epoll是非常高效的I/O复用函数,它不是一个函数,而是一组函数,即
epoll把文件描述符上的事件放入一个内核事件表里,而不是像select和poll那样每次调用重复传入文件描述符,但epll需要一个额外的文件描述符来标识这个内核事件表。
#include
int epoll_create(int size);
创建好了标识内核事件表的文件描述符,即可使用epoll_ctr对内核事件表进行设置
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
struct epoll_event
{
_uint32_t events;
epoll_data_t data;
}
事件类型(poll类型的,epoll在poll类型的事件类型前面 + ”E";
创建并设置好内核事件表后,即可使用epoll_wait()开始等待事件
#include
int epoll_wait(int epfd, struct epoll_event* events,int maxevents,int timeout);
epoll_wait函数检测到事件后,会将内核注册表中的就绪事件放入第二个数组中,当工作线程来数组中取事件处理时,就不需要判断事件是否是就绪事件,而poll则需要先判断是否就绪。
可以这样理解,文件描述符上的事件是一堆未处理的食物,而I/O复用函数作用是将这些食物放入一格一格的容器中。poll容器不管食物是否烹饪过,直接放进去。而epoll会有一个大厨(内核)来将烹饪过的食物放进去。在食客挑选食物食用时(工作线程逻辑处理),如果是poll容器,则需要一个格子一个格子打开判断食物是否可以食用,而epoll容器中都是可以直接食用的食物,食客直接打开食用即可。
epoll对文件描述符的操作又两种模式,LT模式ET模式。
LT模式(水平触发):LT是默认的工作模式,这种模式epoll相当于一个高效的poll
ET模式(边沿触发):当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll会切换成ET模式
LT模式:当epoll_wait检测到文件描述符上有事件发生并通知应用程序后,应用程序可以不立即处理事件,当下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到这个事件被处理。
ET模式:当epoll_wait检测到文件描述符上有事件发生并通知应用程序后,应用程序必须立即处理事件,后续epoll_wait不再通知
LT模式和ET模式可以这样理解,还是上面那个例子,EPOLL容器安装了一个语音呼叫装置,当容器某一格内有食物(就绪事件)时,它就会呼叫食客来取餐。LT模式下,食客可以不来用餐,这样当主线程循环一遍后又一次调用EPOLL时,它又会再次呼叫食客来取走上次未取走的那个格子里的食物。而ET模式下,EPOLL容器一呼叫,食客就必须来取餐。
在使用epoll时可能遇到这样的问题,在某个线程读取完某个socket上的数据开始处理时,这个socket上又来了新的数据,这时另一个线程被唤醒处理这些数据,那么就出现了两个线程处理同一个socket的情况。为了避免这种情况,可以使用EPOLLONESHOT事件。
一个注册了EPOLLONESHOT事件的文件描述符,操作系统最多可以触发其上的一个事件,且只能触发一次。除非用epoll_ctl函数重置该文件描述符上的EPOLLONESHOT事件。反过来,当一个线程处理完一个socket上的事件,就该立刻重置其上的EPOLLONESHOT事件,保证下次其他线程能处理这个socket。
select:使用fd_set。
fd_set并未将文件描述符和事件绑定,所以需要三个参数来分别传入可读,可写和异常事件。且由于内核对fd_set集合为在线修改,应用程序下次调用select前需要重置三个参数
poll:使用pollfd
pollfd内定义文件描述符和事件,所有事件都统一处理;内核每次修改的时revent成员,而event成员不变,所以不用重置事件集参数。
epoll:
在内核中维护事件表,用独立系统调用epoll_ctr来控制事件表,epoll_wait调用直接从内核时间表中取得注册的事件,无需反复从用户空间读入这些事件。
select/poll:
采用轮询的方式,每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,时间复杂度O(n)
epoll:
采用回调的方式,内核检测到就绪的文件描述符时触发回调函数,回调函数将文件描述符上的对应事件加入内核就绪队列,内核在合适的时机将就绪事件队列中的拷贝到用户空间。时间复杂度是O(1)
工作模式:
select/poll只有LT模式
epoll可以使用ET模式
最大文件描述符数:
select:有最大值,且比poll和epoll少
poll/epoll:65353