前言
前面已经学习了BIO以及NIO,本篇学习AIO。
2.3 AIO
A即asynchronous,AIO即异步IO,前面学习的IO模型都是同步IO,回顾一下NIO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,这种方式从用户进程的角度来看,是异步的,而数据在从内核在准备好后,拷贝到用户缓存的过程中,IO操作便是阻塞的,具体的IO操作(读写)还是由用户进程来完成,而AIO则是彻底的异步操作了,用户进程发起IO请求时,便立即返回,数据读写交给操作系统完成,读完了再通知java进程,用点外卖来举例,当我们点了一份螺蛳粉,使用NIO的方式则需要我们时不时的去看一下商家的制作进度,当制作完成了,我们就去拿,而AIO的方式,则只需要我们简单的下单,做好了送上门来。
示例
服务器端:server中使用的通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。
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.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* @author jhz
* @date 18-10-8 下午10:12
*/
public class AIOServer {
private final int port;
private final String IP;
private AsynchronousServerSocketChannel server = null;
public AIOServer(int port,String IP) {
this.port = port;
this.IP = IP;
try {
//产生一个异步通道AsynchronousServerSocketChannel
server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(IP,port));
} catch (Exception e){
e.printStackTrace();
}
}
//使用server来接受并处理客户端消息
public void start(){
System.out.println("开始监听端口:" + port);
//定义IO操作完成后的处理器
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
final ByteBuffer buffer = ByteBuffer.allocate(1024);
//重写完成事件回调方法
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println(Thread.currentThread().getName());
Future<Integer> writeResult = null;
try {
buffer.clear();
result.read(buffer).get(100, TimeUnit.SECONDS);
System.out.println("接收到数据:" + new String(buffer.array()));
//翻转缓冲区
buffer.flip();
//发送消息给客户端
writeResult = result.write(buffer);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
server.accept(null,this);
writeResult.get();
result.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("failed:" + exc);
}
});
}
public static void main(String[] args) {
new AIOServer(12345,"localhost").start();
while (true){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
客户端使用的通道是:AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/**
* @author jhz
* @date 18-10-8 下午10:39
*/
public class AIOClient {
public static void main(String[] args) throws IOException {
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
InetSocketAddress serverAddress = new InetSocketAddress("localhost",12345);
CompletionHandler<Void,? super Object> handler = new CompletionHandler<Void, Object>() {
//客户端定义为一个发送接收hello消息的handler,连接成功后,在complete中将hello写入缓冲区,并发送到客户端,接收到
//服务器返回后,在第二个complete中读取缓冲区的数据,读取完成后在第三个complete中关闭连接
@Override
public void completed(Void result, Object attachment) {
client.write(ByteBuffer.wrap("hello".getBytes()), null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {
final ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
buffer.flip();
System.out.println("客户端接收到消息:" + new String(buffer.array()));
try {
client.close();
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
}
};
client.connect(serverAddress,null,handler);
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
测试
小结
由于NIO的读写过程依然在应用线程里完成,所以对于那些读写过程时间长的,NIO就不太适合。而AIO的读写过程完成后才被通知,所以AIO能够胜任那些重量级,读写过程长的任务。在IO吞吐量上AIO是要高于NIO的,但在Linux系统中,底层的实现都是使用epoll,因此IO性能上没什么区别,个人觉得如果是较为轻量级且频繁的IO操作,还是使用Netty更为灵活且可定制。