一文详解IO多路复用!
一文详解IO多路复用!
月伴飞鱼I/O多路复用通过一种机制,可以监视多个描述符。
一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select,poll,epoll都是IO多路复用的机制。
- 但select,poll,epoll本质上都是同步I/O。
因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
select:
它仅仅知道了,有 I/O 事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部)。
只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
所以select 具有 O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
它是基于数组来存储的,它有最大连接数的限制。
poll:
poll 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态。
但是它没有最大连接数的限制,原因是它是基于链表来存储的。
epoll:
epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述符。
把需要监控的 socket 通过
epoll_ct()
函数加入到内核中的红黑树里。
- 红黑树是个高效的数据结构,增删查一般时间复杂度都是
O(logn)
。通过对这颗红黑树进行操作,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合。
- 只需要传入一个待检测的socket,减少了内核和用户空间大量的数据拷贝和内存分配。
epoll 使用事件驱动的机制,内核里面维护了一个链表来记录就绪事件。
当某个 socket 有事件发生时,通过回调函数内核会将其将入到这个就绪事件列表中。
当用户调用
epoll_wait()
时,只会返回有事件发生的文件描述符的个数。
- 不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。
epoll 相关接口的作用如下:
epoll_create()
,epoll_ctl()
和epoll_wait()
系统调用。
epoll 的方式监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常多。
- 上限就为系统定义的进程打开的最大文件描述符个数。
epoll 支持两种事件触发模式,分别是边缘触发和水平触发。
边缘触发
使用边缘触发时,当被监控的 Socket 描述符有可读事件发生时,服务器端只会从
epoll_wait
中苏醒一次。
- 即时进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此程序要保证一次性将内核缓冲区的数据读取完。
水平触发
使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务端不断地从
epoll_wait
中苏醒。
- 知道内核缓冲区数据被 read 函数读完才结束,目的是告诉有数据需要读取。
select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。