系统调用
epoll_create
:创建一个 epoll 实例
1 |
|
epoll_create 中的参数 size 已被忽略,但它必须是大于零的值。
epoll_ctl
:用于在 epoll
实例中添加、修改或删除文件描述符
1 |
|
epfd
: epoll_create 返回的 epoll
实例的文件描述符
op
:
操作类型,指定你想要执行的操作,可以是以下之一:
- EPOLL_CTL_ADD:添加文件描述符到 epoll 实例中
- EPOLL_CTL_MOD:修改已存在的文件描述符的事件
- EPOLL_CTL_DEL:从 epoll 实例中删除文件描述符
fd
:
需要添加(监视)、修改或删除(不再监视)的文件描述符
event
: 指向 epoll_event
结构体的指针,定义了你希望监视的事件(读写事件)及事件发生时的处理方式
epoll_wait
:等待 epoll 实例中的事件发生
1 |
|
epfd
: epoll_create 返回的 epoll
实例的文件描述符
events
: 指向 epoll_event
结构体数组的指针。epoll_wait 会将发生的事件填充到这个数组中
maxevents
: events
数组的大小,表示最多可以返回多少个事件
timeout
:
超时时间(以毫秒为单位)。如果设置为 -1,epoll_wait
会无限等待直到有事件发生;如果设置为 0,epoll_wait
会立即返回,不会阻塞;其他值表示超时时间
当你调用 epoll_wait
时,它会阻塞,直到有事件发生,或者超时,或者被中断。调用成功的返回值是返回实际发生的事件数,这些实际发生的事件存储在
events 事件数组中,从这里取出来事件并处理。
epoll_ctl 和 epoll_wait 都出现 epoll_event 结构体
1 |
|
events
:
你希望监视的事件类型,可以是以下之一(或者它们的组合):
- EPOLLIN:可读事件(常用)
- EPOLLOUT:可写事件(常用)
- EPOLLERR:错误事件
- EPOLLHUP:挂起事件
- EPOLLET:边缘触发模式(不设置默认是水平触发)
- EPOLLONESHOT:一次性事件
data
:
你可以用来存储用户数据,通常是一个 union,可以存储 int、ptr
或其他数据类型
代码地址:群聊
原理
1 |
|
epoll_create 完成两项工作:
- 创建并初始化一个
eventpoll
对象。 - 把
eventpoll
对象映射到一个文件句柄,并返回这个文件句柄。

我们实际看看内核中 evnetpoll 对象的核心成员:
1 |
|
epoll_ctl 操作 rbr 来管理节点,epoll_wait 就是拷贝 rallist 得到就绪集合。

可以看到 rbr 上是一个一个的 epitem,但我们监听 文件描述符的时候,需要封装为 epitem。
1 |
|
通过 epoll_ctl 中的 EPOLL_CTL_ADD 操作把 epitem 添加到监听集合中,并且设置一个回调函数。当 socket 状态发生变化时,会触发调用这个回调函数,主要工作是把就绪的文件描述符添加到 eventpool 的就绪集合中,然后唤醒调用 epoll_wait 阻塞的进程。
哪个文件描述符触发,就会加入到就绪集合,内核也就知道具体是哪个文件描述符有变化,而之前的 select 方法是无法做到这点的。

我们要把内核中的就绪集合拷贝到用户态,需要创建一个 epoll_event 用以接收,然后调用 epoll_wait 来完成拷贝。
1 |
|
epoll_wait 接触阻塞有三种情况:
- 被监听的文件集合中有就绪的文件
- 设置了超时时间并且超时了
- 接收到信号

至此,归档如下:
- 通过调用
epoll_create()
函数创建并初始化一个eventpoll
对象。 - 通过调用
epoll_ctl()
函数把被监听的文件句柄 (如socket句柄) 封装成epitem
对象并且添加到eventpoll
对象的红黑树中进行管理。 - 通过调用
epoll_wait()
函数等待被监听的文件状态发生改变。 - 当被监听的文件状态发生改变时(如socket接收到数据),会把文件句柄对应
epitem
对象添加到eventpoll
对象的就绪队列rdllist
中。并且把就绪队列的文件列表复制到epoll_wait()
函数的events
参数中。 - 唤醒调用
epoll_wait()
函数被阻塞(睡眠)的进程。

注意点
- 我们要监听一个文件描述符,一定要封装成 epoll_event,再加入监听。
- epoll_wait 需要提供一个 epoll_event 数组,用来存储 内核中的就绪集合。
- 对于不需要监听的文件描述符,记得通过 EPOLL_CTL_DEL 移除。尽管不移除好像也没关系,按理不被关心的它永远不会被触发了,但基于代码的严谨,应该移除。
- 水平触发和边缘触发。