参考文章:9.2 I/O 多路复用:select/poll/epoll
I/O 多路复用 $\color{red}{★}$
多路复用:一个 线程/进程 处理多个io事件流的请求。select
/poll
/epoll
内核提供给用户态的多路复用系统调用,进程可以通过一个系统调用函数从内核中获取多个事件。
select
/poll
/epoll
是如何获取网络事件的呢?
在获取事件时,先把所有连接(文件描述符集合)传给内核,再由内核返回产生了事件的连接,最后在用户态中处理这些连接对应的请求即可。
select
、poll
实现多路复用的方式及区别
两者并没有本质区别,它们内部都是使用「线性结构」来存储进程关注的 Socket 集合。
方式:
- 在使用时,首先要把关注的 Socket 集合通过 select/poll 系统调用从用户态拷贝到内核态。
- 然后由内核检测事件,当有网络事件产生时,内核需要遍历进程关注的 Socket 集合,找到有事件产生的 Socket,并将其状态标记为 可读/可写。
- 然后把整个 Socket 集合从内核态拷贝到用户态,用户态还要再次遍历整个 Socket 集合找到被标记为 可读/可写 的 Socket,然后对其处理。
区别:
select
使用 BitsMap位图 的方式存储 fd_set,支持的文件描述符个数受限(由linux内核中的 FD_SETSIZE 限制),默认为 1024,只能监听 0~1023 的文件描述符。poll
则使用动态数组的方式存储 fd_set,以链表形式来组织。突破了 select 中文件描述符的个数限制,当然还是会受到系统文件描述符个数限制。
缺点:
很明显发现,select
和 poll
的缺陷在于:当客户端越多,也就是 Socket 集合越大,Socket 集合的2次遍历(时间复杂度为 O(n))和2次拷贝会带来很大的开销。
此方式随着并发数上来,性能的损耗会呈指数级增长,因此也很难应对 C10K问题(即单机1万个并发连接)。
epoll
实现多路复用的方式及区别
epoll
是解决 C10K 问题的利器,通过两个方面解决了 select
/poll
的问题。
epoll
在内核里使用「红黑树」来关注进程所有待检测的 Socket。红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。
通过对这棵黑红树的管理,不需要像 select
/poll
在每次操作时都传入整个 Socket 集合,而只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。
- epoll 使用事件驱动(回调函数)的机制,内核里维护了一个「链表」来记录有事件产生的就绪事件。
只将有事件发生的 Socket 集合传递给用户态应用程序,不需要像 select/poll 那样轮询整个集合(包含有事件和无事件的 Socket ),大大提高了检测的效率。
而且,epoll
支持边缘触发(产生事件时,仅通知一次)和水平触发(产生事件时,一直通知)的方式,而 select
/poll
只支持水平触发。一般而言,边缘触发的方式会比水平触发的效率高。