(七)异步编程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,学习手记(七)

posted @ 2021-03-05 17:16  不学无墅_NKer  阅读(170)  评论(0编辑  收藏  举报