epoll 是在 linux 2.6 内核之后才引入的新技术,它也是一种 IO 多路复用技术。之前已经学过了 select 和 poll,为什么还要学习新的东西?新事物的出现必然有它的道理,来分析一下前面学习过的 select 和 poll 来说明。
1. IO 事件
实际上,前面的 select 和 poll 已经提过很多次事件的概念了,比如有可读事件,可写事件,实际上这并不很准确。
在 IO 中,内核中都有一个缓冲区。写 IO 时,数据是写到缓冲区中,而读 IO 时,是从缓冲区中读数据,所以缓冲区就是一块临时内存空间。系统这样设计,是为了提高执行效率的,因为频繁的操作硬件(比如硬盘、网卡)是相当耗时的,所以为了能减少这种操作,就使用缓冲区,等缓冲区的数据达到一定量的时候,一次性写进硬盘或网卡。
事件,是对于缓冲区来说的。
如果缓冲区中的数据发生了变化,说明有 IO 事件产生。比如从空缓冲区变非空缓冲区、缓冲区中的的数据增多、缓冲区中的数据减少等等,这些都可以产生 IO 事件。
一旦有 IO 事件产生,就意味着对应的描述符可读、可写或者发生了异常。
2. select 与 poll的缺点
- 需要自己判断哪个描述符发生事件
虽然 select 和 poll 可以帮助我们同时“监听”IO 事件(待会再详细讲解什么是事件,前面的文件也出现了很多次了),但是它有一个毛病是在 select 或 poll 返回后,需要我们自己一个一个去查询是哪个描述符上发生了 IO 事件。
回忆前面的的程序,对于 select 来说,我们要把所有的描述符都判断一遍是否在返回的描述符集合中(因为只有集合中出现的描述符才有 IO 事件发生)。本质上这局限于 fd_set 集合没有为我们提供一种遍历元素的方法。对于 poll 函数来说也是一样的,它需要我们自己去检查所有 poll_fd 数组中的描述符是否发生了事件。对于这样的操作,需要花费的复杂度是 O(n)。
epoll 则不一样,它能够将所有发生事件的描述符保存到数组中,而没有发生事件的描述符不会保存进数组的。这意味着你已经不再需要去遍历所有描述符来判断谁发生了 IO 事件。
- 描述符复制
另一个方面,当我们使用 select 或 poll 的时候,每一次都需要将想要监听的描述符传递给它。这意味着每一次调用 select 和 poll 都需要将这些描述符复制到内核空间。
而 epoll 的优点是它只要事先复制一次,以后再也不用管了。
3. epoll
说了这么多 epoll 的好处,可以大家还是没见过它,也不知道为什么它会这样好。epoll 能够做到这一点,是因为它不是一个函数,而是 3 个函数。
使用 epoll 函数的步骤通常如下:
- 首先创建一个 epoll 对象,并返回该对象的描述符
- 通过该对象的描述符,将你想要监听的描述符复制到内核
- 开始监听事件
这三个步骤对应三个函数,它们的原型如下:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
看到如此众多复杂的参数,同学们可能懵圈了,不过先不要慌乱,先用最简单的伪代码来说明怎么用,然后具体的使用方法请继续阅读后续文章。
// 先创建一个 epoll 对象,然后拿到它的描述符,参数 3 表示我想监听 3 个描述符。
epfd = epoll_create(3);
// 把你想要监听的描述符添加到 epoll 对象中。
epoll_ctl(epfd, fd0, EPOLLIN); // 监听可读事件
epoll_ctl(epfd, fd1, EPOLLIN); // 监听可读事件
epoll_ctl(epfd, fd1, EPOLLOUT); // 监听可写事件
while(1) {
// 开始监听 IO 事件,将发生事件的描述会放到事件集合 events_set 中
events_set = epoll_wait(epfd);
// 依次处理 events_set 中的所有描述符
for (evt : events_set) {
// 判断事件类型
if (evt.events & EPOLLIN) {
read(evt.fd);
}
if (evt.events & EPOLLOUT) {
write(evt.fd);
}
if (evt.events & OTHER) {
// 比如异常
}
}
}
这样看起来,我们的 epoll 似乎比 select 和 poll 要好用太多太多,是不是?
4. 总结
- 理解什么是 IO 事件
- 知道 select、poll 和 epoll 的区别
- 知道 epoll 的基本使用流程