Undertow的NIO使用XNIO组件,这一篇介绍什么是NIO,以及XNIO的基础。
Java NIO 是由 Java 1.4 引进的异步 IO,Java NIO 由以下几个核心部分组成:
- Channel
- Buffer
- Selector
IO 和 NIO 的区别主要体现在三个方面:
- IO 基于流(Stream oriented), 而 NIO 基于 Buffer (Buffer oriented)
- IO 操作是阻塞的, 而 NIO 操作是非阻塞的
- IO 没有 selector 概念, 而 NIO 有 selector 概念
- 基于 Stream 与基于 Buffer
传统的 IO 是面向字节流或字符流的,而在 NIO 中,我们抛弃了传统的 IO 流,而是引入了 Channel 和 Buffer 的概念。在 NIO 中,我只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。
那么什么是 基于流 呢? 在一般的 Java IO 操作中,我们以流式的方式顺序地从一个 Stream 中读取一个或多个字节,因此我们也就不能随意改变读取指针的位置。
而 基于 Buffer 就显得有点不同了,我们首先需要从 Channel 中读取数据到 Buffer 中,当 Buffer 中有数据后,我们就可以对这些数据进行操作了,不像 IO 那样是顺序操作,NIO 中我们可以随意地读取任意位置的数据。
- 阻塞和非阻塞
Java 提供的各种 Stream 操作都是阻塞的, 例如我们调用一个 read 方法读取一个文件的内容, 那么调用 read 的线程会被阻塞住, 直到 read 操作完成。
而 NIO 的非阻塞模式允许我们非阻塞地进行 IO 操作。例如我们需要从网络中读取数据,在 NIO 的非阻塞模式中,当我们调用 read 方法时,如果此时有数据,则 read 读取并返回,如果此时没有数据,则 read 直接返回,而不会阻塞当前线程。
- selector
selector 是 NIO 中才有的概念, 它是 Java NIO 之所以可以非阻塞地进行 IO 操作的关键。
通过 Selector, 一个线程可以监听多个 Channel 的 IO 事件, 当我们向一个 Selector 中注册了 Channel 后, Selector 内部的机制就可以自动地为我们不断地查询(select) 这些注册的 Channel 是否有已就绪的 IO 事件(例如可读, 可写, 网络连接完成等)。通过这样的 Selector 机制,我们就可以很简单地使用一个线程高效地管理多个 Channel 了。
XNIO基于JDK NIO扩展和封装,提供一套包含阻塞和非阻塞IO的简单API,不用麻烦的操作Selector,XNIO有下面特点:
- Channel,是传输管道的抽象概念,在NIO的Channel上进行的扩展加强,使用ChannelListener API进行事件通知。在创建Channel时,就赋予IO线程,用于执行所有的ChannelListener回调方法。
- 区分IO线程和工作线程,创建一个工作线程池可以用来执行阻塞任务。一般情况下,非阻塞的Handler由IO线程执行,而阻塞任务比如Servlet则被调度到工作线程池执行。这样就很好的区分了阻塞和非阻塞的两种情形。
- 对ByteBuffer进行池化管理。简单来说就是开发者在程序开始时就计划好读写缓存区大小,统一分配好放到池中,Xnio中有Pool和Pooled接口用来管理池化缓存区。以前在接收网络数据时,往往简单的new出一块数据区,填充,解析,使用,最后丢弃,这种方法随着大量的数据读入,必然造成GC反复出现。重用缓存区就可以在这个方面解决一部分问题。
业界主要流行的NIO框架有Netty和Mina,关于Undertow为什么使用XNIO,官方的答复如下:
简单来说,就是XNIO跟Undertow都是JBoss下面的产品,并且XNIO提供的功能刚好跟Undertow要求的契合,还有XNIO有更好的向后兼容能力,这点对Undertow是很重要的概念,所以选择XNIO作为Undertow底层的NIO实现框架。