什么是 I/O 多路复用:
I/O 多路复用指的是可以通过某种机制,监视多个文件描述符,一旦某个文件描述符准备就绪后,就能够通知程序进行相关的读写操作,目前 I/O 多路复用机制主要包含三种:select、poll、epoll
select:
select 函数监视的文件描述符包含三类:writefds、readfds、exceptfds,当调用 select 函数后,会有阻塞,直到有相关的文件描述符准备就绪,select 函数返回,当返回后,可以通过遍历 fdset 获取相关的已经准备就绪的文件描述符
select 函数的本质是通过设置或者检查存放 fd 标志位的数据结构进行下一次的处理,select 机制包含有几个缺点:
- 单个进程能打开的文件描述符有数量限制,由 FD_SETSIZE 控制,默认是 1024
- 扫描 socket 的时候,采用的是线性扫描,也就是轮询的方式,这样的话效率比较低
- 需要维护一个用于保存大量 fd 的数据结构,这样的话会使得在用户态和内核空间之间传递该数据结构的时候增大复制的开销
poll:
poll 和 select 在本质上没有太大的差别, poll 将用户传入的数组拷贝至内核空间,查询每一个 fd 对应的设备状态,如果 fd 对应的设备状态处于准备就绪,则会在设备等待列表中存入一项继续遍历,如果遍历完所有 fd 后发现没有处于准备就绪状态的设备,那么会将当前进程挂起,直到有相关设备处于准备就绪状态,再次被唤醒,等被唤醒后需要再次进行多次遍历
poll 机制中没有最大连接数的限制,因为它采用的是链表方式存储,但是这样做会有一些缺陷:
- 大量的 fd 的数组被整体复制于用户空间和内核地址空间中,而不管这些复制是不是有意义
- poll 还有一个特点,支持水平触发,如果 fd 被上报但是没有被处理,那么下一次 poll 时会继续上报这个 fd
epoll:
epoll 是 select 和 poll 的增强版本,epoll 相比较于 select 和 poll 更加的灵活,epoll 使用一个文件描述符管理多个文件描述符,将用户关系的文件描述符的事件保存在一个内核的时间列表中,这样在用户空间和内核空间只需要复制一次
epoll:
epoll 支持水平触发和边缘触发,最大的优点在于边缘触发,它只会告诉进程刚刚变为准备就绪状态的 fd,并且只会通知一次,还有一个特点是,它采用的是事件就绪的通知方式,通过 epoll_ctl 注册 fd,一旦 fd 准备就绪后,内核会采用类似 callback 回调函数的机制激活 fd,然后 epoll_await 等待通知
epoll的优点:
- 没有最大并发连接数的限制,打开 fd 的上限远高于 1024
- epoll 的效率高,没有采用轮询的方式,不会随着 fd 的数目增加而降低效率,只有处于活跃状态的 fd 才会调用 callback 函数,epoll 最大的特点就在于它只管活跃状态的连接,和连接总数没有关系,这也是在实际网络中 epoll 要比 poll 和 select 效率高的原因