博客主页:花果山~程序猿-CSDN博客
关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!
目录
嗨!收到一张超美的图,愿你每天都能顺心!
一,接口
epoll_create
epoll_create(size_t size)
用于创建一个epoll
文件描述符,返回一个非负整数表示新创建的epoll
实例的文件描述符。size
是一个建议值,表示最初能容纳多少个事件,但实际上内核可能会忽略此参数。
- 参数:
size
:建议的初始事件槽的数量,但在现代内核版本中此参数几乎无用,内核会根据需要动态调整。
epoll_ctl
epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
用于向epoll
实例添加、修改或删除文件描述符的监听事件。
- 参数:
epfd
:epoll_create
返回的epoll
文件描述符。op
:操作类型,可以是EPOLL_CTL_ADD
(添加)、EPOLL_CTL_MOD
(修改)、EPOLL_CTL_DEL
(删除)。fd
:需要操作的文件描述符。event
:指向struct epoll_event
结构体的指针,包含需要监控的事件类型。
event 事件类型:
- EPOLLIN - 表示描述符可读(例如,有数据可读取)。
- EPOLLOUT - 表示描述符可写(例如,可以发送数据)。
- EPOLLERR - 表示描述符有错误。
- EPOLLHUP - 表示描述符挂起(例如,对端关闭了连接)。
- EPOLLET - 这是一个边缘触发模式标志,不是事件类型,但它可以与其他事件类型结合使用,以改变事件检测的行为。
- EPOLLONESHOT - 这个标志让 epoll_wait() 在第一次匹配到这个事件后就不再为这个文件描述符报告该事件,直到 epoll_ctl() 再次修改此文件描述符的监听条件。
- EPOLLEXCLUSIVE - 当设置此标志时,如果多个进程或线程尝试等待同一个事件,那么仅有一个等待者会被唤醒
epoll_wait
epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
等待已注册文件描述符上的I/O事件发生,并返回就绪事件的数目。
- 参数:
epfd
:epoll_create
返回的epoll
文件描述符。events
:一个指向epoll_event
数组的指针,用于返回就绪事件。maxevents
:最大可返回的就绪事件数。timeout
:等待的超时时间(毫秒为单位)。如果设置为负数或0,则epoll_wait
立即返回;如果大于0,则表示等待的时间。
结构体epoll_event:
从底层原理理解三接口负责的功能图:
当某一进程调用 epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件,这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度). 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
在epoll中,对于每一个事件,都会建立一个epitem结构体.
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
如果 rdlist 不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户 . 这个操作的时间复杂度是O(1)。
如何理解参数epfd的作用?
总结一下, epoll的使用过程就是三部曲:
- 调用epoll_create创建一个epoll句柄;
- 调用epoll_ctl, 将要监控的文件描述符进行注册;
- 调用epoll_wait, 等待文件描述符就绪;
二,epoll优点(相较select,poll)
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件,描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响。
- 没有数量限制: 文件描述符数目无上限
三,epoll有2种工作方式
epoll 有 2 种工作方式 - 水平触发 (LT) 和边缘触发 (ET)
如何理解两种工作方式:(快递员例子)
LT: 当你的外卖(数据)到时,外卖员(底层)会一直给你打电话(通知)直到你下来将你的所有外卖都取走(数据拿走 )。ET: 外卖来时,外卖员(底层)只给你打一次电话,你如果不下来取,外卖员(底层)不会再通知你,你的外卖(数据)就再也拿不到了。
比较标准的解释:
水平触发Level Triggered 工作模式
epoll 默认状态下就是 LT 工作模式:
- 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分. 如由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait 仍然会立刻返回并通知socket读事件就绪. 直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.(一直通知你直到数据全部取走)
- 支持阻塞读写和非阻塞读写
边缘触发Edge Triggered工作模式
如果我们在第 1 步将 socket 添加到 epoll 描述符的时候 使用了EPOLLET标志, epoll进入ET工作模式.
- 当epoll检测到socket上事件就绪时, 必须立刻处理. 如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了. 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.(ET模式下,只有一次处理机会,这样倒逼程序员,要一次取完所有的数据)
- ET的性能比LT性能更高( 相同的运行时间内epoll_wait 返回的次数少了很多== 无效通知减少 == 增加其他socket通知的数量). Nginx默认采用ET模式使用epoll.
- 只支持非阻塞的读写
select 和 poll 其实也是工作在 LT 模式下 . epoll 既可以支持 LT, 也可以支持ET。
在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话 , 其实性能也是一样的 . 但是另一方面, ET 的代码复杂程度更高了。
epoll使用场景
epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反.
- 对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll.
例如 , 典型的一个需要处理上万个客户端的服务器 , 例如各种互联网 APP的入口服务器 , 这样的服务器就很适合 epoll. 如果 只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适 . 具体要根 据需求和场景特点来决定使用哪种IO 模型。
epoll中的惊群问题(选学)
惊群问题有些面试官可能会问到 . 建议同学们课后自己查阅资料了解一下问题的解决方案。
参考 http://blog.csdn.net/fsmiy/article/details/36873357
ET模式使用思路
1.epoll_ctl时添加的文件描述符,需要添加设置 EPOLLET,这样一旦事件就绪,通过epoll_wait报告一次。
2.将需要设置的fd,如listen_socket,设置为非阻塞式;在通过accept系统调用时进行轮询,直到资源被全部提取后,才结束轮询。(采用非阻塞式,就是为了避免一次未取完资源,ET模式下,事件不再通知,导致事件资源丢失)->可参考fcntl接口的非阻塞例子
在优化web服务器为epollET,后面持续更新中...
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源泉。