Java NIO(一)-I/O模型: 阻塞、非阻塞、I/O复用、
目的##
为后期学习 Netty框架打好理论基础,并且在分布式RPC 服务中对客户端与服务端之间服务的调用,底层数据通讯可以使用Netty 进行封装。
记录结构##
- Java NIO(一)--I/O模型: 阻塞、非阻塞、I/O复用、同步、异步
地址:http://www.jianshu.com/writer#/notebooks/5970279/notes/7531041/preview - Java NIO(二)--Channel、Buffer、Selector
地址:待定 - Java NIO(三)--多路复用之TCP传输中的NIO应用
地址:待定
今天记录I/O模型中的阻塞、非阻塞、I/O复用、同步、异步。
混沌的概念##
对于上述四种概念,常常使我陷入混沌,我经常这样想同步不就是阻塞的么?异步不就是非阻塞的么?
我也会去看下别人写的博客以验证我的想法是正确的,事实上,大多博客也这样举例子:同步类似于你叫某人吃饭,某人不应答你,你就一直等着他,这期间你什么事情都不能做,也就是说你在等待某人去吃饭这个时刻内一直都是阻塞的。
针对于上述的举例,我一直深信不疑,一句话,没毛病。
但直到我看到UNIX 网络编程之后,才发现理解有偏差,起码我之前的理解是将I/O操作中的概念结合起来记忆。即:同步==阻塞 异步==非阻塞。
但其实,以上的记忆有点以偏概全,或者说根本没有清晰的认识。
话不多说,开始记录。
1. 明确I/O考察的对象和流程##
1.1 参考Unix网络编程,一个输入操作通常包括两个不同的阶段:
- 等待数据准备好;
- 从内核向进程复制数据。
1.2 对于一个套接字的输入操作:
- 通常涉及等待数据从网络到达,当所等待分组到达时,被复制到内核的某个缓冲区;
- 把数据从内核缓冲区复制到应用进程缓冲区。
注意: 理解上述两个不同阶段对于后续理解I/O模型尤其是非阻塞I/O与同步I/O关系十分必要。
2. I/O模型##
2.1 阻塞式I/O模型
阻塞式I/O是最流行的I/O,也是所有套接字默认的I/O。Java BIO中对socket 网络数据通信的
封装 就采用的是这种方式。当然效率也是低下的。
阻塞式I/O是最流行的I/O,也是所有套接字默认的I/O。
阻塞式I/O.png(注:所有图片来源 Unix网络编程卷1,第三版)
如图所示,进程调用recvfrom系统调用,直到网络数据报到达且被复制到应用进程缓冲区中或发生错误才返回。
注意:也就是说,进程从调用recvfrom开始到返回的整个时段都是阻塞的(上述1.1两个阶段都是阻塞),recvfrom成功返回后,应用进程才开始处理数据报。
上述的注意读三遍
2.2 非阻塞I/O模型
直接上图:
如图所示,不同于阻塞式I/O,非阻塞I/O在第一阶段数据没有准备好的时候,不阻塞,而是直接返回一个错误(EWOULDBLOCK)。
所以一般采用轮询(polling)的方式,应用进程持续轮询内核,查看数据是否准备好。当数据准备好时,被复制到应用进程缓冲区(第二阶段)。
注意:值得注意的一点是,当第一阶段数据准备完成后,进入第二阶段,内核向内存的复制。这一阶段仍然是阻塞的,这对于后续理解非阻塞与同步的关系十分重要。
上述注意项读三遍。
2.3 I/O多路复用模型
I/O复用最常见的就是select和epoll,其阻塞发生在上述两个系统调用之一,而不是真正的I/O系统调用上。 Java NIO 对TCP 网络通信的封装内部采用的就是这种原理。
当用户进程调用了select,那么整个进程会被阻塞与select。内核会“监视”所有select负责的套接字,当任何一个套接字中的数据准备好了,select就会返回。(进程阻塞)
这时候进入第二阶段,完成内核向内存的数据复制。(进程阻塞)
注意:I/O复用的优势在于同时等待多个描述符就绪,单就一个描述符可言,其没有优势,反而还会因为多一次select系统调用存在劣势。
上述注意项读三遍。
2.4 异步I/O模型
异步I/O的工作机制是告知内核启动某个操作,并让内核在整个操作(包括第二阶段数据从内核向内存的复制)完成后告知我们。
如下图所示:
异步I/O 模型.png注意:异步I/O要通过调用特殊API实现(如POSIX的aio_read),可以看出,其在两个阶段都是没有对于用户进程的阻塞的,依靠信号通知进程整个过程完成。
上述注意读三遍
2.5 同步、异步与阻塞、非阻塞、I/O复用的关系
在了解了阻塞式I/O、非阻塞式I/O、I/O多路复用、异步I/O后我们看下这几个模式的I/O模型与同步异步模型有什么关系。
注意:重头戏,接下来就是彻底领悟这几个概念之间关系,让你不再混沌,请保持接受状态,保持信心看下去。
首先先来再明确一下同步、异步I/O之间的区别。
书中所述,POSIX把两种术语定义如下:
同步I/O:导致请求进程阻塞,直到I/O操作完成;(两个阶段(等待网络数据到达内核空间缓存区域,以及将内核空间缓存区域中的数据复制到用户进程缓存中)中只要有一个阶段阻塞,那整个I/O操作就就是同步)
异步I/O:不导致请求进程阻塞。 (两个阶段都不阻塞,那么就是异步I/O)
注意:所以说,阻塞式I/O, 非阻塞I/O, I/O复用由于都导致了请求进程阻塞,所以均属于同步I/O。
(值得注意的是非阻塞I/O,正如之前提示要注意的,其在第二阶段内核向内存复制数据是会导致用户进程的阻塞,所以也属于同步I/O
上述注意读三遍
3. 总结
如下图所示:(暂时忽略信号驱动I/O)
可以看出阻塞式、非阻塞式、与I/O复用,其不同之处在于第一阶段,第二阶段的处理方式相同(均阻塞与recvfrom调用),这也是刚才说到的将他们归于同步I/O的原因。
注意:异步I/O不存在请求进程阻塞的情况。同时注意前三种I/O模型在第一阶段的处理方式(阻塞,返回+轮询,阻塞于select等),区分这三种I/O模型。
上述注意读三遍
完。