1.epoll相关函数接口
1.创建一个epoll句柄
int epoll_create(int size);
注意:用完之后,必须调用close()关闭
2.注册epoll事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
- 第一个参数是epoll_create()的返回值(epoll的句柄).
- 第二个参数表示动作,用三个宏来表示.
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd;
- 第三个参数是需要监听的fd.
- 第四个参数是告诉内核需要监听什么。
- 收集在epoll监控的事件中已经发送的事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 参数events是分配好的epoll_event结构体数组。
- epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存)。
- maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size。
- 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞). 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函 数失败。
2. epoll的优点
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文 件描述符, 也做到了输入输出参数分离开。
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不 频繁(而select/poll都是每次循环都要进行拷贝) 。
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述 符数目很多, 效率也不会受到影响。
- 没有数量限制: 文件描述符数目无上限。
3.LT与ET模式
来看这样一个例子:
我们已经把一个tcp socket添加到epoll描述符 这个时候socket的另一端被写入了2KB的数据 调用epoll_wait,并且它会返回. 说明它已经准备好读取操作 然后调用read, 只读取了1KB的数据 继续调用epoll_wait。
3.1 LT
LT即Level Triggered 水平触发,epoll默认工作模式就是LT模式。
- 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.
- 如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪。
- 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回。
- 支持阻塞读写和非阻塞读写。
3.2 ET
- 当epoll检测到socket上事件就绪时, 必须立刻处理。
- 如上面的例子, 虽然只读了1K的数据, 缓冲区还- 剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了。
- 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会. ET的性能比LT性能更高( - epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll。
- 只支持非阻塞的读写。