epoll通过下面3个epoll系统调用为用户提供服务。
(1)epoll_create系统调用
epoll_create在C库中的原型如下:
int epoll_create(int size);
epoll_create返回一个句柄,之后epoll的使用都将依靠这个句柄来标识。参数size是告诉epoll所要处理的大致事件数目。不再使用epoll时,必须调用close关闭句柄。
注:size参数只是告诉内核这个epoll对象会处理的事件大致数目,而不是能够处理的事件的最大个数。在Linux最新的一些内核版本的实现中,这个size参数没有任何意义。
(2)epoll_ctl系统调用
epoll_ctl在C库中的原型如下:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl向epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回-1,此时需要根据errno错误码判断错误类型。epoll_wait方法返回的事件必然是通过epoll_ctl添加到epoll中的。
参数epfd是epoll_create返回的句柄,而op参数的意义见表:
op的取值 | 意义 |
EPOLL_CTL_ADD | 添加新的事件到epoll中 |
EPOLL_CTL_MOD | 修改epoll中的事件 |
EPOLL_CTL_DEL | 删除epoll中的事件 |
第3个参数fd是待监测的连接套接字,第4个参数是在告诉epoll对什么样的事件感兴趣,它使用了epoll_event结构体,如下是epoll_event的定义:
struct epoll_event {
__uint32_t events;
epoll_data_t data;
};
events的取值如下:
events取值 | 意义 |
EPOLLIN | 表示对应的连接上有数据可读(TCP连接的远端主动关闭连接,也相当于可读事件,因为需要处理发送来的FIN包) |
EPOLLOUT | 表示对应的连接上可写入数据发送(主动向上游服务器发起非阻塞的TCP连接,连接建立成功的事件相当于可写事件) |
EPOLLRDHUP | 表示TCP连接的远端关闭或半关闭连接 |
EPOLLPRI | 表示对应的连接上有紧急的数据需要读取 |
EPOLLERR | 表示对应的连接发生错误 |
EPOLLHUP | 表示对应的连接被挂起 |
EPOLLET | 表示将触发方式设置为边缘触发ET,系统默认为水平触发LT |
EPOLLONESHOT | 表示对这个事件智处理一次,下次需要处理时需重新加入epoll |
而data成员是一个epoll_data联合,其定义如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
可见,这个data成员还与具体的使用方式有关。
(3) eplll_wait系统调用
epoll_wait在C库中的原型如下:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
收集在epoll监控的事件中已经发生的事件,如果epoll中没有任何一个事件发生,则最多等待timeout毫秒后返回。epoll_wait的返回值表示当前发生的事件个数,如果返回0,则表示本次调用中没有事件发生,如果返回-1,则表示出现错误,需要检查errno错误码判断错误类型。
第1个参数epfd是epoll的描述符。
第2个参数events则是分配好的epoll_event结构体数组,epoll将会把发生的事件复制到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮我们在用户态中分配内存。内核这种做法效率很高)。
第3个参数maxevents表示本次可以返回的最大事件数目,通常maxevents参数与预分配的events数组的大小是相等的。
第4个参数timeout表示在没有检测到事件发生时最多等待事件,单位毫秒,如果timeout为0,则表示epoll_wait在rdllist为空时,立刻返回,不会等待。
epoll有两种工作模式:LT(水平触发)模式和ET(边缘触发)模式。默认情况下epoll采用LT模式工作,这时可以处理阻塞和非阻塞套接字,上表中EPOLLET表示可以将一个事件改为ET模式。ET模式的效率比LT模式的效率高,它只支持非阻塞套接字。ET模式与LT模式的区别在于:
当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次如果没有把这个事件对应的套接字缓冲区处理完,在这个套接字没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的;
而LT模式则相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
因此在LT模式下开发基于epoll的应用要简单一些,不太容易出错,而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。
默认情况下Nginx是通过ET模式使用epoll的。