AIO介绍
JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO 2.0,Java正式提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。AIO是真正的异步非阻塞I/O。它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。目前的AIO与NIO底层都使用了epoll(Linux中),所以二者性能都很好,主要差异在于同步与异步,NIO是同步的,始终只有一个线程在进行结果处理,而AIO的异步回调则是基于多线程的,如果NIO结果处理中引入多线程,个人认为二者性能是相仿的。
什么是epoll?
epoll是Linux中多路复用IO接口select/poll的增强版本,select/poll模型是忙轮询,即一直不停地轮询看哪些操作已经结束可以获取操作结果了,而epoll则是将已经结束的操作的操作结果放入队列中,然后只需要遍历处理队列中的操作就可以了,避免了CPU的浪费,提升程序运行效率。
AIO与NIO有什么区别?
1.NIO是同步非阻塞I/O,AIO是异步非阻塞I/O;
2.AIO与NIO的操作结果获取方式不同,NIO的操作结束后会将操作就绪的I/O放在队列中,由Selector依次循环获取处理;AIO操作结束后则会直接回调CompletionHandler的实现类的相应函数来进行处理;
3.处理操作结果时NIO是单线程,即由Selector依次在当前线程中进行处理,如果需要多线程处理需要自行实现,这也是为什么它是同步而非异步;而AIO在回调处理操作结果时,是多线程的,其底层设有线程池。
AIO既然是异步的,那么如何获得操作结果?
1.通过返回的Future模式java.util.concurrent.Future类来表示异步操作的结果;
2.在执行异步操作时传入一个java.nio.channel,并传入CompletionHandler接口的实现类作为操作完成的回调,CompletionHandler顾名思义就是专门用来处理完成结果的。
我更推荐用CompletionHandler的方式,这些handler的调用是由 AsynchronousChannelGroup的线程池派发的。显然,线程池的大小是性能的关键因素。AsynchronousChannelGroup允许绑定不同的线程池,通过三个静态方法来创建:
AIOAPI介绍
java.nio.channels.AsynchronousChannel
标记一个channel支持异步IO操作。
java.nio.channels.AsynchronousServerSocketChannel
ServerSocket的aio版本,创建TCP服务端,绑定地址,监听端口等。
java.nio.channels.AsynchronousSocketChannel
面向流的异步socket channel,表示一个连接
java.nio.channels.AsynchronousChannelGroup
异步channel的分组管理,目的是为了资源共享。一个AsynchronousChannelGroup绑定一个线程池,这个线程池执行两个任务:处理IO事件和派发CompletionHandler。AsynchronousServerSocketChannel创建的时候可以传入一个 AsynchronousChannelGroup,那么通过AsynchronousServerSocketChannel创建的 AsynchronousSocketChannel将同属于一个组,共享资源。
java.nio.channels.CompletionHandler
异步IO操作结果的回调接口,用于定义在IO操作完成后所作的回调工作. CompletionHandler有三个方法,分别对应于处理成功、失败、当操作完成时,会回调completed,出现异常失败时会回调failed。
completed
操作完成时,回调completed函数,其有result和attachment两个参数:
- result是操作完成后的操作结果;
- attachment是在进行回调时可以传入的附件,用于回调内的操作;
failed操作异常时回调failed函数,其有exc和attachment两个参数:
- exc即进行操作时出现的异常;
- attachment和completed中的一致,为在进行回调时传入的附件,用于回调内操作;
其中的泛型参数V表示IO调用的结果,而A是发起调用时传入的attchment。
client端:
public class SimpleTimeClient {
private String host;
private int port;
private CountDownLatch latch;
private AsynchronousSocketChannel channel;//异步socket通道
public static void main(String [] args){
while (true) {
new Thread(() -> {
try {
System.out.println("time client thread: " + Thread.currentThread());
SimpleTimeClient client = new SimpleTimeClient("localhost", 8088);
client.latch.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private SimpleTimeClient(String host, int port) throws IOException{
this.host = host;
this.port = port;
this.latch = new CountDownLatch(1);
initChannel();
}
private void initChannel() throws IOException
{
channel = AsynchronousSocketChannel.open();// 打开异步socket通道
// 异步连接指定地址,连接完成后会回调ConnectionCompletionHandler
//A attachment :AsynchronousSocketChannel的附件,用于回调通知时作为参数传递,调用者可以自定义。
//CompletionHandler<Void,? super A> handler 异步操作回调通知接口
channel.connect(new InetSocketAddress(host, port), null, new ConnectionCompletionHandler());
}
private class ConnectionCompletionHandler implements CompletionHandler<Void, Void> {
@Override
public void completed(Void result, Void attachment)
{
System.out.println("connection thread: " + Thread.currentThread());
String msg = "query time order";
ByteBuffer writeBuffer = ByteBuffer.allocate(msg.length());
writeBuffer.put(msg.getBytes(StandardCharsets.UTF_8)).flip();
// 异步写入发送数据,写入完成后会回调WriteCompletionHandler
channel.write(writeBuffer, writeBuffer, new WriteCompletionHandler());
}
@Override
public void failed(Throwable exc, Void attachment)
{
exc.printStackTrace();
latch.countDown();//异常时执行让线程执行完毕
}
}
//写数据完成回调处理类
private class WriteCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
@Override
public void completed(Integer result, ByteBuffer buffer){
System.out.println("write thread: " + Thread.currentThread());
if(buffer.hasRemaining())
channel.write(buffer, buffer, this);
else{
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//异步读取返回的数据,读取结束后会回调ReadCompletionHandler
channel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>(){
@Override
public void completed(Integer result, ByteBuffer buffer)
{
System.out.println("read thread: " + Thread.currentThread());
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body;
body = new String(bytes, StandardCharsets.UTF_8);
System.out.println("now is " + body);
latch.countDown();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment)
{
try{
channel.close();
latch.countDown();
} catch (IOException e){
e.printStackTrace();
}
}
});
}
}
@Override
public void failed(Throwable exc, ByteBuffer attachment)
{
exc.printStackTrace();
latch.countDown();
}
}
}
server端:
public class SimpleTimeServer implements Runnable{
//维持服务线程的门闩
private CountDownLatch latch;
//异步socket服务通道
private AsynchronousServerSocketChannel asynchronousServerSocketChannel;
public static void main(String [] args){
try{
System.out.println("我是主线程: " + Thread.currentThread());
new SimpleTimeServer(8088).run();
System.out.println("监听线程已挂");
} catch (IOException e) {
e.printStackTrace();
}
}
private SimpleTimeServer(int port) throws IOException{
//开启异步socket服务
asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
//绑定端口
asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
System.out.println("simple time server start in " + port);
}
@Override
public void run()
{
latch = new CountDownLatch(1);//阻塞当前线程防止服务端任务执行完退出,
//在实际项目中,不需要启动独立的线程来处理asynchronousServerSocketChannel,这里仅仅是个demo。
System.out.println("我是监听线程:" + Thread.currentThread());
//异步socket服务接收请求,传递一个attach对象和实现了CompletionHandler的回调来处理AIO操作结果
asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
try{
latch.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
//接收请求的结束动作处理类,当异步socket服务接收到一个请求时,会回调此handler,从而对收到的请求进行处理
//AsynchronousSocketChannel为处理结果 SimpleTimeServer为发起调用时传入的参数
private class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, SimpleTimeServer>{
@Override
public void completed(AsynchronousSocketChannel channel, SimpleTimeServer attachment)
{
System.out.println("我是处理线程:" + Thread.currentThread());
//循环监听,进行监听操作的是SimpleTimeServer运行的线程,这样做的目的是因为一个asynchronousServerSocketChannel
//可以接收成千上万个客户端,所以当系统回调我们传入的CompletionHandler时,表示新的客户端已经接入成功,
//所以继续调用accept接受其他客户端 如果处理不过来回用新的线程来接收其他接入
attachment.asynchronousServerSocketChannel.accept(attachment, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
//ByteBuffer dst :接受缓冲区,用于从异步的channel中读取数据包
//A attachment :异步channel携带的附件,通知回调时作为参数传递使用。
//CompletionHandler<Integer,? super A> handler 接收通知回调的业务handleer
channel.read(buffer, buffer, new ReadCompletionHandler(channel));
}
@Override
public void failed(Throwable exc, SimpleTimeServer attachment)
{
//接收请求失败,打印异常信息,将门闩减一,服务线程终止
exc.printStackTrace();
attachment.latch.countDown();
}
//读取数据的结束动作处理类,当系统将数据读取到buffer中,会回调此handler
private class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
//将AsynchronousSocketChannel通过参数传递到ReadCompletionHandler中 当做成员变量用来读取包中消息和发送应答
private AsynchronousSocketChannel channel;
ReadCompletionHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void completed(Integer result, ByteBuffer attachment)
{
//首先对attachment进行flip操作,为后续从缓冲区读取数据做准备
//根据缓冲区的可读字节创建byte数组,然后通过newString方法创建请求消息,对请求消息进行判断
//如果是“query time order” 则获取当前系统的服务器时间,调用dowrite方法发送客户端。
attachment.flip();
byte[] body = new byte[result];//attachment.remaining()
attachment.get(body);
String req = new String(body, StandardCharsets.UTF_8);
System.out.println("the time server received order: " + req);
String currentTime = "query time order".equalsIgnoreCase(req) ? new Date(System.currentTimeMillis())
.toString() : "BAD ORDER";
doWrite(currentTime);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment)
{
try{//读取失败关闭通道
channel.close();
} catch (IOException e){
e.printStackTrace();
}
}
private void doWrite(String msg)
{
//将字符串解码为字节数组再调用AsynchronousSocketChannel的write方法
if(msg != null){
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>(){
@Override
public void completed(Integer result, ByteBuffer attachment)
{
//如果没有发送完成就继续发送
if(attachment.hasRemaining())
channel.write(attachment, attachment, this);
}
@Override //可以对异常判断如果是I/O异常,就关闭链路,释放资源,如果是其他异常按照业务逻辑处理。
public void failed(Throwable exc, ByteBuffer attachment)
{
try{
channel.close();
} catch (IOException e){
e.printStackTrace();
}
}
});
}
}
}
}
}
AIO读取文件:AsynchronousFileChannel
第一种方式是调用返回值为Future的read()方法:这种方式中,read()接受一个ByteBuffer座位第一个参数,数据会被读取到ByteBuffer中。 第二个参数是开始读取数据的文件位置。read()方法会立刻返回,即使读操作没有完成。我们可以通过isDone()方法检查操作是否完成。
static void read1() throws IOException{
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/ser.ser"), StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
Future<Integer> operation = fileChannel.read(buffer, position);
while (!operation.isDone());
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();
}
第二种通过CompletionHandler读取数据
static void read2() throws IOException
{
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/ser.ser"), StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("result = " + result);
attachment.flip();
byte[] data = new byte[attachment.limit()];
attachment.get(data);
System.out.println(new String(data));
attachment.clear();
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
try
{
fileChannel.close();
} catch (IOException e){
e.printStackTrace();
}
}
});
}
通过Future写数据: 第二个参数是开始写入数据的文件位置
static void write() throws IOException{
Path path = Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/copy.txt");
AsynchronousFileChannel fileChannel =
AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
buffer.put("test data".getBytes());
buffer.flip();
Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();
while(!operation.isDone());
System.out.println("Write done");
}
通过CompletionHandler写数据
static void write2() throws IOException{
Path path = Paths.get("E:/ModMudEngine/mt/trunk/logic/gsever/test/copy.txt");
if(!Files.exists(path)){
Files.createFile(path);
}
AsynchronousFileChannel fileChannel =
AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;
buffer.put("test dataa".getBytes());
buffer.flip();
fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("bytes written: " + result);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Write failed");
exc.printStackTrace();
try{
fileChannel.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
});
}
close时如果有未完成的操作或继续启动操作会抛出异常。