I/O多路复用之epoll
简介
I/O多路复用(I/O multiplexing)使得程序可以同时监测多个文件描述符,查看它们是否就绪(ready),即能否进行I/O操作。能够进行I/O操作,具体来说就是,在描述符上执行I/O相关的系统调用时不会阻塞,函数要么成功返回,要么返回错误,进程不会进入睡眠状态。
在Linux系统中,多路复用由select
,poll
和epoll
三个函数支持。它们的作用相同,特别地,poll
和epoll
的接口非常相似。在处理大量描述符时,epoll
的性能优于另外两个函数。epoll
同时支持水平触发(level-triggered)和边沿触发(edge-triggered)模式,而select
和poll
只支持水平触发模式。本文结合一个回射服务器程序来介绍epoll
的基本用法。
触发模式
水平触发和边沿触发模式的区别如下:
- 水平触发:如果对文件描述符执行I/O操作不会阻塞,则该文件描述符状态为就绪,内核会通知进程
- 边沿触发:当文件描述符上有新的I/O事件到来时,内核才会通知进程
如果使用水平触发模式,当内核通知文件描述符可读写时,接下来可以继续去检测它的状态,看它是否依然可读或可写。所以在收到通知后,没必要一次执行尽可能多的读写操作。
如果使用边沿触发模式,I/O事件发生时才会有通知,只有另一个I/O事件到来时才会收到新的通知。由于我们不知道到底能读写多少数据,所以在收到通知后应尽可能地读写数据,以免错失读写的机会。如果使用循环从文件描述符读写数据,且文件描述符是阻塞的,那么没有数据可读写时,进程会阻塞在读写函数那里。所以边沿触发模式一般和非阻塞I/O搭配使用,程序会一直执行I/O操作,直到系统调用(如read
和write
)返回错误,错误类型为EAGAIN
或EWOULDBLOCK
。关于这点,详情请看下面代码中的do_echo
函数。
echo服务器
我们通过一个回射服务器程序来展示epoll
的标准用法。先简要介绍epoll
的接口。主要由三个函数组成:
epoll_create
创建新的epoll
实例,一般使用最新的epoll_create1
调用epoll_ctl
管理感兴趣的文件描述符和相应事件epoll_wait
返回就绪的文件描述符,然后对它们进行I/O操作
程序的主要流程如下:
- 创建一个监听套接字
lfd
,调用set_nonblocking
函数将其设置为非阻塞 - 调用
epoll_create1
函数创建一个epoll
实例,对应的文件描述符为epfd
- 将监听套接字
lfd
加入到epfd
的事件列表event
中,监听的事件为EPOLLIN
- 进入死循环,
epoll_wait
函数一直阻塞,直到有事件发生。事件信息保存在evlist
中 - 检查文件描述符及其事件:
- 如果是发生在监听套接字
lfd
上的事件,则收到了一个客户请求,将返回的连接套接字cfd
设置为边沿触发(EPOLLET)模式,加入到事件列表event
中 - 如果是连接套接字
cfd
上发生的事件,对EPOLLIN
事件,调用do_echo
函数执行回射操作;对EPOLLERR
或EPOLLHUP
事件,则关闭描述符
- 如果是发生在监听套接字
全部代码如下:
1 |
|