Java三大IO模型小结
# IO模型
# 什么是IO呢?
对此我们首先介绍一下夫诺伊曼提出的计算机体系结构,如下图所示,由输入和输出、以及运算器、控制器、存储器构成。
实际上IO就是计算机与外部设备数据交互的过程。为了保证操作系统的稳定性和安全性,操作系统会将空间分为用户空间和内核空间。
我们平常所运行的程序都处于用户态,当我们需要进行文件管理、进程通信、内存管理,都需要从用户态转为内核态执行才能执行这些特权指令。

所以我们应用程序执行IO时,会进行以下步骤:
- 应用程序发起
IO请求,这个特权指令会从用户态转到内核执行。 - 内核等待
IO准备好数据。 - 内核
IO数据准备完毕,将数据从内核空间拷贝用户空间。
# 深入内核理解网络数据包IO过程(重点)
# 图解网络数据包接收过程
如下图所示,我们自底向上粗略了解一下过程,首先收到一个网络包后首先会到达网卡,然后通过DMA(直接存储器访问)环形缓冲区。完成数据拷贝之后,发起一次硬中断通知CPU有网络数据来了。然后CPU将这个数据拷贝到sk_buffer,再发起一次软中断将数据向上传输。注意,若有多个CPU则哪个CPU发起硬中断,则就是哪个CPU发起软中断。
然后就是不断解数据包头的过程,数据会不断解包,以下图为例,我们得知最终的报文是一个TCP数据包,我们就会根据socket四元组将数据拷贝到socket缓冲区,反之返回一个目的不可达的icmp包。
最后数据从socket缓冲区拷贝到用户数据区read返回。

所以,性能开销大抵在这几处:
DMA拷贝到内存的开销。- 硬中断的开销。
- 软中断的开销。
- 网络数据从内核空间拷贝到用户空间的开销。
- 整个调用过程从用户态转内核态再从内核态转为内核态的过程。
# 图解网络包发送过程
发送过程和接收过程类似,整体如下图,整体步骤为:
- 应用程序发起
send请求,将数据从用户空间拷贝到内核空间。 - 封装成
struct msghdr对象加入socket队列中 - 需要发送时进行封装处理。
- 然后经过软中断、硬中断到达硬件层发送出去

# 阻塞与非阻塞是什么意思呢?
要了解阻塞和非阻塞,我们必须了解数据准备阶段和数据拷贝阶段:
- 数据准备阶段:接受方将数据从
DMA方式拷贝到内存中,再经历硬中断、软中断之后到达socket缓冲区的过程。 - 数据拷贝阶段:数据从内核空间拷贝到用户空间的过程。
有了这个概念之后,阻塞和非阻塞的概念就很好理解了。
阻塞就是在数据准备阶段和数据拷贝阶段都会阻塞。
非阻塞就数据准备阶段不会阻塞,若数据准备阶段数据没有完全接收,则系统调用会返回EWOULDBLOCK标志,当数据准备完成,数据拷贝阶段也是会阻塞的。
# 同步和非同步
同步和非同步讲述则是指数据拷贝阶段,即数据从内核空间拷贝到用户空间的这个过程。
- 同步:值得就是数据拷贝阶段由用户线程从用户态转内核态完成这个拷贝操作,操作期间是阻塞的。
Linux下的epoll和Mac下的kqueue都属于同步IO。 - 非同步:也就是我们常说的异步,即数据拷贝阶段由系统内核自行完成,完成后回调通知用户完成。
Windows中的IOCP才真正属于异步 IO,实现的也非常成熟。但Windows很少用来作为服务器使用。
# Java中常见的IO模型
# BIO(Blocking I/O)
同步阻塞IO模型,应用程序发起read请求后,会阻塞,直到数据从内核空间拷贝到用户空间完成为止。

# NIO(Non-blocking/New I/O)
Java的NIO是 I/O 多路复用模型,很多人它属于同步非阻塞模型,其实不对,对此我们不妨了解一下两者区别。
# 同步非阻塞IO模型
如下图所示,实际上同步非阻塞IO模型可以理解程序非阻塞IO+系统级别同步IO阻塞。说的通俗一些就是应用程序不断轮询调用read,操作系统准备数据完成后,告知应用程序可以开始真正的read,在数据读取期间是阻塞的。

# I/O 多路复用模型
如下图所示,I/O 多路复用模型则是基于一个线程通过系统内核提供选择器在内核去轮询对应的socket是否准备完成。当然这期间用户线程是阻塞的,但相比NIO避免了用户线程不断转换内核态和用户态这个过程的开销。
select会将用户需要监听的fd数组拷贝一份,不断去轮询,轮询查看这些fd的情况,然后返回给应用层让用户进行下一步处理。

- select调用:内核提供的系统调用,支持一次查询多个系统调用状态,几乎所有的操作系统都支持。
- epoll调用: Linux内核2.6版本提供的select增强版本,提供的IO效率。
可以说IO 多路复用模型,减少了用户线程没必要的轮询调用,减少对CPU资源的消耗。

# AIO
AIO即Java NIO2版本,是一种异步IO。工作原理是应用程序向操作系统发起IO请求,然后返回,当操作系统IO资源准备完毕后,触发回调请求,此时应用程序才发起read。AIO目前用的不是很广泛,因为AIO相比NIO性能并没有太大提升。

# 小结

# 参考文献
Java IO设计模式总结 (opens new window)
Java IO模型详解 (opens new window)