type
status
date
slug
summary
tags
category
icon
password
我们首先来看一张图(来自The Linux Programming Interface),这张图直观地展示了
select
,poll
,epoll
三种不同的 I/O 多路复用技术的性能差异。
从图中可以明显地看到,
epoll
的性能是最好的,即使在监听多达 10000 个文件描述符的情况下,其性能和监听 10 个文件描述符相比,差别也不大。而随着文件描述符的增大,select
和 poll
的性能逐渐变得很差。那么,
epoll
究竟使用了什么“魔法”,有如此惊人的效果呢?接下来,我们就一起来看一下吧…1. epoll
的使用
首先,我们通过编写一个聊天室服务器的例子认识一下
epoll
。使用
epoll
编写网络程序需要三个步骤:分别是epoll_create
epoll_ctl
和epoll_wait
。接下来,我们详细讲解一下这三个 API。1.1 epoll_create()
epoll_create()
函数会创建一个 epoll 实例,并返回一个文件描述符指向该 epoll 实例。参数说明
size
: 自Linux 2.6.8,参数size
将被忽略,但是仍需传入一个大于 0 的整数。这其实是一个历史包袱:在早期的
epoll_create
实现中,size
参数表示应用程序期望监控的文件描述符的数量,然后内核会根据size
来初始化内核的数据结构。但在新的实现中,内核可以动态分配所需的数据结构,因此就不再需要这个参数了。我们只需注意,将size
设置成一个大于 0 的整数就可以了。1.2 epoll_ctl
epoll_ctl()
函数是 epoll 的控制接口,它可以往 epoll 实例中添加一个文件描述符,从epoll 实例中删除一个文件描述符,或者修改和某个文件描述符关联的事件。参数
epfd
: 指向 epoll 实例的文件描述符。op
: 操作,可取值有EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
。fd
: 要添加、修改或删除的文件描述符。event
: 与文件描述符fd
关联的事件。如果op
的值为EPOLL_CTL_DEL
,event
参数则被忽略。struct epoll_event
的定义如下:首先,我们来看一下
struct epoll_event
的events
成员,它表示监视事件的类型,常见的取值有以下几种:EPOLLIN
: 所关联的文件可读。
EPOLLOUT
: 所关联的文件可写。
EPOLLRDHUP
: 适用于stream socket,表示对方已关闭连接,或者对方关闭了写端。
EPOLLET
: 设置该文件监听的事件为边缘触发(edge-triggered),默认为水平触发(level-triggered)。(边缘触发和水平触发的区别,下面会将…)
struct epoll_event
的data
成员是用户自己设置的数据。我们一般会设置这个联合(union)里的fd
字段,将其设置为所关联的文件描述符fd
。1.3 epoll_wait
epoll_wait()
函数类似之前讲过的select()
和poll()
,等待内核 I/O 事件的分发。如果没有事件就绪,调用线程则会被挂起。参数
epfd
: 指向 epoll 实例的文件描述符。events
: 这是一个数组,当epoll_wait()
返回的时候,里面存放的就是已经就绪的事件。maxevents
: epoll_wait()
可以返回的最大事件数目,一般设置events
数组的长度。timeout
: 超时时间 (ms)。-1 表示永久等待,0 表示立马返回,不等待。2. 经典示例——聊天室
3. 水平触发 VS 边缘触发
水平触发(level-triggered)和边缘触发(edge-triggered)这两个术语来自于电子电路科学。其中水平触发的意思是,一个信号的持续状态(而不是其变化)触发某个事件的发生,比如如果有数据可读,就会一直触发
EPOLLIN
事件。而边缘触发的意思是,事件是由信号的变化触发的,而不是由信号的持续状态触发。举个例子,再边缘触发机制下,当数据到达时,会触发
EPOLLIN
事件;尽管这些数据没有被读取,之后也不会再触发EPOLLIN
事件了。[demo演示]边缘触发会导致一些小问题:明明有数据可读,却不会触发
EPOLLIN
事件,导致应用程序陷入阻塞。为了避免这类现象发生,边缘触发往往会配合非阻塞I/O一起使用,最佳实践如下:- 配合非阻塞I/O使用。
- 一直
read
和write
,直到这两个操作返回EAGAIN
。
[使用边缘触发改写聊天室服务器]
Q: So, 水平触发和边缘触发哪个会更好呢?
A: 一般来说,我们认为边缘触发的效率会更高,因为它可以有效地减少事件触发的次数。但是相应的代码复杂性也会更高,因为它得配合非阻塞I/O一起使用,并且我们得一直读和写,直到返回
EAGAIN
。而在水平触发模式(默认),epoll
仅仅是一个更高效版本的poll
。epoll
的边缘触发模式是构建高性能服务器的有力杀手锏之一。Q:
select
和poll
是水平触发还是边缘触发?
A: 自己写一个小程序测试一下:)归纳总结
epoll
的出现,为Linux高性能网络编程补齐了最后一块拼图。epoll
避免了用户态—内核态频繁的数据拷贝,大大提高了性能。在使用epoll
的时候,我们一定要理解条件触发和边缘触发两种模式。其中边缘触发模式是构建高性能服务器的有力杀手锏之一,著名的http服务器nginx就是基于这种模式构建的。参考文章
欢迎您在底部评论区留言,我们一起交流学习~
- 作者:Thomas He
- 链接:https://notion-next-lovat-ten.vercel.app/article/networkprogramming/8
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章