在之前博客中学习了Java中的 BIO 和 NIO 的模型及使用(初始JavaBIO和NIO),今天来学习一种新的 I/O 模型——AIO。
3.1 什么是AIO
AIO(Asynchronous IO):AIO 又称为 NIO2.0,它是在 JDK1.7 对 java.nio 包升级后的支持;是对 NIO 的一种增强,是真正的异步非阻塞I/O。
- AsynchronousServerSocketChannel:异步服务端套接字通道用于:绑定端口并接受客户端连接
- AsynchronousSocketChannel:异步客户端套接字通道用于:连接远程地址,数据的读写
- Future:对于异步操作会返回一个Future使用同步的方式获取结果(对于I/O(accept/read/write))会阻塞到操作完成或超时异常,需要考虑场景来使用
- CompletionHandler:实现该接口作为操作完成/操作失败时的回调
AIO 是支持同步和异步(基于回调)的方式处理 I/O 请求: 对于获取结果有上面两种方式:Future 和 CompletionHandler。
AIO概述:提供了异步套接字通道和异步文件通道(不需要通过多路复用器(Selector)即可实现异步读写)的实现,不需要手动创建独立的线程去处理I/O读写操作,客户端的 I/O 请求都是由操作系统先完成后通知服务器启动 JDK 提供的线程池负责回调和数据读写(当真正读取到/写入完数据的时候,会通知我们注册的回调实现类)。
3.1.1 处理方式
图中处理流程仅个人理解
3.1.2 服务端代码示例
/*
* 打开异步服务套接字通道 open() 打开异步服务器套接字通道 open(AsynchronousChannelGroup group)
* 指定异步通道组,用来"处理I/O事件"的线程池
*/
AsynchronousServerSocketChannel asSocketChannel = AsynchronousServerSocketChannel.open();
asSocketChannel.bind(new InetSocketAddress(8888));// 监听端口8888
while (true) {
/*
* 接收客户端连接,使用Future方式: get方法是同步的 使用Future操作I /
* O(accept/read/write)会阻塞到操作完成或者超时异常,需要考虑操作场景
*/
Future<AsynchronousSocketChannel> future = asSocketChannel.accept();
AsynchronousSocketChannel asChannel = future.get();
System.out.println("============接收到客户端连接:" + asChannel.getRemoteAddress() + "==============");
if (asChannel.isOpen()) { // 如果通道已经打开
/*
* 接收客户端响应数据,Callback方式:发送I / O操作请求,指定一个(CompletionHandler
* handler) ,当异步I /
* O操作完成后,注册的handler的completed方法被调用,如果发送异常则调用failed方法。 dst -
* 将数据读取到readBuffer attachment - 要附加到I / O操作的对象 --> readBuffer
* handler - 读取客户端数据进行处理结果
*/
ByteBuffer readBuffer = ByteBuffer.allocate(20);
asChannel.read(readBuffer, readBuffer, new ServerSocketChannelReadHandler(asChannel));
// read方法不会阻塞下面的代码
System.out.println(this.getClass() + ":" + Instant.now());
/* 向客户端写入数据,Future方式
* Future<Integer> result = asChannel.write(ByteBuffer.wrap(
* "connecting successful!".getBytes())); if (result.get() == -1
* ) { //返回写入的字节数 return; }
*/
}
}
代码中accep()、read()、write() 这些方法都是支持异步操作的,accept() 一般可以直接使用返回的 Future 同步获取结果,所以并没有注册回调类;read()注册的回调类代码如下
回调类代码示例:
public class ServerSocketChannelReadHandler implements CompletionHandler<Integer,ByteBuffer>{
private AsynchronousSocketChannel asChannel;
public ServerSocketChannelReadHandler(AsynchronousSocketChannel asChannel){
this.asChannel = asChannel;
}
@Override
public void completed(Integer result, ByteBuffer attachment) {
if (result == -1) { //客户端停止套接字传输
try {
this.asChannel.close();
return;
} catch (IOException e) {
e.printStackTrace();
}
}
//读取字节数据
attachment.flip();
System.out.println("接收到客户端响应数据: " + Charset.forName("UTF-8").decode(attachment));
attachment.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("---------------服务端接收数据异常,关闭当前通道连接----------------");
System.out.println(exc.getMessage());
try {
this.asChannel.close();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e);
}
}
}
3.1.3 AIO与NIO的对比
NIO 是基于多路复用 I/O 实现的非阻塞 I/O,AIO 是采用异步 I/O 模型(Windows IOCP)实现的的异步非阻塞 I/O,目前 Linux 对 AIO 的支持有限,底层采用的还是 epoll。
AIO 封装的API异步编程比 NIO 编程更为简单,但是采用异步的方式对于流程的控制和在多线程调试查找问题时比较困难。
总结:不同I/O模型的对比
BIO(同步阻塞I/O) | NIO(同步非阻塞I/O) | AIO(异步非阻塞I/O) | |
---|---|---|---|
启动线程方式 | 一个连接请求一个线程 | 一个读写请求一个线程 | 有数据到达(不需要手动启动)一个线程 |
客户端个数:I/O线程 | 1:1(一个客户端连接启动一个线程) | M:1(一个线程处理多个客户端连接) | M:0(基于回调处理) |
开发/使用难度 | 简单 | 非常复杂(存在Bug) | 复杂 |
可靠性/吞吐量 | 差 | 高 | 高 |