1 疑问解答
-
为什么redis官方只支持Linux版本?
因为只有Linux支持epoll网络模型,window下的select模型在高并发下性能不如epoll。
-
epoll相比selete、poll优势点?
epoll监听的连接数基本没有限制,select所能打开的最大连接数有FD_SETSIZE宏定义限制(当然可以自己配置,但会影响性能),poll使用链表存储的没有最大连接数的限制;
高并发下IO效率问题:epoll的时间复杂度是O(1),select和poll都是O(n)
2 epoll原理
2.1 创建动作
要使用 epoll 首先需要调用 epoll_create() 函数。整个方法主要会做两件事
-
创建并初始化一个 eventpoll 对象。对象结构见eventpoll结构体
-
把 eventpoll 对象映射到一个文件句柄,并返回这个文件句柄。
struct eventpoll {
...
wait_queue_head_t wq;
...
struct list_head rdllist;
struct rb_root rbr;
...
};
-
wq: 等待队列,当调用 epoll_wait(fd) 时会把进程添加到 eventpoll 对象的 wq 等待队列中。
-
rdllist: 保存已经就绪的文件列表。
-
rbr: 使用红黑树(提高增删等维护动作效率)来管理所有被监听的文件。
2.2 向epoll添加文件句柄
通过调用 epoll_ctl() 函数可以向 epoll 添加要监听的文件。方法原型为:long epoll_ctl(int epfd, int op, int fd,struct epoll_event *event);
-
epfd: 通过调用 epoll_create() 函数返回的文件句柄。
-
op: 要进行的操作,有3个选项:
-
EPOLL_CTL_ADD:表示要进行添加操作。
-
EPOLL_CTL_DEL:表示要进行删除操作。
-
EPOLL_CTL_MOD:表示要进行修改操作。
-
-
fd: 要监听的文件句柄。
-
event: 告诉内核需要监听什么事。(可读、可写等)
2.3 等待被监听的文件状态改变
把被监听的文件句柄添加到epoll后,就可以通过调用 epoll_wait() 等待被监听的文件状态发生改变。epoll_wait() 调用会阻塞当前进程,当被监听的文件状态发生改变时,epoll_wait() 调用便会返回。
long epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
各个参数的意义:
-
epfd: 调用 epoll_create() 函数创建的epoll句柄。
-
events: 用来存放就绪文件列表。
-
maxevents: events 数组的大小。
-
timeout: 设置等待的超时时间。
2.4 整体流程
最后,我们通过一张图来总结epoll的原理:
-
通过调用 epoll_create() 函数创建并初始化一个 eventpoll 对象。
-
通过调用 epoll_ctl() 函数把被监听的文件句柄 (如socket句柄) 封装成 epitem 对象并且添加到 eventpoll 对象的红黑树中进行管理。
-
通过调用 epoll_wait() 函数等待被监听的文件状态发生改变。
-
当被监听的文件状态发生改变时(如socket接收到数据),会把文件句柄对应 epitem 对象添加到 eventpoll 对象的就绪队列 rdllist 中。并且把就绪队列的文件列表复制到 epoll_wait() 函数的 events 参数中。
-
唤醒调用 epoll_wait() 函数被阻塞(睡眠)的进程。
附:为什么使用红黑树
- 插入效率高:调用epoll_ctl监听sockt会在红黑树插入节点,而红黑树插入节点最多两次旋转,相比AVL重平衡效率更高。
- 查询效率高:红黑树最长链路不会超过最短链路两倍,查询效率高且相对平衡。
- 内存占用相对小:相比于跳表来说,红黑树指针数更少,占用更少的内存空间。