• (七)异步编程AIO


    1.从操作系统的内核角度对比三大模型

       1.1 阻塞式I/O---对应BIO

       1.2 非阻塞式I/O----对应NIO的非阻塞模式

    注意:上图并不包括NIO的Selector监听模式,仅仅对应NIO中把服务端ServerSocketChannel和客户端SocketChannel配置的非阻塞模式。

          1.2.1 I/O多路复用----对于NIO + Selector

    • 第一次系统调用,如果数据尚未准备好,就要求内核监听I/O通道(监听的过程类似于select()函数,是阻塞式的)。直到有数据可供应用程序操作,再通知应用程序。
    • 多路是指,系统内核能够监听多个IO通道。
    • (六)NIO聊天室实战——如同之前的案例一样,我们不仅使用了非阻塞式模型,还使用到了I/O多路复用。

    • Selector还可以翻译成多路复用器。

       1.3 异步I/O----AIO模型

    • 如上三种模型本质上都是同步模型, 同步指的是,如果数据没准备好,即使没有被阻塞住,但想要得到要等待的数据,必须要再发起一次新的系统调用。
    • 下图的异步体现在:我们程序只对数据发起了一次请求,没有请求到,就直接返回了,而之后,当这个数据已经准备好的时候,系统回来通知我们,而不需要我们再次发起请求,就能获取到这个数据,这就体现了异步的特点。A就是asynchronous,也就是异步的意思

    2. 异步调用机制

    如何实现异步操作的呢?有两种方法。

    (1)通过返回Future对象

    注意Future对象的get()方法是阻塞式的,直到Future所对应的任务返回了。

    (2)CompletionHandler(常用)

    • 在执行调用时,传入CompletionHandler。
    • CompletionHandler是一个接口,需要实现其两个方法(回调函数),1.当I/O操作完成的话,需要执行什么逻辑;2.当I/O操作失败,需要执行什么逻辑。

    3.“回音壁”实验结果

     

    4.完整代码

       4.1服务器端

    public class Server {
        final String LOCAL_HOST = "localhost";
        final int DEFAULT_PORT = 8888;
        AsynchronousServerSocketChannel serverChannel;
    
        private void close(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                    System.out.println("关闭" + closeable);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void start() {
            try {
                // 绑定监听端口
                // serverChannel底层默认绑定了一个AsynchronousChannelGroup
                // AsynchronousChannelGroup所包含的线程池中的线程用于各种异步回调函数的操作
                serverChannel = AsynchronousServerSocketChannel.open();
                serverChannel.bind(new InetSocketAddress(LOCAL_HOST,DEFAULT_PORT));
                System.out.println("启动服务器,监听端口:" + DEFAULT_PORT);
    
                // accept()是异步的调用
                while (true) {
                    serverChannel.accept(null, new AcceptHandler());// 参数attachment类似于邮件的附件,也可以不放
                    // 小技巧:阻塞住,保证服务器的主线程不过早的返回,同时避免过于频繁的调用accept函数
                    System.in.read();  // read()是阻塞式调用
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                close(serverChannel);
            }
        }
    
        // 用于处理serverChannel异步调用accept()函数的结果
        private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {
    
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                if (serverChannel.isOpen()) {
                    // 确保服务端还在运行,让服务器继续等待下一个客户端的连接请求
                    // 底层已经进行了保护,不用担心出现栈溢出的问题
                    serverChannel.accept(null, this);
                }
    
                // 处理已连接客户端的读写操作, 读写仍然是异步的
                AsynchronousSocketChannel clientChannel = result;
                if (clientChannel != null && clientChannel.isOpen()) {
                    // 处理客户端通道上异步调用的读/写操作
                    ClientHandler handler = new ClientHandler(clientChannel);
    
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // attachmentInfo包含了clientChannel的回调函数在处理read()结果时需要用到的信息
                    Map<String, Object> attachmentInfo = new HashMap<>();
                    attachmentInfo.put("type", "read");
                    attachmentInfo.put("buffer", buffer);
                    clientChannel.read(buffer, attachmentInfo, handler); // 接收客户端发来的消息,写入到buffer
                }
            }
    
            @Override
            public void failed(Throwable exc, Object attachment) {
                // 处理错误
            }
        }
    
        // 处理异步调用clientChannel的read和write操作结束后返回的结果
        private class ClientHandler implements CompletionHandler<Integer, Object>{
            private AsynchronousSocketChannel clientChannel;
    
            public ClientHandler(AsynchronousSocketChannel channel) {
                this.clientChannel = channel;
            }
    
            @Override
            public void completed(Integer result, Object attachment) {
                Map<String, Object> info = (Map<String, Object>) attachment;
                // 判断完结的操作是读操作 还是 写操作呢?
                String type = (String) info.get("type");
    
                // 如果是读操作完成了,拿到读进buffer的数据,把它写回ClientChannel(即让ClientChannel再读取buffer)
                if ("read".equals(type)) {
                    ByteBuffer buffer = (ByteBuffer) info.get("buffer");
                    buffer.flip(); // 写模式变为读模式
                    // 更改attachment的type为写
                    info.put("type", "write");
                    clientChannel.write(buffer, info, this); // 由buffer读出数据,写入clientChannel
    
                } else if ("write".equals(type)) {
                    // 把信息原封不动发回客户后,继续监听客户发来的消息
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    info.put("type", "read"); // 更新为read
                    info.put("buffer", buffer);
                    clientChannel.read(buffer, info, this);
                }
            }
    
            @Override
            public void failed(Throwable exc, Object attachment) {
                // 处理错误
            }
        }
    
        public static void main(String[] args) {
            Server server = new Server();
            server.start();
        }
    }

       4.2客户端

    public class Client {
    
        final String LOCAL_HOST = "localhost";
        final int DEFAULT_PORT = 8888;
        AsynchronousSocketChannel clientChannel;
    
        private void close(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                    System.out.println("关闭" + closeable);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void start() {
            try {
                // 创建channel
                clientChannel = AsynchronousSocketChannel.open();
    
                // 与服务器不同,演示异步调用机制的另一种实现方法:通过返回Future对象来完成异步机制
                // connect是异步调用
                Future<Void> future = clientChannel.connect(new InetSocketAddress(LOCAL_HOST,DEFAULT_PORT));
                future.get(); // 阻塞式调用,直到建立连接再进行下一步操作
    
                // 等待用户的输入
                BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
                while (true) {
                    String input = consoleReader.readLine(); // 阻塞式调用
    
                    // ① 发送给服务端
                    byte[] inputBytes = input.getBytes();
                    ByteBuffer buffer = ByteBuffer.wrap(inputBytes); // 把用户输入的数据放入到buffer
                    Future<Integer> writeResult = clientChannel.write(buffer); // 把buffer的数据写入到客户端的channel,注意write也是异步的,返回值表示写了多少字节
    
                    writeResult.get(); // 阻塞,等待把buffer的数据写入到客户端的channel,发送给服务器
    
                    // ②读取从服务端发回的消息
                    buffer.flip(); // 读模式变为写模式
                    Future<Integer> readResult = clientChannel.read(buffer); // 异步
    
                    readResult.get(); // 阻塞式等待read操作完成
                    String echo = new String(buffer.array());
                    buffer.clear();
    
                    System.out.println("Server : " + echo);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                close(clientChannel);
            }
        }
    
        public static void main(String[] args) {
            Client client = new Client();
            client.start();
        }
    }

    参考

    一站式学习Java网络编程 全面理解BIO_NIO_AIO,学习手记(七)

  • 相关阅读:
    暑期学习录
    08管道命名符
    07输入输出重定向
    06grep与find命令详解
    05tar命令详解
    04文件目录管理命令
    03工作目录切换命令与文本文件编辑命令
    02系统状态检测命令
    01常用系统工作命令
    vue2.0细节剖析
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14454095.html
Copyright © 2020-2023  润新知