AIO

转载地址:https://blog.csdn.net/Alexwym/article/details/89373011?utm_medium=distribute.pc_relevant.none-task-blog-title-10&spm=1001.2101.3001.4242

上一篇博客中我们介绍了Java中的NIO模型,而JDK1.7之后升级NIO类库,也就是NIO2.0.Java正式提供了异步IO操作,同时提供了与UNIX网络编程事件驱动IO相对应的AIO。NIO(non-block IO)指的是同步非阻塞IO,AIO(Asynchronous IO)则是异步非阻塞IO。我们先来了解一些基础知识,最后再用AIO来设计一个服务器。

一、IO处理模型

服务器往客户端发送数据的过程主要有以下四步:

1、服务器调用read()方法,由用户态转为内核态,把磁盘的数据读到Read Buffer(内核缓存区)上;

2、read()方法返回,把Read Buffer(内核缓存区)的数据读到Buffer上,服务器由内核态转为用户态;之后对Buffer中的数据做进一步处理;

3、数据处理完之后,服务器调用send()方法,由用户态转为内核态,把Buffer中的数据读到Socket Buffer上;

4、将数据读到NIC Buffer(NetWork Interface Card,网卡的缓存区),send()方法返回,服务器由内核态转为用户态;

如果想更详细地了解IO的知识,可以看下这篇博文(https://www.jianshu.com/p/9b2922c10129

二、NIO和AIO的区别

1、同步与异步

NIO,又称同步IO,当应用程序发出一个IO请求后,它需要主动去检查这个IO请求是否完成。

AIO,又称异步IO,当应用程序发出一个IO请求后,就不再管它了,当这个IO请求完成之后,操作系统主动通知应用程序来做后续的处理。

举个例子:你在家里烧开水,你把水放进去烧以后就去看电视了。如果是普通的烧水壶,你每隔一段时间都要去看一眼水烧开了没有,这就是同步IO;但是如果是响水壶,你只要安安静静地看你的电视,等水壶一响,你就知道水烧开了,这就是异步IO。

2、设计模式

NIO采用Reactor的设计模式,而AIO采用Proactor的设计模式。

Reactor和Proactor最主要的区别就是数据的读取和写入Buffer的操作由谁来完成。

对于Reactor而言,它被激活时仅表示当前SocketChannel中有数据来了,应用程序需要自己把SocketChannel中的数据搬到Buffer里面;

而Proactor被激活时则表示SocketChannel中有数据来了,并且我已经帮你把它搬到Buffer里面了,应用程序可以直接对Buffer中的数据进行处理。

3、适用场景

NIO采用了Reactor的模式,读写由应用程序自己进行,适合用于连接数目多且连接短的场景中

AIO采用了Proactor,读写操作由内核完成,适合用于连接数目多且连接长的场景中

三、AIO原理

1、Java回调模式

设想这么一个场景,我们现在需要往磁盘中取数据进行处理,并把处理后的结果发送给客户端。

(1)、传统方法

定义一个类A,实现read()、process()和send()三个方法,在线程中一次调用这三个方法。但是计算很复杂,主线程就会一直阻塞在process()方法中。

(2)、异步调用

我们定义了两个类,其中类A包含read()和send()方法,类B包含process()方法。首先我们启动主线程读取数据,数据读取结束后我们就启动新线程实例化类B,调用它的process()处理数据,主线程就可以做其他事了。等到b.process()方法执行结束后,再主动回调a的send()方法即可。

关于Java回调模式,奉上一篇通俗易懂的博文(《java回调函数详解》

2、AIO原理图

我们根据上面的原理来分析一下AIO的过程

(1)、客户端

A、首先建立一个AsynchronusSocketChannel,绑定端口号,并调用它的connect()方法尝试连接服务器,并指明connect()方法的回调类为ConnectCompletionHandler;

B、连接服务器得到两种返回值。如果连接失败则调用AcceptCompletionHandler类的failed()方法进行后续处理,如果成功,则调用ConnectCompletionHandler类的completion()方法进行后续处理。

(2)、服务端

A、建立一个AsynchronousServerSocketChannel,绑定端口号,调用accept()方法等待客户端连接,并指明accept()方法的回调类为AcceptCompletionHandler;

B、判断连接是否成功,如果失败就调用AcceptCompletionHandler的failed()方法,如果成功就调用AcceptCompletionHandler的completed()方法;

C、如果调用了AcceptCompletionHandler的completed()方法,即连接成功时。服务端总共进行了三个操作。一个是建立AsynchronousSocketChannel与当前的客户端建立连接(调用completed()方法之前),一个是调用AsynchronousServerSocketChannel.accept()继续处理其他的客户端连接(调用completed()方法时),最后一个是调用AsychronousSocketChannel.read()进行客户端消息的读操作,并指明回调类为ReadCompletionHandler

D、如果AsynchronousSocketChannel接收到数据,在接收完数据后,会调用ReadCompletionHandler.completed()方法对消息进行处理

(3)注意点

A、所有方法的回调结果都由相应的CompletionHandler类的进行处理,eg.accept()方法由AcceptCompletionHandler类(实现了CompletionHandler接口的类)进行处理。

四、代码实现

1、服务器

(1)、TimeServer类

  1.  
    package aioserver;
  2.  
     
  3.  
    import java.io.IOException;
  4.  
    import java.net.ServerSocket;
  5.  
    import java.net.Socket;
  6.  
     
  7.  
    public class TimeServer {
  8.  
     
  9.  
    public static void main(String[] args) throws IOException {
  10.  
    int port = 8080;
  11.  
    if (args != null && args.length > 0) {
  12.  
    try{
  13.  
    port = Integer.valueOf(args[0]);
  14.  
    }catch(NumberFormatException e){
  15.  
     
  16.  
    }
  17.  
    }
  18.  
     
  19.  
    /*
  20.  
    *创建异步的时间服务器处理类,并启动线程将其拉起
  21.  
    */
  22.  
    AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
  23.  
     
  24.  
    new Thread(timeServer,"AIO-AsyncTimeServerHandler-001").start();
  25.  
    }
  26.  
    }

(2)、AsyncTimeServerHandler类

  1.  
    package aioserver;
  2.  
     
  3.  
    import java.io.IOException;
  4.  
    import java.net.InetSocketAddress;
  5.  
    import java.nio.ByteBuffer;
  6.  
    import java.nio.channels.AsynchronousServerSocketChannel;
  7.  
    import java.nio.channels.AsynchronousSocketChannel;
  8.  
    import java.nio.channels.CompletionHandler;
  9.  
    import java.util.concurrent.CountDownLatch;
  10.  
     
  11.  
    public class AsyncTimeServerHandler implements Runnable{
  12.  
    private int port;
  13.  
    /*
  14.  
    * CountDownLatch类位于java.util.concurrent包下,
  15.  
    * 利用它可以实现类似计数器的功能。比如有一个任务A,
  16.  
    * 它要等待其他4个任务执行完毕之后才能执行,
  17.  
    * 此时就可以利用CountDownLatch来实现这种功能了。
  18.  
    */
  19.  
    CountDownLatch latch;
  20.  
    AsynchronousServerSocketChannel asychronousServerSocketChannel;
  21.  
     
  22.  
    public AsyncTimeServerHandler(int port) {
  23.  
    this.port = port;
  24.  
    try {
  25.  
    //1、创建一个异步的服务器通道AsynchronousServerSocketChannel,并绑定端口号
  26.  
    asychronousServerSocketChannel = AsynchronousServerSocketChannel.open();
  27.  
    asychronousServerSocketChannel.bind(new InetSocketAddress(port));
  28.  
    System.out.println("The time server is start in port : " + port);
  29.  
    } catch (IOException e) {
  30.  
    // TODO Auto-generated catch block
  31.  
    e.printStackTrace();
  32.  
    }
  33.  
     
  34.  
    }
  35.  
     
  36.  
    public void run() {
  37.  
    //初始化CountDownLantch为1,在完成一组正在执行的操作之前,允许当前线程一直阻塞
  38.  
    //这里是为了防止服务端执行完成退出
  39.  
    //在实际的项目应用中,不需要启动独立的线程来处理AsynchronousServerSocketChannel?
  40.  
    latch = new CountDownLatch(1);
  41.  
     
  42.  
    doAccept();
  43.  
    //等待线程执行完毕
  44.  
    try {
  45.  
    //await()和wait()的区别
  46.  
    latch.await();
  47.  
    } catch (InterruptedException e) {
  48.  
    // TODO Auto-generated catch block
  49.  
    e.printStackTrace();
  50.  
    }
  51.  
    }
  52.  
     
  53.  
    private void doAccept() {
  54.  
    //1、调用accept()方法,并指明回调类为AcceptCompletionHandler,不过这里用了匿名内部类,没有定义出具体的类名
  55.  
    asychronousServerSocketChannel.accept(this, new CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler>() {
  56.  
    //2、判断是否连接成功,成功就调用completed()方法
  57.  
    @Override
  58.  
    public void completed(AsynchronousSocketChannel result,
  59.  
    AsyncTimeServerHandler attachment) {
  60.  
    //3、调用accept()方法,监听其他的客户端连接
  61.  
    attachment.asychronousServerSocketChannel.accept(attachment, this);
  62.  
    //链路建立成功之后,服务端需要接收客户端的请求消息,
  63.  
    //创建新的ByteBuffer,预分配1M的缓冲区。
  64.  
    ByteBuffer buffer = ByteBuffer.allocate(1024);
  65.  
    //3、调用AsynchronousSocketChannel的read方法进行异步读操作,并指明回调类为ReadCompletionHandler
  66.  
    result.read(buffer, buffer, new ReadCompletionHandler(result));
  67.  
    }
  68.  
     
  69.  
    @Override
  70.  
    public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
  71.  
    exc.printStackTrace();
  72.  
    attachment.latch.countDown();
  73.  
    }
  74.  
    });
  75.  
     
  76.  
    }
  77.  
     
  78.  
     
  79.  
    }

(3)、ReadCompletionHandler类

  1.  
    package aioserver;
  2.  
     
  3.  
    import java.nio.ByteBuffer;
  4.  
    import java.nio.channels.CompletionHandler;
  5.  
    import java.io.IOException;
  6.  
    import java.io.UnsupportedEncodingException;
  7.  
    import java.nio.channels.AsynchronousSocketChannel;
  8.  
     
  9.  
    public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
  10.  
    private AsynchronousSocketChannel channel;
  11.  
     
  12.  
    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
  13.  
    //将AsynchronousSocketChannel通过参数传递到ReadCompletion Handler中当作成员变量来使用
  14.  
    //主要用于读取半包消息和发送应答。本例程不对半包读写进行具体说明
  15.  
    if (this.channel == null)
  16.  
    this.channel = channel;
  17.  
    }
  18.  
     
  19.  
    //4、读取消息结束,调用ReadCompletionHandler.completed()方法进行处理
  20.  
    @Override
  21.  
    public void completed(Integer result, ByteBuffer attachment) {
  22.  
    //读取到消息后的处理,首先对attachment进行flip操作,为后续从缓冲区读取数据做准备。
  23.  
    attachment.flip();
  24.  
    //根据缓冲区的可读字节数创建byte数组
  25.  
    byte[] body = new byte[attachment.remaining()];
  26.  
    attachment.get(body);
  27.  
    try {
  28.  
    //通过new String方法创建请求消息,对请求消息进行判断,
  29.  
    //如果是"QUERY TIME ORDER"则获取当前系统服务器的时间,
  30.  
    String req = new String(body, "UTF-8");
  31.  
    System.out.println("The time server receive order : " + req);
  32.  
    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(
  33.  
    System.currentTimeMillis()).toString() : "BAD ORDER";
  34.  
    //调用doWrite方法发送给客户端。
  35.  
    doWrite(currentTime);
  36.  
    } catch (UnsupportedEncodingException e) {
  37.  
    e.printStackTrace();
  38.  
    }
  39.  
    }
  40.  
     
  41.  
    private void doWrite(String currentTime) {
  42.  
    if (currentTime != null && currentTime.trim().length() > 0) {
  43.  
    //首先对当前时间进行合法性校验,如果合法,调用字符串的解码方法将应答消息编码成字节数组,
  44.  
    //然后将它复制到发送缓冲区writeBuffer中,
  45.  
    byte[] bytes = (currentTime).getBytes();
  46.  
    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
  47.  
    writeBuffer.put(bytes);
  48.  
    writeBuffer.flip();
  49.  
    //最后调用AsynchronousSocketChannel的异步write方法。
  50.  
    //正如前面介绍的异步read方法一样,它也有三个与read方法相同的参数,
  51.  
    //在本例程中我们直接实现write方法的异步回调接口CompletionHandler。
  52.  
    channel.write(writeBuffer, writeBuffer,
  53.  
    new CompletionHandler<Integer, ByteBuffer>() {
  54.  
    @Override
  55.  
    public void completed(Integer result, ByteBuffer buffer) {
  56.  
    //对发送的writeBuffer进行判断,如果还有剩余的字节可写,说明没有发送完成,需要继续发送,直到发送成功。
  57.  
    if (buffer.hasRemaining())
  58.  
    channel.write(buffer, buffer, this);
  59.  
    }
  60.  
     
  61.  
    @Override
  62.  
    public void failed(Throwable exc, ByteBuffer attachment) {
  63.  
    //关注下failed方法,它的实现很简单,就是当发生异常的时候,对异常Throwable进行判断,
  64.  
    //如果是I/O异常,就关闭链路,释放资源,
  65.  
    //如果是其他异常,按照业务自己的逻辑进行处理,如果没有发送完成,继续发送.
  66.  
    //本例程作为简单demo,没有对异常进行分类判断,只要发生了读写异常,就关闭链路,释放资源。
  67.  
    try {
  68.  
    channel.close();
  69.  
    } catch (IOException e) {
  70.  
    // ingnore on close
  71.  
    }
  72.  
    }
  73.  
    });
  74.  
    }
  75.  
    }
  76.  
     
  77.  
    @Override
  78.  
    public void failed(Throwable exc, ByteBuffer attachment) {
  79.  
    try {
  80.  
    this.channel.close();
  81.  
    } catch (IOException e) {
  82.  
    e.printStackTrace();
  83.  
    }
  84.  
    }
  85.  
    }

 

说明:本文代码部分主要来自《netty权威指南一书》

C10k系列文章:

《C10k破局(一)——线程池和消息队列实现高并发服务器》

《C10k破局(二)——Java NIO实现高并发服务器(一张图看懂Java NIO)》

《C10破局(三)——Java AIO实现高并发服务器》

posted @ 2020-11-05 19:50  清风名曰  阅读(1017)  评论(0)    收藏  举报