AIO(Asynchronous Input and Output)
异步IO则采用“订阅-通知”模式:
即应用程序向操作系统注册IO监听,然后继续做自己的事情。
当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数
NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。
NIO的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作(IO操作本身是同步的)
AIO不是在IO操作准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。
因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。
基本操作
与NIO不同,当进行读写操作时,AIO只须直接调用API的read或write方法即可。
两种方法均为异步的:
对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;
对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。
即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
在JDK1.7中,这部分内容被称作NIO2,主要在Java.nio.channels包下增加了下面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel
在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel:
这个类提供了一个open()静态工厂,
一个bind()方法用于绑定服务端IP地址(还有端口号),
另外还提供了accept()用于接收用户连接请求。
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT);
public abstract <A> void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,? super A> handler);
在客户端使用的通道是AsynchronousSocketChannel:
这个通道处理提供open静态工厂方法外,还提供了read和write方法。
Future<Void> connect = AsynchronousSocketChannel.open().connect(new InetSocketAddress(IP, PORT));
在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类是
CompletionHandler<V,A>,接口定义了如下两个方法,分别在异步操作成功和失败时被回调:
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);
异步channel API提供了两种方式监控/控制异步操作(connect,accept, read,write等):
第一种方式是返回java.util.concurrent.Future对象,
检查Future的状态可以得到操作是否完成还是失败,还是进行中(future.get阻塞当前进程)
第二种方式为操作提供一个回调参数java.nio.channels.CompletionHandler
这个回调类包含completed,failed两个方法。
Future方式
Future是在JDK1.5中加入Java并发包的,该接口提供get()方法用于获取任务完成之后的处理结果。
在AIO中,可以接受一个I/O连接请求,返回一个Future对象。
然后可以基于该返回对象进行后续的操作,包括使其阻塞、查看是否完成、超时异常。
FutureClient
public class ClientOnFuture {
static final int PORT = 10000;
static final String IP = "localhost";
static ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
public static void main(String[] args) {
//尝试创建AsynchronousSocketChannel
try (AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open()) {
//获取连接
Future<Void> connect = socketChannel.connect(new InetSocketAddress(IP, PORT));
//返回连接状态
Void aVoid = connect.get();
//返回null表示连接成功
if (aVoid == null) {
/**
* 向服务端发送数据
*/
Future<Integer> write = socketChannel.write(ByteBuffer.wrap("客户端说:我连接成功了!".getBytes()));
Integer integer = write.get();
System.out.println("服务端接收的字节长度:" + integer);
/**
* 接收服务端数据
*/
while (socketChannel.read(buffer).get() != -1) {
buffer.flip();
CharBuffer decode = Charset.defaultCharset().decode(buffer);
System.out.println(decode.toString());
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
int r = new Random().nextInt(10);
if (r == 5) {
System.out.println("客户端关闭!");
break;
} else {
/**
* 如果在频繁调用write()的时候,在上一个操作没有写完的情况下,
* 调用write会触发WritePendingException异常
*
* 应此此处最好在调用write()之后调用get()阻塞以便确认io操作完成
*/
socketChannel.write(ByteBuffer.wrap(("客户端发送的数据:" + r).getBytes())).get();
}
}
} else {
System.out.println("无法建立连接!");
}
} catch (Exception e) {
System.out.println("出错了!");
}
}
}
FutureServer
public class ServerOnFuture {
static final int PORT = 10000;
static final String IP = "localhost";
static ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
public static void main(String[] args) {
try (AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open()) {
serverSocketChannel.bind(new InetSocketAddress(IP, PORT));
while (true) {
Future<AsynchronousSocketChannel> channelFuture = serverSocketChannel.accept();
try (AsynchronousSocketChannel socketChannel = channelFuture.get()) {
while (socketChannel.read(buffer).get() != -1) {
buffer.flip();
/**
* 此处要注意:千万不能直接操作buffer(因为write要用到buffer),否则客户端会阻塞并报错
* “java.util.concurrent.ExecutionException: java.io.IOException: 指定的网络名不再可用。”
*
* 缓冲区的复制有分两种:
* 1、完全复制:调用duplicate()函数或者asReadOnlyBuffer()函数
* 2、部分复制:调用slice函数
*
* duplicate()函数创建了一个与原始缓冲区相似的新缓冲区。
* 每个缓冲区有自己的位置信息,但对缓冲区的修改都会映射到同一个底层数组上。
*/
//复制一个缓冲区会创建一个新的 Buffer 对象,但并不复制数据。原始缓冲区和副本都会操作同样的数据元素。
ByteBuffer duplicate = buffer.duplicate();
CharBuffer decode = Charset.defaultCharset().decode(duplicate);
System.out.println("收到客户端数据:" + decode);
/**
* 写回数据(get()会阻塞以等待io操作完成)
*/
socketChannel.write(buffer).get();
/**
* 清理buffer,准备下一次read
*/
if (buffer.hasRemaining()) {
/**
* 如果未写完,表示buffer还有数据,则只清理写过的数据
* compact()方法只会清除已经读过的数据。
* 任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
*/
buffer.compact();
} else {
buffer.clear();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}