Linux 高性能服务 epoll 的本质,真的不简单(含实例源码)
内容提要
本文介绍了epoll在Linux系统中的原理和使用方法,epoll是一种高效的事件驱动方式,可以处理大量并发连接。与select和poll不同,epoll在内核中申请了一个简易的文件系统,将事件的处理分成了三个步骤:epoll_create、epoll_ctl和epoll_wait。通过这种方式,只需要在进程启动时建立一个epoll对象,并在需要的时候向其中添加或删除连接即可。epoll的两种触发模式分别是LT(水平触发)和ET(边缘触发),其中ET模式只有数据到来才触发,而LT模式只要有数据都会触发。最后,文章介绍了epoll的反应堆模型和相关的代码示例。
延伸解读
从select/poll到epoll:设计思路的转变
文章指出,select和poll每次调用都需要把全部监控的套接字从用户态复制到内核态,并由内核遍历查找就绪连接。在百万连接仅少数活跃的场景下,这种全量传递和遍历造成巨大浪费。epoll将监控注册与事件收集分离,通过epoll_ctl维护关注集合,epoll_wait只检查就绪链表,从而避免了每次调用时的全量数据传递。这种设计思路的转变是epoll高效的关键。
红黑树与就绪链表:epoll高效的内核基础
epoll在内核中用红黑树存储所有被监控的事件,保证添加、删除和查找的较高效率;同时用双向链表rdllist保存已就绪的事件。当设备事件发生时,内核回调ep_poll_callback将对应epitem放入就绪链表。epoll_wait只需检查该链表是否为空,有数据则复制到用户态并返回。这种结构使得epoll_wait无需遍历全部连接,因而能支撑百万级并发。
LT与ET模式的选择与使用注意
LT是默认模式,只要文件描述符还有数据可读,每次epoll_wait都会返回该事件;ET模式仅在数据到来时通知一次,若未读完缓冲区剩余数据,下次epoll_wait不会再次通知。文章强调,ET模式下必须将数据一直读到errno返回EAGAIN为止,且文件描述符需设为非阻塞,否则最后一次读写可能阻塞。ET可避免大量不关心的就绪事件反复返回,但使用不当容易丢事件。
反应堆模型:用回调组织事件处理流程
文章展示的反应堆模型将每个文件描述符封装为自定义事件结构,包含回调函数和状态。当监听套接字就绪时,accept新连接并将其挂到红黑树监听读事件;读事件就绪时调用recvdata读取数据,随后摘除读监听并改为监听写事件;写事件就绪时调用senddata回射数据,再切回监听读事件。这种回调驱动方式使事件处理流程清晰,但需注意状态管理和事件切换的正确性。
Q&A
epoll 为什么比 select 和 poll 更高效?
epoll 在内核中创建了一个简易的文件系统,将事件处理分为 epoll_create、epoll_ctl 和 epoll_wait 三个步骤。它不需要每次调用时都把全部连接的套接字从用户态复制到内核态,内核也无需遍历所有连接,而是通过红黑树管理注册的事件,并用就绪链表存放已触发的事件,因此 epoll_wait 只需检查链表是否为空,效率极高,能轻松处理百万级并发连接。
epoll 的使用步骤是怎样的?
epoll 的使用分为三步:1) 调用 epoll_create 创建一个 epoll 对象(句柄);2) 调用 epoll_ctl 向 epoll 对象中添加、修改或删除需要监控的套接字事件;3) 调用 epoll_wait 等待并收集发生事件的连接。通常只需在进程启动时创建 epoll 对象,之后动态增删连接即可。
epoll 的 LT 和 ET 触发模式有什么区别?
LT(水平触发)是默认模式,只要文件描述符上还有数据可读,每次 epoll_wait 都会返回该事件,提醒程序继续处理;ET(边缘触发)是高速模式,仅在数据到来时触发一次通知,如果一次没有读完数据,下次 epoll_wait 不会再次通知,直到有新的数据到来。ET 模式效率更高,但要求程序必须一次将数据读写完(通常需配合非阻塞 IO),否则可能丢失事件。
epoll 反应堆模型是如何工作的?
epoll 反应堆模型通过回调机制处理就绪事件。主循环调用 epoll_wait 等待事件,当有连接到来时,监听套接字触发 acceptconn 回调,将新连接挂到红黑树上监听读事件;当读事件就绪时,触发 recvdata 回调读取数据,然后将该连接改为监听写事件;写事件就绪时,触发 senddata 回调发送数据,再改回监听读事件。如此循环,每个文件描述符绑定相应的回调函数,实现高效的事件驱动处理。
epoll 在内核中使用了哪些数据结构来管理事件?
epoll 在内核中使用 eventpoll 结构体,其中包含一个红黑树(rbr)用于存储所有注册的监控事件,以及一个双向链表(rdllist)用于存放已就绪的事件。每个事件对应一个 epitem 结构体,同时作为红黑树节点和链表节点。当事件就绪时,内核回调函数 ep_poll_callback 会将对应的 epitem 插入到 rdllist 中,epoll_wait 只需检查该链表即可快速返回就绪事件。
在 ET 模式下编程需要注意什么?
在 ET 模式下,当 epoll_wait 通知某个文件描述符可读时,必须循环读取直到返回 EAGAIN(表示数据已读完),否则剩余数据不会再次触发通知,可能导致事件丢失。同时,通常需要将文件描述符设置为非阻塞模式,以避免在最后一次读取时阻塞。写事件同理,需一次写完或处理部分写的情况。