待编辑中…
一、前言
在《Unix网络编程》一书中提到了五种IO模型,分别是:阻塞IO(blocking IO)、非阻塞IO(nonblocking IO)、多路复用IO(IO multiplexing)、信号驱动IO(signal driven IO)以及异步IO(asynchronous IO)。
IO发生时涉及到的对象和步骤:
- 对于网络IO而言(以read操作为例),涉及到的两个对象:用户进程(或者线程)和 系统内核。
- 当read操作发生时,它会经历两个阶段:
1)等待数据准备(Waiting for the data to be ready)
2)将数据从内核拷贝到用户进程中(Copying the data from the kernel to the process)
这两点很重要,因为这些IO模型的主要区别在这两个阶段各有不同。
二、五种网络IO模型
1. BIO
1.1. 定义/概念
当用户程序中发起read操作(即底层的recvfrom系统调用)后,内核进入第一个阶段,即等待数据就绪,而用户程序线程则被阻塞住。当数据准备就绪后,则内核将数据拷贝到用户程序缓冲区中并返回结果(或者发生错误时才返回结果),用户进程才解除阻塞状态。IO执行的两个阶段(等待数据和拷贝数据)用户线程都被阻塞住了。
1.2. 优/缺点
每连接每线程。连接少的情况下表现良好,但高并发情况下,创建的线程很多而导致资源消耗和性能降低。
主要问题:
1)线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统耗不起。
2)线程切换效率低下:频繁进行线程切换(包括保护现场和恢复现场等操作),使应用性能急剧下降。
3)以字节流为单位,效率不高。
(关于线程资源受限的优化:
可以通过伪异步方式优化:即服务端维护线程池,将请求由其中的线程处理,但达到一定并发上限后,性能也会达到其瓶颈。)
为了解决这三个问题,JDK在1.4之后提出了NIO。
2. NIO
2.1. 定义/概念
从用户程序中发起read操作(即底层的recvfrom系统调用)后,用户程序不需要等待,而立即收到一个结果,一般用户程序中轮询检查这个结果状态,如果是个error(EWOULDBLOCK),则表明内核中数据还没准备好,于是再次发送read操作。而内核中一旦数据准备就绪又再次收到用户请求,则内核将数据拷贝到用户缓存区中并返回OK。
2.2. 优/缺点
非阻塞IO中,用户程序通过死循环不断询问内核是否数据准备就绪,即用户线程不会释放CPU执行权限,而导致CPU占用率升高!因此一般情况下很少使用循环方式来读取数据。
3. 多路复用IO
3.1. 定义/概念
从用户程序中发起.。。。
3.2. 优/缺点
4. 异步IO
4.1. 定义/概念
从用户程序中发起.。。。
4.2. 优/缺点
5. 异步IO
5.1. 定义/概念
从用户程序中发起.。。。
5.2. 优/缺点
三、参考
聊聊Linux 五种IO模型
Java NIO1:I/O模型概述
五种IO模型详解
5种网络IO模型(有图,很清楚)
NIO概述
《跟闪电侠学Netty》开篇:Netty是什么?
附:
- Linux文件描述符的理解:
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //进程级打开文件描述符表
};
打开文件描述符表:实际上就是files_struct 中的成员struct file * fd_array[NR_OPEN_DEFAULT]它是一个指针数组,数组每一个元素都是一个指向file类型的指针,可想而知,这些指针都会指向一个打开的文件,并且file这一数据结构就是用来描述一个打开的文件的,而我们所说的文件描述符,实际上就是这个指针数组的索引。这也是为什么文件描述符是非负整数。
参考:
Linux文件描述符的理解