⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 my.oschina.net/u/1859679/blog/1839169 「wier」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

这是【NIO系列】第二篇,欢迎持续关注:

《【NIO 系列】——之 TCP 探秘》

上一篇我们讲到了关于TCP/IP协议的一些内容,这些是网络编程的必备知识。在了解NIO之前我们必须要了解一下对应的系统层IO模型,比如java的NIO对应是那种IO模型,阻塞和同步的差异在哪里,又是否相同。了解了这些更方便我们的后续的NIO探解。

一、同步、异步、阻塞、非阻塞

同步、异步,阻塞、非阻塞,这四种状态常有人分不清,主要是这四种状态的定义本身也不是很明确,所以各种解答的方式都有。常见的分类有以下:

  1. 同步阻塞IO
  2. 同步非阻塞IO
  3. 异步非阻塞IO

针对某种IO模型,我们如何分类,可以基于POSIX对同步/异步的定义来判别:

- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;

- An asynchronous I/O operation does not cause the requesting process to be blocked;

那么从上我们可以看出:

阻塞:是否阻塞主要体现在调用的线程是否可以干别的,关注的是程序的等待状态

同步:是否同步体现在消息通信机制上

也就是说同步和异步说的是消息的通知机制,阻塞非阻塞说的是线程的状态 。

如果说以上的定义依然无法判别,我们可以从输入操作的两个阶段来看:

一般来说,一个输入操作通常包括两个不同阶段:

(1)等待数据准备好;(2)从内核向进程复制数据。

是否同步的判断依据是:是否针对的是整个过程,也就是2个阶段,是否有阻塞。

是否阻塞的判断依据是:按程序(线程)等待消息通知时的状态角度来说的,也就是主要是针对第一阶段来说。

举例

我们举例来说:

比如说做饭这件事,一般要分为连个步骤。

1、买菜,准备食材

2、炒菜,做出饭菜

方案一:自己动手处理。

1、去超市买菜,准备食材(阻塞,当前时段只能做一件事,且需要持续的等待)

2、回家切菜,炒菜,做出美味饭菜(阻塞,还是自己来处理)

评价: 方案一同步阻塞。首先阶段一是阻塞的,所以认定为阻塞,两个阶段都是阻塞的,认定为同步的。

方案二:盒马配送食材,自己做饭

1、网上下单,盒马配送食材,快递到了会敲门联系你。(非阻塞的,这期间你可以干其他事)

2、拿到菜,切菜、炒菜,做出美味饭菜(阻塞)

评价:方案二为同步非阻塞。阶段一为非阻塞,认定为非阻塞。阶段二为阻塞,两阶段中有一个为阻塞,认定为同步。

方案三:盒马配送,请阿姨做饭

1、网上下单,盒马配送食材,快递到了会敲门联系你。(非阻塞的,这期间你可以干其他事)

2、网上请阿姨小时工,帮忙做这一餐,做好通知我。(非阻塞,期间可以干其他事)

评价:方案三为异步非阻塞。阶段一为非阻塞,认定为非阻塞。阶段二非阻塞,则两阶段中都没有阻塞,认定为异步。

那么是否有异步阻塞IO模型,没有,要记得异步状态是包含二个阶段的,如果有阻塞的过程,为何还叫异步?

网上有很多介绍有异步阻塞模型的,我目前查到的资料还没有这个证明,若有找到相关论文,还请指教。目前我认为没有这个模型的。

二、Unix 5种I/O模型

《UNIX网络编程:卷一》的第六章书中列出了五种IO模型:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用(select,poll,epoll...);
  • 信号驱动式I/O(SIGIO);
  • 异步I/O(POSIX的aio_系列函数);

1.阻塞式I/O

同步阻塞 IO 模型是最常用的一个模型,也是最简单的模型。在linux中,默认情况下所有的socket都是blocking。它符合人们最常见的思考逻辑。

在这个IO模型中,用户空间的应用程序执行一个系统调用(recvform),这会导致应用程序阻塞,什么也不干,直到数据准备好,等待kernel准备好从网络上接收到的数据报 + 等待收到的报文被从kernel复制到buf中,recvfrom方法才会返回,最后进程再处理数据。

这就是阻塞式IO模型

2.非阻塞式I/O

非阻塞IO时对一个非阻塞描述符循环调用recvfrom,持续的轮询(polling),以查看某个操作是否就绪。与阻塞IO不一样,"非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 '被' CPU光顾"。

非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。如此循环的进行recvform系统调用,检查内核数据,直到数据准备好,再拷贝数据到进程。拷贝数据整个过程,进程仍然是属于阻塞的状态

这就是非阻塞式IO模型

3.I/O复用

IO multiplexing就是我们说的select,poll,epoll 。为何叫多路复用,是因为它I/O多路复用可以同时监听多个fd,如此就减少了为每个需要监听的fd开启线程的开销。

select调用是内核级别的,可以等待多个socket,能实现同时对多个IO端口进行监听,当其中任何一个socket的数据准好了,就能返回进行可读然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,这个过程是阻塞的。

I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这几个函数可以同时阻塞多个I/O操作`。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时(不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户进程来处理),才真正调用I/O操作函数。

IO复用有人把其成为同步非阻塞的,也有称为同步阻塞。其实这个是否阻塞还需要看第一个阶段,第一个阶段有的阻塞,有的不阻塞。主要也是阻塞在select阶段,属于用户主动等待阶段,我们且规范为阻塞状态,所以,把IO多路复用归为同步阻塞模式

这是IO复用的模型:

select、poll、epoll的不同

4.信号驱动式I/O

信号驱动式I/O:首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

也就是说第一个阶段,完全是非阻塞的,等数据到达会给一个信号通知,第二个阶段recvfrom还是阻塞过程,和之上无差异。

信号驱动式I/O 过程如下:

5.异步I/O

异步IO不是顺序执行,用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知IO两个阶段,进程都是非阻塞的

总结

针对这5中IO模型,我采用一张图来总结一下。

三、java IO

Unix中的五种I/O模型,除信号驱动I/O外,Java对其它四种I/O模型都有所支持。其中Java最早提供的blocking I/O即是同步阻塞I/O,而NIO即是同步非阻塞I/O,同时通过NIO实现的Reactor模式即是I/O复用模型的实现,通过AIO实现的Proactor模式即是异步I/O模型的实现。

所以说严格意义上来说,通过Reactor模式实现的NIO,和unix中的I/O多路复用是相同的概念,但这是一种编程模型,而不是原生支持。这也是我们下面所要进行的netty讲解的主要思想。

文章目录
  1. 1. 一、同步、异步、阻塞、非阻塞
    1. 1.1. 举例
  2. 2. 二、Unix 5种I/O模型
    1. 2.1. 1.阻塞式I/O
    2. 2.2. 2.非阻塞式I/O
    3. 2.3. 3.I/O复用
      1. 2.3.0.1. select、poll、epoll的不同
  3. 2.4. 4.信号驱动式I/O
  4. 2.5. 5.异步I/O
  5. 2.6. 总结
  • 3. 三、java IO