NIO 中主要的三个概念为缓冲区、通道、选择器,它们之间的关系如下所示:
此处要提醒的是,JDK 1.7 升级了 NIO 类库,升级后的 NIO 类库被称为 NIO2.0。在 NIO2.0 中,提供了异步文件I/O操作,同时提供了与 UNIX 网络编程事件驱动I/O对应的 AIO。
在之前《Java NIO 缓冲区》一文中已经介绍过缓冲区的相关知识,本文主要介绍通道的使用。
通道(Channel)
Channel 用于缓冲区与文件或套接字之间有效的传输数据。传统的字符/字节流不同的读写操作是分开的,对应于 InputStream 与 OutputStream 两个类,而 Channel 同时支持读写操作,可以更好的映射底层操作系统的API。
Channel 在 Java 中是一个接口,里面只有两个方法,用来检查通道是否打开与关闭通道:
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
Channel接口与其主要实现类的结构图如下所示:
- ReadableByteChannel:支持读取字节的通道
- WritableByteChannel:支持写入字节的通道
- NetworkChannel:NIO 2.0 新增,用于加强对 Socket 的操作
- SelectableChannel:支持通过 Selector 实现多路复用的通道
可以看到,FileChannel、SocketChannel、DatagramChannel 都实现了 ReadableByteChannel 与 WritableByteChannel 接口,因此它们都同时支持读写操作。
此外,对于图中的四种Channel来说,它们都是线程安全的。
- FileChannel:文件通道,可以通过在 RandomAccessFile、FileInputStream、FileOutputStream 对象上调用 getChannel() 方法获取,在NIO 2.0 中可以通过 open() 方法直接打开文件创建文件通道
- SocketChannel:通过静态 open() 方法创建。对应 Socket,通过 socket() 方法获得与之关联的 socket 对象
- ServerSocketChannel:通过静态 open() 方法创建。对应 ServerSocket,通过 accept() 方法接收请求,并返回 SocketChannel 对象
- DatagramChannel:通过静态 open() 方法创建。对应 DatagramSocket,用于 UDP 数据包的传输
通道可以设置为阻塞或非阻塞模式,默认是阻塞模式,可以通过 configureBlocking(false) 方法设置为非阻塞模式。
要注意的是,FileChannel 只能运行在阻塞模式下,其它通道可以在阻塞和非阻塞模式之间选择。通道需要和字节缓冲区配合使用。
ServerSocketChannel(服务器端)示例:
public class SSCDemo {
public static void run() throws IOException, InterruptedException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(1212));
while (true) {
System.out.println("正在等待连接...");
SocketChannel sc = ssc.accept();
if (sc == null)
TimeUnit.SECONDS.sleep(2);
else {
System.out.println("新连接接入:" + sc.getRemoteAddress());
sc.read(byteBuffer);
byteBuffer.flip();
System.out.print("接收数据:");
while (byteBuffer.hasRemaining()) {
System.out.print((char) byteBuffer.get());
}
System.out.println();
byteBuffer.clear();
}
}
}
public static void main(String[] args) throws IOException, InterruptedException {
SSCDemo.run();
}
}
SocketChannel(客户端)示例:
public class SCDemo {
public static void run() throws IOException, InterruptedException {
byte[] array = "Hello World!".getBytes();
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
sc.bind(new InetSocketAddress(1213));
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 1212));
while (!sc.finishConnect()) {
System.out.println("连接中...");
TimeUnit.SECONDS.sleep(2);
}
System.out.println("连接完成!");
sc.write(byteBuffer);
sc.close();
}
public static void main(String[] args) throws IOException, InterruptedException {
SCDemo.run();
}
}
运行结果(服务器端):
正在等待连接...
正在等待连接...
正在等待连接...
新连接接入:/169.254.77.64:1213
接收数据:Hello World!
正在等待连接...
正在等待连接...