AIO入门实例

AIO入门实例

摘要:阅读《Netty权威指南》笔记
AIO概念:
AIO即JDK7开始引入的NIO,提供了异步文件通道和异步到街子通道的实现。异步通道提供两种方式操作结果:

  1. 通过java.util.concurrent.Future类来表示异步操作的结果;
  2. 在执行异步操作时传入一个java.nio.channels.CompletionHandler接口的实现类作为操作完成的回调(本例子主要使用这个方法)。

AIO创建的TimeServer

public class TimeServer {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
            }
        }
        AsyncTimeServerHander timeServerHander = new AsyncTimeServerHander(port);
        new Thread(timeServerHander, "AIO-AsyncTimeServerHandler-001").start();
    }
}

很简单,就是初始化一个线程,接下来看AsyncTimeServerHander 的实现代码:

public class AsyncTimeServerHander implements Runnable {

    private int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    /**
     * 构造方法,创建一个异步的服务端通道AsynchronousServerSocketChannel,调用它的bind方法绑定监
     * 听端口,绑定成功则打印提示到输入台
     *
     * @param port
     */
    public AsyncTimeServerHander(int port) {
        this.port = port;
        try {
            asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
            System.out.println("The time server is start in port " + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //阻塞当前线程,防止服务端异常退出
        latch = new CountDownLatch(1);
        doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 连接客户端的方法,采用的CompletionHandler方法,接收AcceptCompletionHandler作为采用的CompletionHandler方
     * 法实例
     */
    public void doAccept() {
        asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
    }
}

CompletionHandler接口有两个方法:

  1. public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment)
  2. public void failed(Throwable exc, AsyncTimeServerHander attachment)

根据方法名很容易就能理解两个方法的执行时间。
接下来看整个AcceptCompletionHandler 的代码:

public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHander> {
    @Override
    public void completed(AsynchronousSocketChannel result, AsyncTimeServerHander attachment) {
        /**
         * 接收客户端请求,因为AsynchronousServerSocketChannel可以接收成千上万的客户端,
         * 所以回调AsyncTimeServerHander中asynchronousServerSocketChannel.accept方法,
         * 让新的客户端继续接入,最终形成一个循环
         */
        attachment.asynchronousServerSocketChannel.accept(attachment, this);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        /**异步读操作
         * 第一个buffer:接收缓冲区
         * 第二个buffer:异步Channel携带的附件,通知回调的时候作为入参使用(我的理解是当该Channel继续被调用时的参数)
         * CompletionHandler:接收通知回调的业务Handler,这里的实现类是ReadCompletionHandler
         */
        result.read(buffer, buffer, new ReadCompletionHandler(result));
    }

    @Override
    public void failed(Throwable exc, AsyncTimeServerHander attachment) {
        attachment.latch.countDown();
    }
}

AcceptCompletionHandler 就可以看出,主要是通过不断的回调来实现非阻塞,同时不会对像NIO那样需要不断的判断连接的状态去根据具体的状态分配Handler。
继续往下看ReadCompletionHandler

public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {

    private AsynchronousSocketChannel channel;

    /**
     * 构造方法:将AsynchronousSocketChannel作为构造方法的参数传入,作为成员变量使用,
     * 主要用于半包消息和发送应答
     *
     * @param channel
     */
    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        if (channel != null) {
            this.channel = channel;
        }
    }

    /**
     * 读取到消息的处理
     *
     * @param result
     * @param attachment
     */
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        attachment.flip();
        byte[] body = new byte[attachment.remaining()];
        attachment.get(body);
        try {
            String req = new String(body, "UTF-8");
            System.out.println("The time server receive order : " + req);
            //消息判断,如果是正确的消息,调用doWrite发送当前消息到客户端
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ?
                    new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
            doWrite(currentTime);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息到客户端
     *
     * @param currentTime
     */
    private void doWrite(String currentTime) {
        if (currentTime != null && currentTime.trim().length() > 0) {
            byte[] bytes = currentTime.getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
            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
                public void failed(Throwable exc, ByteBuffer attachment) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        //ingore on close
                    }
                }
            });
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ReadCompletionHandler 的主要功能已经在代码里注释好了,理解起来也不是很难

总结

  1. AIO使用起来逻辑上很好理解,但是因为有很多的匿名函数,导致代码看起来很复杂;
  2. AIO非阻塞的思想其实和NIO一样,都是通过不断轮询AsynchronousServerSocketChannelaccept 方法获取连接,分配对应的Handler 去处理业务;
  3. demo很简单,重要的是对代码的理解以及熟练使用API。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章