IO多路复用( 四 )


目前来看,高性能的web服务器都不会使用和poll 。他们俩存在的意义仅仅是“兼容性”,因为很多操作系统都实现了这两个系统调用 。
如果是追求性能的话,在BSD/macOS上提供了 api;在中提供了/dev/poll(可惜该操作系统已经凉凉);而在Linux上提供了epoll api 。它们的出现彻底解决了和poll的问题 。Java NIO,nginx等在对应的平台的上都是使用这些api实现 。
因为大部分情况下我会用Linux做服务器,所以下文以Linux epoll为例子来解释多路复用是怎么工作的 。
用epoll实现的IO多路复用
epoll是Linux下的IO多路复用的实现 。这里单开一章是因为它非常有代表性,并且Linux也是目前最广泛被作为服务器的操作系统 。细致的了解epoll对整个IO多路复用的工作原理非常有帮助 。
与和poll不同,要使用epoll是需要先创建一下的 。
int epfd = epoll_create(10);
在内核层创建了一个数据表,接口会返回一个“epoll的文件描述符”指向这个表 。注意,接口参数是一个表达要监听事件列表的长度的数值 。但不用太在意,因为epoll内部随后会根据事件注册和事件注销动态调整epoll中表格的大小 。
epoll创建
为什么epoll要创建一个用文件描述符来指向的表呢?这里有两个好处:
epoll创建后,第二步是使用接口来注册要监听的事件 。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
其中第一个参数就是上面创建的epfd 。第二个参数op表示如何对文件名进行操作,共有3种 。
第三个参数是要操作的fd,这里必须是支持NIO的fd(比如) 。
第四个参数是一个的类型的数据,表达了注册的事件的具体信息 。
typedef union epoll_data {
void*ptr;
intfd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;

struct epoll_event {
uint32_tevents;/* Epoll events */
epoll_data_t data;/* User data variable */
};

比方说,想关注一个fd1的读取事件事件,并采用边缘触发(下文会解释什么是边缘触发),大概要这么写:
struct epoll_data ev;
ev.events = EPOLLIN | EPOLLET; // EPOLLIN表示读事件;EPOLLET表示边缘触发
ev.data.fd = fd1;

通过就可以灵活的注册/取消注册/修改注册某个fd的某些事件 。
管理fd事件注册