5种 io模型
阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO
前四个都是同步IO,在内核数据copy到用户程序时都是阻塞的,而第五个则是异步的
NIO即 Non-Block IO, 使用多路复用模型实现 单线程 多通道。
I/O 多路复用主要由以下实现
select 它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符,将数据从kernel复制到进程缓冲区。select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select单个进程能够监视的文件描述符的数量存在最大限制,一般为1024(64位为 2048)。随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll 它和select在本质上没有多大差别,但poll使用链表来存储文件描述符,没有数量限制。poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
- epoll epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。
select | poll | epoll | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 哈希表 |
IO效率 | 每次调用都进行线性遍历,时间复杂度为O(n) | 每次调用都进行线性遍历,时间复杂度为O(n) | 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) |
最大连接数 | 1024(x86)或2048(x64) | 无上限 | 无上限 |
fd拷贝 | 每次调用select,都需要把fd集合从用户态拷贝到内核态 | 每次调用poll,都需要把fd集合从用户态拷贝到内核态 | 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝 |
在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点:
1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好
,毕竟epoll的通知机制需要很多函数回调。
2、select低效是因为每次它都需要轮询
。但低效也是相对的,视情况而定,也可通过良好的设计改善。
Reference
https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20IO.md