作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦
千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者
前言
在前两篇文章中,壹哥 给大家介绍了BIO和NIO模型。我们现在知道,NIO相比BIO在性能上已经有了较大提升,但随着技术地不断革新,现在又出现了更为高效先进的AIO技术,所以今天壹哥会给大家继续普及AIO的内容。
------------------------------前戏已做完,精彩即开始----------------------------
全文大约【2400】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......
配套开源项目资料
Github: github.com/SunLtd/Lear...
Gitee: gitee.com/sunyiyi/Lea...
一. AIO-异步非阻塞式IO
1. AIO简介
AIO(Asynchronous IO),异步非阻塞的I/O, 这是在JDK 7中出现的新特性。AIO是在NIO基础上进行的扩展,属于NIO中的一项重要功能,它能够更加高效地处理大量的I/O操作,帮助我们实现高性能、高并发的网络编程。AIO的 主要优点在于可以在不阻塞线程的情况下进行异步操作,相对于BIO同步阻塞I/O 和 NIO同步非阻塞I/O,AIO更加的灵活和高效,响应更加迅速,提高了并发处理的能力。
2. 工作原理
壹哥把AIO的工作原理给大家简单描述总结如下:
AIO的异步操作模型是基于事件和回调机制来实现的 *。 *与NIO模式不同,AIO操作不需要在准备好之后才能进行读写,只需要在完成时通知应用程序进行下一步处理即可 *。 *一般是在应用程序调用异步I/O操作时,操作系统会为该操作分配一个线程,在该I/O操作完成后就通知应用程序,应用程序只需要处理已经完成的操作即可。
总的来说,AIO的工作原理与NIO的Selector相似,但AIO是基于事件和回调的异步操作模型,而Selector是基于轮询的同步操作模型。
3. 三种AIO异步通道
在AIO模型中,给我们提供了如下三种异步通道:
AsynchronousSocketChannel、AsynchronousServerSocketChannel和AsynchronousDatagramChannel。
- AsynchronousSocketChannel:这是一个异步的客户端Socket通道。我们可以使用它来创建一个连接到远程服务器的Socket连接,也可以使用它来读取和写入数据。
- AsynchronousServerSocketChannel:这是一个异步的服务端Socket通道,基于TCP协议和事件驱动,通过注册回调函数来实现异步操作,我们可以使用它来接受来自客户端的连接请求。
- AsynchronousDatagramChannel:该通道用于非阻塞地发送和接收UDP数据包,基于UDP协议和事件驱动,通过注册回调函数来实现异步操作,不需要建立连接。
4. AIO工作流程
一般情况下,AIO的工作流程会遵循如下步骤:
- 应用程序向操作系统注册需要进行异步I/O操作的事件,如读操作或写操作;
- 操作系统将事件加入到I/O事件队列中,等待I/O完成;
- 操作系统通知应用程序,已经有一个事件准备好进行I/O操作了,应用程序可以继续执行其它操作;
- 应用程序调用操作系统提供的方法,来获取I/O操作的结果。
接下来壹哥就通过具体的案例,来给大家演示如何基于AIO模型进行数据的读写,请大家继续往下看吧。
二. 代码实现
1. 定义服务端
首先壹哥定义一个AIO服务器端,这里我们会监听一个9999端口,大家要注意,这个端口是任意的,但不能和别的进程端口重复。
java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @author 一一哥Sun
*/
public class AioServer {
//定义一个网络端口
private static final int PORT = 9999;
public static void main(String[] args) throws Exception {
// 创建一个AsynchronousServerSocketChannel对象
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(PORT));
System.out.println("Server started at port " + PORT);
// 接受客户端发来的异步连接请求
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
//处理成功时的请求
@Override
public void completed(AsynchronousSocketChannel channel, Void attachment) {
// 接收下一次的连接
serverChannel.accept(null, this);
// 处理请求:读取客户端发来的信息,并向客户端返回消息
handle(channel);
}
@Override
public void failed(Throwable throwable, Void attachment) {
// 失败时进行异常处理
throwable.printStackTrace();
}
});
// 阻塞程序的执行,保持服务器一直运行
Thread.currentThread().join();
}
//读取和返回信息
private static void handle(AsynchronousSocketChannel channel) {
//创建一个缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
//从缓冲区中读取数据
Future<Integer> future = channel.read(buffer);
//获取读取到的数据
int readBytes = future.get();
if (readBytes > 0) {
//切换到读模式
buffer.flip();
String message = StandardCharsets.UTF_8.decode(buffer).toString();
System.out.println("接收到客户端发来的消息: " + message);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
} finally {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
壹哥给大家分析一下上面案例的执行步骤:
- 当服务端监听到客户端发来的连接请求时,AsynchronousServerSocketChannel对象可以通过accept()方法来接受客户端发来的异步请求;
- 接着我们使用CompletionHandler来处理连接事件,在AsynchronousServerSocketChannel的回调方法completed中获取AsynchronousSocketChannel对象用于和客户端进行通信;
- 然后利用AsynchronousServerSocketChannel.accept()方法来接收下一个连接;
- 接着将当前连接交给handle()方法处理。在handle()方法中,我们使用Future来读取客户端发来的数据;
- 最后需要关闭连接。
以上案例只是一个简单的示例,实际上AIO还有更多的用途,比如异步文件IO操作、异步套接字通信等。大家需要注意的是,AIO虽然比BIO和NIO更加高效,但我们在使用AIO也要注意一些问题,比如AIO的线程模型和内存占用等。
2. 定义客户端
接着壹哥再定义一个简单的客户端代码,使用AsynchronousSocketChannel连接到一个服务端并发送一个请求。当客户端与服务端的连接建立好之后,completed方法会被调用。
java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
/**
* @author 一一哥Sun
*/
public class AioClient {
// 定义一个Channel客户端对象
private AsynchronousSocketChannel client;
public AioClient(String host, int port) throws Exception {
// 打开链接,获取Channel对象
client = AsynchronousSocketChannel.open();
// 设置网络地址和端口
InetSocketAddress serverAddress = new InetSocketAddress(host, port);
// 客户端异步连接到服务器
client.connect(serverAddress, null, new CompletionHandler<Void, Void>() {
// 连接成功时的回调方法
@Override
public void completed(Void result, Void attachment) {
System.out.println("server连接成功...");
}
// 连接失败时的回调方法
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("server连接失败...");
}
});
}
// 定义一个发送消息的方法
public void send(String message) throws InterruptedException, ExecutionException {
// 缓冲区
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
// 发送消息
client.write(buffer).get();
buffer.clear();
}
public static void main(String[] args) throws Exception {
AioClient client = new AioClient("localhost", 9999);
// 向服务端发送消息
client.send("Hello, Server,我是客户端!");
}
}
在上面的案例中,我们在AioClient类的构造方法中,通过AsynchronousSocketChannel.open()方法创建了一个 AsynchronousSocketChannel对象,并调用connect()方法来发起连接请求。该连接请求是异步的,因此需要提供一个CompletionHandler对象,当连接成功或失败时,该对象的相关回调方法会被调用。
在send()方法中,我们利用ByteBuffer缓冲区将消息转换为字节序列,并调用 AsynchronousSocketChannel.write()方法发送消息。由于write()方法也是异步的,因此我们需要通过get()方法等待写操作的完成。
最后,我们在main()方法中创建一个 AioClient对象,并通过send()方法发送一条消息。
------------------------------正片已结束,来根事后烟----------------------------
三. 结语
至此,壹哥 就用三篇文章把BIO、NIO和AIO模型给大家讲解完毕了。当然,壹哥并没有详细地讲解NIO和AIO模型,主要是带大家详细地学习了BIO模型,后面我会开辟专门的NIO和AIO模型专栏,敬请大家持续关注。
我们在面试时,有很多面试官都会考察这样一道面试题, "请说说BIO、NIO和AIO的区别" ,所以最后壹哥就把这道题目给大家梳理一下,算是IO流部分的收尾吧。
我们现在知道,BIO、NIO和AIO都是Java中用于处理网络通信及信息处理的API,但它们的实现方式和特点有所不同,主要是体现在以下方面:
1. BIO同步阻塞I/O
BIO是一种最为传统的I/O模型,当客户端发起请求时,服务端会把该请求封装成一个Socket对象,然后在该Socket上进行 I/O操作。这种模式的特点是代码简单,易于理解和使用。但它每个请求都会对应一个线程,当并发请求数量较大,需要大量线程时就会导致服务器性能的严重下降。
2. NIO同步非阻塞I/O
NIO是 Java 1.4中引入的一种新的I/O模型,它主要提供了三个核心组件:Channel、Buffer、Selector。在NIO模型中,客户端发起请求时,不会创建一个线程处理该请求,而是将该请求放入到一个请求队列中,然后通过一个单独的线程轮询这些请求。对于已经准备好的请求,最后其分配给对应的线程进行处理。相对于BIO模型,NIO模型可以使用较少的线程支持较大的并发连接数。
3. AIO异步非阻塞I/O
AIO是Java 1.7中引入的一种 I/O模型,它和NIO一样,采用了非阻塞I/O的方式。但它和 NIO的区别在于,AIO 中的 I/O 操作不需要通过 Selector来轮询,而是由操作系统在数据准备好之后,主动通知应用程序进行处理。这种方式相对NIO更加的高效,因为AIO不需要将就绪的事件一个一个地进行轮询,而是由操作系统来进行通知,减少了不必要的上下文切换。
总的来说,BIO、NIO 和 AIO模型各有优劣,我们在开发时应该根据具体的应用场景进行选择。如果是并发量较小的应用程序,可以选择BIO模型;如果并发量较大,可以考虑使用NIO;如果是要处理大量的并发请求,应该选择使用AIO模型。
如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。