@
目录I/O模式
- 阻塞I/O
- 非阻塞I/O
- I/O多路复用
- 信号驱动I/O
- 异步I/O
I/O多路复用
I/O 多路复用 相较于多进程多线程技术区别在于一个进程或线程可以处理多个事件。I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知相应的进程/线程进行相应操作
select、poll、epoll
都是IO多路复用的机制,它们可以同时监控多个fd
的操作
文件描述符(fd):内核利用文件描述符来访问文件。文件描述符是非负整数。打开文件或新建文件时,内核会返回一个文件描述符。
select
将多个fd
放到一个文件描述符集合(数组),将其拷贝到内核 bitmap
中同时监视多个fd
只要有任何一个数据状态准备就绪了,就将其标记为可读或可写,再接着把整个文件描述符集合拷贝回用户态,用户态还需要通过遍历找到可读或可写的fd
,然后对其处理。
缺点:
-
select
这种方式需要进行2次 遍历 文件描述符集合,一次在内核态里,一次在用户态里。 - 还会发生2次 拷贝 文件描述符集合,先从用户空间传到内核空间,由内核修改后,再传出到用户空间。
-
select
使⽤固定⻓度的BitsMap
表示文件描述符集合,个数是有限制的,最多为 1024 个。
poll
poll
和 select
并没有太大本质区别,只是不再用 BitsMap
来存储所关注的⽂件描述符,而是用动态数组,以链表的形式来组织,不再有最大文件描述符数量的限制。
epoll
epoll
在内核中保存了一份文件描述符集合,底层数据结构是红黑树,增删查时间复杂度都是O(logn),通过对这颗红黑树进行操作,就不需要像select/poll
每次操作都从用户态传入整个文件描述符集合到内核态,减少了内核和用户空间的数据拷贝和内存分配
内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒,将其通过回调函数加入到一个就绪队列中(双向链表),当返回时只返回就绪队列中有I/O事件的文件描述符,所以不需要像select/poll
那样扫描整个集合,大大提高了检测效率。
事件触发模式
epoll
支持两种事件触发模式,分别是 边缘触发 和 水平触发
- 边缘触发(ET)
使用边缘触发模式时,当监控的文件描述符有可读写事件发生时,服务器只会从epoll_wait中苏醒一次。
也就是说每有一个可读写事件发生,服务器就只会苏醒一次,就算一次没有读取完也不会再次苏醒。
因此我们程序要保证⼀次性将内核缓冲区的数据读取完;所以边缘触发模式一般和非阻塞I/O搭配使用
- 水平触发(LT)
使用水平触发模式时,当监控的文件描述符有可读写事件发生时,epoll_wait会通知处理程序去读写,如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么会一直通知你告诉你,直到内核缓冲区读取完
参考