zoukankan      html  css  js  c++  java
  • 深入学习Netty(3)——传统AIO编程

    前言

      之前已经整理过了BIO、NIO两种I/O的相关博文,每一种I/O都有其特点,但相对开发而言,肯定是要又高效又简单的I/O编程才是真正需要的,在之前的NIO博文(深入学习Netty(2)——传统NIO编程)中就已经介绍过NIO编程的缺点(相比较而言的缺点:同步非阻塞,需要单独开启线程不断轮询),所以才会有真正的异步非阻塞I/O出现,这就是此篇博文需要介绍的AIO编程。

      参考资料《Netty In Action》、《Netty权威指南》(有需要的小伙伴可以评论或者私信我)

      博文中所有的代码都已上传到Github,欢迎Star、Fork

      感兴趣可以先学习相关博文:

     


    一、NIO 2.0与AIO编程

      JDK 1.7升级了NIO类库,升级后的NIO类库称之为NIO 2.0,Java提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO

      NIO 2.0的异步套接字通道是真正的异步非阻塞I/O,对应有UNIX网络编程中的事件驱动I/O(AIO),相比较NIO,它不需要通过Selector对注册的通道进行轮询操作即可实现异步读写,简化了NIO的编程模型。

      NIO 2.0提供了新的异步通道的概念,异步通道提供了以下两种方式获取操作结果:

    • 通过juc.Futrue类来表示异步操作的结果
    AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
    InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 8080);
    Future<Void> connect = socketChannel.connect(inetSocketAddress);
    while (!connect.isDone()) {
      Thread.sleep(
    10); }
    • 在异步操作的时候传入java.nio.channels。实现CompletionHandler接口complete()的方法作为操作完成回调
    private class MyCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
     
      @Override
      public void completed(Integer result, ByteBuffer attachment) {
        // TODO 回调后业务操作
      }
      @Override
      public void failed(Throwable t, ByteBuffer attachment) {
        t.printStackTrace();
      }

    二、AIO服务端

    (1)服务端AIO异步处理任务AsyncTimeServerHandler

    • 创建异步服务通道并监听端口
    • 异步监听客户端连接
    /**
     * 服务端AIO异步处理任务
     * -创建异步服务通道监听端口
     * -监听客户端连接
     */
    public class AsyncTimeServerHandler implements Runnable{
    
        private int port;
    
        CountDownLatch latch;
        AsynchronousServerSocketChannel asynchronousServerSocketChannel;
    
        public AsyncTimeServerHandler(int port) {
            this.port = port;
            try {
                // 创建异步的服务通道asynchronousServerSocketChannel, 并bind监听端口
                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() {
            // countDownLatch没有count减一,所以导致一直阻塞
            latch = new CountDownLatch(1);
            doAccept();
            try {
                // 防止执行操作线程还未结束,服务端线程就退出,程序不退出的前提下,才能够让accept继续可以回调接受来自客户端的连接
                // 实际开发过程中不需要单独开启线程去处理AsynchronousServerSocketChannel
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 接收客户端的连接
         * 参数CompletionHandler类型的handler实例来接收accept操作成功的通知消息
         */
        public void doAccept() {
            asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
        }
    }

    (2)服务端连接异步回调处理器AcceptCompletionHandler:异步处理客户端连接完成后的操作

    /**
     * 客户端连接异步处理器
     * completed()方法完成回调logic
     * failed()方法完成失败回调logic
     */
    public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {
    
        /**
         * 调用该方法表示客户端已经介接入成功
         * 同时再accept接收新的客户端连接
         * @param result
         * @param attachment
         */
        @Override
        public void completed(AsynchronousSocketChannel result,
                              AsyncTimeServerHandler attachment) {
            // 此时还要继续调用accept方法是因为,completed方法表示上一个客户端连接完成,而下一个新的客户端需要连接
            // 如此形成新的循环:每接收一个客户端的成功连接之后,再异步接收新的客户端连接
            attachment.asynchronousServerSocketChannel.accept(attachment, this);
            // 预分配1M的缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 调用read方法异步读,传入CompletionHandler类型参数异步回调读事件
            result.read(buffer, buffer, new ReadCompletionHandler(result));
        }
    
        @Override
        public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
            exc.printStackTrace();
            // 让服务线程不再阻塞
            attachment.latch.countDown();
        }
    }

    (3)服务端read事件异步回调处理器ReadCompletionHandler:异步回调处理客户端请求数据

    /**
     * 服务端read事件异步处理器
     *  completed异步回调处理客户端请求数据
     */
    public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {
        private AsynchronousSocketChannel channel;
    
        public ReadCompletionHandler(AsynchronousSocketChannel channel) {
            if (this.channel == null) {
                this.channel = channel;
            }
        }
    
        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            attachment.flip();
            // 根据缓冲区的可读字节创建byte数组
            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);
                String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(
                        System.currentTimeMillis()).toString() : "BAD ORDER";
                // 发送当前时间给客户端
                doWrite(currentTime);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    
        private void doWrite(String currentTime) {
            if (currentTime != null && currentTime.trim().length() > 0) {
                byte[] bytes = (currentTime).getBytes();
                ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
                writeBuffer.put(bytes);
                writeBuffer.flip();
                // write异步回调,传入CompletionHandler类型参数
                channel.write(writeBuffer, writeBuffer,
                        new CompletionHandler<Integer, ByteBuffer>() {
                            @Override
                            public void completed(Integer result, ByteBuffer buffer) {
                                // 如果没有发送完成,继续发送
                                if (buffer.hasRemaining()) {
                                    channel.write(buffer, buffer, this);
                                }
                            }
    
                            @Override
                            public void failed(Throwable exc, ByteBuffer attachment) {
                                try {
                                    channel.close();
                                } catch (IOException e) {
                                    // TODO 只要是I/O异常就需要关闭链路,释放资源
    
                                }
                            }
                        });
            }
        }
    
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            try {
                this.channel.close();
            } catch (IOException e) {
                e.printStackTrace();
                // TODO 只要是I/O异常就需要关闭链路,释放资源
            }
        }
    }

    (4)服务端启动TimeServer

    /**
     * AIO 异步非阻塞服务端
     * 不需要单独开线程去处理read、write等事件
     * 只需要关注complete-handlers中的回调completed方法
     */
    public class TimeServer {
    
        public static void main(String[] args) throws IOException {
            int port = 8086;
            AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
            new Thread(timeServer, "AIO-AsyncTimeServerHandler").start();
        }
    }

    (5)启动服务端

    服务端Console:

    使用命令netstat查看8086端口是否监听

    三、AIO客户端

    (1)客户端AIO异步回调处理任务

    • 打开AsynchronousSocketChannel通道,连接服务端
    • 发送服务端指令
    • 回调处理服务端应答
    /**
     * 客户端AIO异步回调处理任务
     * -打开AsynchronousSocketChannel通道,连接服务端
     * -发送服务端指令
     * -回调处理服务端应答
     */
    public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable {
    
        private AsynchronousSocketChannel client;
        private String host;
        private int port;
        private CountDownLatch latch;
    
        public AsyncTimeClientHandler(String host, int port) {
            this.host = host;
            this.port = port;
            try {
                client = AsynchronousSocketChannel.open();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void run() {
            latch = new CountDownLatch(1);
            client.connect(new InetSocketAddress(host, port), this, this);
            try {
                // 防止异步操作都没完成,连接线程就结束退出
                latch.await();
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 发送请求完成异步回调
         * @param result
         * @param attachment
         */
        @Override
        public void completed(Void result, AsyncTimeClientHandler attachment) {
            byte[] req = "QUERY TIME ORDER".getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
            writeBuffer.put(req);
            writeBuffer.flip();
            client.write(writeBuffer, writeBuffer,
                    new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            if (buffer.hasRemaining()) {
                                client.write(buffer, buffer, this);
                            } else {
                                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                                // 回调服务端应答消息
                                client.read(readBuffer, readBuffer,
                                        new CompletionHandler<Integer, ByteBuffer>() {
                                            @Override
                                            public void completed(Integer result, ByteBuffer buffer) {
                                                buffer.flip();
                                                byte[] bytes = new byte[buffer.remaining()];
                                                buffer.get(bytes);
                                                String body;
                                                try {
                                                    body = new String(bytes, "UTF-8");
                                                    System.out.println("Now is : " + body);
                                                    // 服务端应答完成后,连接线程退出
                                                    latch.countDown();
                                                } catch (UnsupportedEncodingException e) {
                                                    e.printStackTrace();
                                                }
                                            }
    
                                            @Override
                                            public void failed(Throwable exc, ByteBuffer attachment) {
                                                try {
                                                    client.close();
                                                    // 防止线程一直阻塞
                                                    latch.countDown();
                                                } catch (IOException e) {
                                                    // ingnore on close
                                                }
                                            }
                                        });
                            }
                        }
    
                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            try {
                                client.close();
                                latch.countDown();
                            } catch (IOException e) {
                                // ingnore on close
                            }
                        }
                    });
        }
    
        @Override
        public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
            exc.printStackTrace();
            try {
                client.close();
                latch.countDown();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    (2)客户端TimeClient

    /**
     * AIO 异步非阻塞 客户端
     * 不需要单独开线程去处理read、write等事件
     * 只需要关注complete-handlers中的回调completed方法
     */
    public class TimeClient {
        public static void main(String[] args) {
            int port = 8086;
            new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClientHandler").start();
    
        }
    }

    (3)启动客户端

    客户端Console:

    服务端Console:

    四、总结

    服务端通过countDownLatch一直阻塞

    由代码实践我们可知:

      JDK底层通过ThreadPoolExecutor执行回调通知,异步回调通知类由sun.nio.ch.AsynchronousChannelGroupImpl实现,然后将任务提交到该线程池以处理I/O事件,并分派给completion-handlers ,该队列消耗对组中通道执行的异步操作的结果

      异步SocketChannel是被动执行,不需要单独像NIO编程那样单独创建一个独立的I/O线程处理读写操作,都是由JDK底层的线程池负责回调并驱动读写操作的。所以基于NIO 2.0的新的异步非阻塞相比较NIO编程要简单,这两区别在于:

    • 在NIO中等待IO事件由我们注册的selector来完成,在感兴趣的事情来了,我们的线程来accept.read.write.connect...解析,解析完后再交由业务逻辑处理。
    • 而在在异步IO(AIO、NIO 2.0)中等待IO事件同样为accept,read,write,connect,但数据处理交由系统完成,我们需要做的就是在completionHandlers中处理业务逻辑回调即可
  • 相关阅读:
    zookeeper基础
    4. Zookeeper单机版安装
    3. 服务中间件Dubbo
    2. Maven工程的搭建
    Maven的优点
    应用架构的演进历史 MVC、 RPC、SOA 和 微服务架构
    js 正则表达式验证
    后台正则表达式验证部分部分总结
    Linux下记录登录用户历史操作
    Django使用Ace实现在线编辑器
  • 原文地址:https://www.cnblogs.com/jian0110/p/14980644.html
Copyright © 2011-2022 走看看