在Java Socket通信中,Java NIO是一个很重要的底层实现类,理解其核心组件和使用方法,将会帮助了解更多在Java NIO基础之上搭建的其它Socket通信应用,例如Netty IO等。
本文将对Java NIO 核心组件做一个简单介绍,读完本文,你将可以读Java NIO有个总体上的认识,并能够通过代码样例,编程实现一个多线程异步消息通信。
1. Java NIO概览
Java NIO全称是Java New Input/Output,是很早在JDK1.4中引入的输入输出类库,之后在JDK7中提供了升级版的NIO2,提供了异步编程模型的IO操作。下文若没有说明,Java NIO是指在JDK7之后的最新NIO类库。
有一个首先需要了解的问题是,为什么会有NIO?和之前的IO类库相比,其带来了哪些优点和改进?
在JDK1.4之前,所有IO操作都是基于流(stream)进行数据读写,其读写操作方法会阻塞,这将大大影响程序的效率。Java NIO一方面解决了阻塞的问题,另一方面对数据块存储、数据处理方面进行了抽象,提供了缓存区、通信通道和选择器三个组件概念,使得程序开发者可以在更深入的层次上介入IO处理过程,对相关的通信进行多线程优化,编程可扩展性大大提高。
下表列出IO和NIO在主要特性上的区别对比,
IO | NIO | |
---|---|---|
操作对象 | 面向流 | 面向缓存区 |
操作特性 | 单向操作,或读,或写 | 支持读写双向操作 |
读写单位 | 按字节一一进行读/写 Bytes | 按指定块存储大小进行读/写 Block Buffer |
支持非阻塞 | 只支持阻塞读写 | 支持阻塞和非阻塞读写 |
通信通道 | 无,一个流本身就是一个通道 | 基于通道进行数据传输 |
单线程 多通道 | 不支持 | 支持,通过选择器实现一个线程处理多个通道 |
异步编程 | 不支持 | 支持异步编程模型(自JDK7) |
可以看到Java NIO在IO操作上进行了很大幅度的改进和提升。
在Java NIO类库中,有四个核心组件接口,
- Channel通道
- Buffer缓存区
- Selector 选择器
- CompletionHandler 异步回调处理器
本文将对这四个组件逐一进行讲解,了解其作用和使用方法,并提供简单的代码使用样例。最后给出一个多线程异步通信的使用样例。
2. 通道Channel
一个通道是数据传输的连接,可以和IO设备建立连接,进行数据的获取和传送。在一定程度上,可以认为通道是流的升级版实现。
整个Java NIO中最常用的通道类有如下几个,
- FileChannel,since 1.4
- DatagramChannel,since 1.4
- SocketChannel,since 1.4
- ServerSocketChannel,since 1.4
- AsynchronousFileChannel,since 1.7
- AsynchronousSocketChannel,since 1.7
- AsynchronousServerSocketChannel,since 1.7
其中Asynchronous*这几个类是在JDK7开始提供的NIO 2.0类库,主要提供了异步编程模型,详细见后面异步回调处理器的讨论。
(注:AsynchronousDatagramChannel在JDK7中并没有提供,原先是有准备发布这个类库的,但是由于某些原因,在发布前被删除。)
通道类中使用最多的四个方法是,
- Channel.bind() 绑定连接
- Channel.accept() 建立连接
- Channel.read(buffer) 把数据从缓冲区读到通道
- Channel.write(buffer) 把数据从通道写到缓冲区
一个简单的代码样例如下,
// please note: configure channel blocking state as false
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false);
System.out.println("A server is started on port 9000");
ServerSocket serverSocket = server.socket();
serverSocket.bind(new InetSocketAddress(9000));
// please note: server.accept() will not block current thread
// Accept method will return null directly if no client is connected
SocketChannel channel = null;
int count = 0;
while (channel == null) {
System.out.println("The server is trying to connect client, count=" + count++);
channel = server.accept();
Thread.sleep(500);
}
//channel.read(ByteBuffer)
System.out.println("the end");
channel.close();
server.close();
3. 缓冲区Buffer
Java NIO是基于块进行IO操作,缓冲区就是对这个块的抽象定义,在这个缓冲区中可以反复进行读写,通过通道实现数据的传输。
我们先看看缓冲区是什么样子,一个缓冲区是一个有指定大小的数组,其有读模式和写模式两种状态,请注意在读写不同模式下其Position和Limit的位置指向,
上图的缓冲区中显示了如下三个属性,
- Capacity:缓冲区容量大小
- Position:缓冲区的读写位置,根据当前读/写模式,含义如下
- 在读模式下,读的当前位置
- 在写模式下,写的当前位置
- Limit:缓冲区的读写限制位,根据当前读/写模式,含义如下
- 在读模式下,可以读的最大位置,等于当前缓冲区内数据量,防止读溢出
- 在写模式下,可以写的最大位置,其实就等于Capacity值,防止写溢出