前言

  之前已经整理过了BIO、NIO两种I/O的相关博文,每一种I/O都有其特点,但相对开发而言,肯定是要又高效又简单的I/O编程才是真正需要的,在之前的NIO博文(深入学习Netty(2)——传统NIO编程)中就已经介绍过NIO编程的缺点(相比较而言的缺点:同步非阻塞,需要单独开启线程不断轮询),所以才会有真正的异步非阻塞I/O出现,这就是此篇博文需要介绍的AIO编程。

  参考资料《Netty In Action》、《Netty权威指南》(有需要的小伙伴可以评论或者私信我)

  博文中所有的代码都已上传到Github,欢迎Star、Fork

  感兴趣可以先学习相关博文:


一、NIO 2.0与AIO编程

  JDK 1.7升级了NIO类库,升级后的NIO类库称之为NIO 2.0,Java提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。

  NIO 2.0的异步套接字通道是真正的异步非阻塞I/O,对应有UNIX网络编程中的事件驱动I/O(AIO),相比较NIO,它不需要通过Selector对注册的通道进行轮询操作即可实现异步读写,简化了NIO的编程模型。

  NIO 2.0提供了新的异步通道的概念,异步通道提供了以下两种方式获取操作结果:

  • 通过juc.Futrue类来表示异步操作的结果
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 8080);
Future<Void> connect = socketChannel.connect(inetSocketAddress);
while (!connect.isDone()) {
  Thread.sleep(10);
}
  • 在异步操作的时候传入java.nio.channels。实现CompletionHandler接口complete()的方法作为操作完成回调
private class MyCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {

  @Override
  public void completed(Integer result, ByteBuffer attachment) {
    // TODO 回调后业务操作
  }
  @Override
  public void failed(Throwable t, ByteBuffer attachment) {
    t.printStackTrace();
  }

二、AIO服务端

(1)服务端AIO异步处理任务AsyncTimeServerHandler

  • 创建异步服务通道并监听端口
  • 异步监听客户端连接
/**
* 服务端AIO异步处理任务
* -创建异步服务通道监听端口
* -监听客户端连接
*/
public class AsyncTimeServerHandler implements Runnable{ private int port; CountDownLatch latch;
AsynchronousServerSocketChannel asynchronousServerSocketChannel; public AsyncTimeServerHandler(int port) {
this.port = port;
try {
// 创建异步的服务通道asynchronousServerSocketChannel, 并bind监听端口
asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("The time server is start in port : " + port);
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void run() {
// countDownLatch没有count减一,所以导致一直阻塞
latch = new CountDownLatch(1);
doAccept();
try {
// 防止执行操作线程还未结束,服务端线程就退出,程序不退出的前提下,才能够让accept继续可以回调接受来自客户端的连接
// 实际开发过程中不需要单独开启线程去处理AsynchronousServerSocketChannel
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
} /**
* 接收客户端的连接
* 参数CompletionHandler类型的handler实例来接收accept操作成功的通知消息
*/
public void doAccept() {
asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
}
}

(2)服务端连接异步回调处理器AcceptCompletionHandler:异步处理客户端连接完成后的操作

/**
* 客户端连接异步处理器
* completed()方法完成回调logic
* failed()方法完成失败回调logic
*/
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> { /**
* 调用该方法表示客户端已经介接入成功
* 同时再accept接收新的客户端连接
* @param result
* @param attachment
*/
@Override
public void completed(AsynchronousSocketChannel result,
AsyncTimeServerHandler attachment) {
// 此时还要继续调用accept方法是因为,completed方法表示上一个客户端连接完成,而下一个新的客户端需要连接
// 如此形成新的循环:每接收一个客户端的成功连接之后,再异步接收新的客户端连接
attachment.asynchronousServerSocketChannel.accept(attachment, this);
// 预分配1M的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 调用read方法异步读,传入CompletionHandler类型参数异步回调读事件
result.read(buffer, buffer, new ReadCompletionHandler(result));
} @Override
public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
exc.printStackTrace();
// 让服务线程不再阻塞
attachment.latch.countDown();
}
}

(3)服务端read事件异步回调处理器ReadCompletionHandler:异步回调处理客户端请求数据

/**
* 服务端read事件异步处理器
* completed异步回调处理客户端请求数据
*/
public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
private AsynchronousSocketChannel channel; public ReadCompletionHandler(AsynchronousSocketChannel channel) {
if (this.channel == null) {
this.channel = channel;
}
} @Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
// 根据缓冲区的可读字节创建byte数组
byte[] body = new byte[attachment.remaining()];
attachment.get(body);
try {
// 解析请求命令
String req = new String(body, "UTF-8");
System.out.println("The time server receive order : " + req);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
// 发送当前时间给客户端
doWrite(currentTime);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} private void doWrite(String currentTime) {
if (currentTime != null && currentTime.trim().length() > 0) {
byte[] bytes = (currentTime).getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
// write异步回调,传入CompletionHandler类型参数
channel.write(writeBuffer, writeBuffer,
new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
// 如果没有发送完成,继续发送
if (buffer.hasRemaining()) {
channel.write(buffer, buffer, this);
}
} @Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
channel.close();
} catch (IOException e) {
// TODO 只要是I/O异常就需要关闭链路,释放资源 }
}
});
}
} @Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
this.channel.close();
} catch (IOException e) {
e.printStackTrace();
// TODO 只要是I/O异常就需要关闭链路,释放资源
}
}
}

(4)服务端启动TimeServer

/**
* AIO 异步非阻塞服务端
* 不需要单独开线程去处理read、write等事件
* 只需要关注complete-handlers中的回调completed方法
*/
public class TimeServer { public static void main(String[] args) throws IOException {
int port = 8086;
AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
new Thread(timeServer, "AIO-AsyncTimeServerHandler").start();
}
}

(5)启动服务端

服务端Console:

使用命令netstat查看8086端口是否监听

三、AIO客户端

(1)客户端AIO异步回调处理任务

  • 打开AsynchronousSocketChannel通道,连接服务端
  • 发送服务端指令
  • 回调处理服务端应答
/**
* 客户端AIO异步回调处理任务
* -打开AsynchronousSocketChannel通道,连接服务端
* -发送服务端指令
* -回调处理服务端应答
*/
public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable { private AsynchronousSocketChannel client;
private String host;
private int port;
private CountDownLatch latch; public AsyncTimeClientHandler(String host, int port) {
this.host = host;
this.port = port;
try {
client = AsynchronousSocketChannel.open();
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void run() {
latch = new CountDownLatch(1);
client.connect(new InetSocketAddress(host, port), this, this);
try {
// 防止异步操作都没完成,连接线程就结束退出
latch.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 发送请求完成异步回调
* @param result
* @param attachment
*/
@Override
public void completed(Void result, AsyncTimeClientHandler attachment) {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
client.write(writeBuffer, writeBuffer,
new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
if (buffer.hasRemaining()) {
client.write(buffer, buffer, this);
} else {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
// 回调服务端应答消息
client.read(readBuffer, readBuffer,
new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer buffer) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
try {
body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
// 服务端应答完成后,连接线程退出
latch.countDown();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} @Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
client.close();
// 防止线程一直阻塞
latch.countDown();
} catch (IOException e) {
// ingnore on close
}
}
});
}
} @Override
public void failed(Throwable exc, ByteBuffer attachment) {
try {
client.close();
latch.countDown();
} catch (IOException e) {
// ingnore on close
}
}
});
} @Override
public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
exc.printStackTrace();
try {
client.close();
latch.countDown();
} catch (IOException e) {
e.printStackTrace();
}
}
}

(2)客户端TimeClient

/**
* AIO 异步非阻塞 客户端
* 不需要单独开线程去处理read、write等事件
* 只需要关注complete-handlers中的回调completed方法
*/
public class TimeClient {
public static void main(String[] args) {
int port = 8086;
new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClientHandler").start(); }
}

(3)启动客户端

客户端Console:

服务端Console:

四、总结

服务端通过countDownLatch一直阻塞

由代码实践我们可知:

  JDK底层通过ThreadPoolExecutor执行回调通知,异步回调通知类由sun.nio.ch.AsynchronousChannelGroupImpl实现,然后将任务提交到该线程池以处理I/O事件,并分派给completion-handlers ,该队列消耗对组中通道执行的异步操作的结果

  异步SocketChannel是被动执行,不需要单独像NIO编程那样单独创建一个独立的I/O线程处理读写操作,都是由JDK底层的线程池负责回调并驱动读写操作的。所以基于NIO 2.0的新的异步非阻塞相比较NIO编程要简单,这两区别在于:

  • 在NIO中等待IO事件由我们注册的selector来完成,在感兴趣的事情来了,我们的线程来accept.read.write.connect...解析,解析完后再交由业务逻辑处理。
  • 而在在异步IO(AIO、NIO 2.0)中等待IO事件同样为accept,read,write,connect,但数据处理交由系统完成,我们需要做的就是在completionHandlers中处理业务逻辑回调即可

深入学习Netty(3)——传统AIO编程的更多相关文章

  1. 深入学习Netty(4)——Netty编程入门

    前言 从学习过BIO.NIO.AIO编程之后,就能很清楚Netty编程的优势,为什么选择Netty,而不是传统的NIO编程.本片博文是Netty的一个入门级别的教程,同时结合时序图与源码分析,以便对N ...

  2. 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?

    前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...

  3. 深入学习Netty(2)——传统NIO编程

    前言 学习Netty编程,避免不了从了解Java 的NIO编程开始,这样才能通过比较让我们对Netty有更深的了解,才能知道Netty大大的好处.传统的NIO编程code起来比较麻烦,甚至有遗留Bug ...

  4. 深入学习Netty(1)——传统BIO编程

    前言 之前看过Dubbo源码,Nacos等源码都涉及到了Netty,虽然遇到的时候查查资料,后面自己也有私下学习Netty并实践,但始终没有形成良好的知识体系,Netty对想要在Java开发上不断深入 ...

  5. AIO编程

    AIO编程,在NIO基础之上引入了异步通道的概念,并提供了异步文件和异步套接字通道的实现,从而在真正意义上实现了异步非阻塞,之前我们学习的NIO只是非阻塞而并非异步.而AIO它不需要通过多路复用器对注 ...

  6. Netty 中的异步编程 Future 和 Promise

    Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...

  7. Java NIO系列教程(八)JDK AIO编程

    目录: Reactor(反应堆)和Proactor(前摄器) <I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor> <[转]第8章 前摄器(Proa ...

  8. 08.十分钟学会JSP传统标签编程

    一.认识标签 1,说明:传统标签编程在开发中基本用不到,学习标签编程主要还是为了完善知识体系. 2,标签的主要作用:移除或减少jsp中的java代码 3,标签的主要组成部分及运行原理 4,简单标签示例 ...

  9. NIO&AIO编程模型

    NIO线程模型 什么是NIO线程模型? 上图是NIO的线程模型,  基于select实现,   这种线程模型的特点:  多条channel通过一个选择器和单挑线程绑定, 并且在这种编程模型中, Cha ...

随机推荐

  1. 还可以使用 -c 参数来显示全部内容,并标出不同之处 diff -c test2.txt test1.txt

    二.实例 在test目录下存放了两个文本文件,test1.txt  test2.txt . 比较这两个文件的异同. diff  test1.txt   test2.txt     "5c5& ...

  2. Centos6下通过 oprofile分析CPU性能

    Centos6下通过 oprofile分析CPU性能 2014-01-18 10:55:15 bobpen 阅读数 2218更多 分类专栏: linux   版权声明:本文为博主原创文章,遵循CC 4 ...

  3. MegaCli是一款管理维护硬件RAID软件,可以通过它来了解当前raid卡的所有信息,包括 raid卡的型号,raid的阵列类型,raid 上各磁盘状态

    MegaCli 监控raid状态 转载weixin_30344131 最后发布于2015-10-16 13:05:00 阅读数    简介 MegaCli是一款管理维护硬件RAID软件,可以通过它来了 ...

  4. Linux进阶之综合练习

    综合练习: 1.准备2台centos7系统的服务器,远程互相免密登录,以下所有题目过程中开启防火墙 2.给1号机和2号机使用光盘搭建本地yum源(永久生效) 3.给服务器1添加2块硬盘,1块1GB,1 ...

  5. Scala 中为什么不建议用 return 关键字

    在scala中使用 return 的话,编译的时候会提示the latest statement is method is automatically returned, use of th retu ...

  6. 实操笔记:为 NSQ 配置监控服务的心路历程

    在 Go 语言实现的实时消息队列中, NSQ 的热度可以排第一. NSQ 这款消息中间件简单易用,其设计目标是为在分布式环境下运行,为去中心化服务提供一个强大的基础架构.它具有分布式.去中心化的拓扑结 ...

  7. 四:net core(.NET 6)使用Filter过滤器拦截webapi方法

    Filter的基本用法 代码在最下方 使用filter过滤器,来实现拦截接口信息.咱们先新建一个项目,在原有的webapi上面,选择添加项目,添加一个类库项目: 我起个名字,就叫 Wsk.Core.F ...

  8. Spring Cloud Alibaba(13)---Sleuth概述

    Sleuth概述 前言 在微服务架构中,众多的微服务之间互相调用,如何清晰地记录服务的调用链路是一个需要解决的问题.同时,由于各种原因,跨进程的服务调用失败时,运维人员希望能够通过 查看日志和查看服务 ...

  9. CVD和ALD薄膜沉积技术应用领域

    CVD和ALD薄膜沉积技术应用领域 显示 用于OLED.QD-OLED.甚至未来QLED的薄膜封装,通过有机/无机叠层结构的保护,水汽渗透率WVTR可降至10-5g/m2/day,保证OLED或者量子 ...

  10. CUDA上的量化深度学习模型的自动化优化

    CUDA上的量化深度学习模型的自动化优化 深度学习已成功应用于各种任务.在诸如自动驾驶汽车推理之类的实时场景中,模型的推理速度至关重要.网络量化是加速深度学习模型的有效方法.在量化模型中,数据和模型参 ...