• (八)AIO聊天室实战


    1.AIO模型分析

    • AsyncChannelGroup指的是,可以被多个异同通道所共享的资源群组。其中,最主要的资源有线程池。
    • 不指定的情况下,系统会使用一个默认的AsyncChannelGroup。
    • 在AIO编程模型中,高效率和方便是因为操作系统做了很多的事。
    • 在请求处理的过程中,并不是在主线程中完成的,而是通道组利用线程池资源,在不同的线程中完成异步处理。

    2.实验结果

     

     

    3.完整代码

       3.1服务器端

    public class ChatServer {
    
        private static final String LOCALHOST = "localhost";
        private static final int DEFAULT_PORT = 8888;
        private static final String QUIT = "quit";
        private static final int BUFFER = 1024;
        private static final int THREADPOOL_SIZE = 8;
    
        private AsynchronousChannelGroup channelGroup; // 自定义Group
        private AsynchronousServerSocketChannel serverChannel;
        private List<ClientHandler> connectedClients;
        private Charset charset = Charset.forName("UTF-8");
        private int port;
    
        public ChatServer() {
            this(DEFAULT_PORT);
        }
    
        public ChatServer(int port) {
            this.port = port;
            this.connectedClients = new ArrayList<>();
        }
        private boolean readyToQuit(String msg) {
            return QUIT.equals(msg);
        }
        private void close(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        // 获取客户端的端口号并打印出来
        private String getClientName(AsynchronousSocketChannel clientChannel) {
            int port = -1;
            try {
                InetSocketAddress remoteAddress = (InetSocketAddress) clientChannel.getRemoteAddress();
                port = remoteAddress.getPort();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "客户端[" + port + "]";
        }
    
    
        // 添加和移除用户操作,记得用synchronized修饰
        private synchronized void addClient(ClientHandler clientHandler) {
            connectedClients.add(clientHandler);
            System.out.println(getClientName(clientHandler.getClientChannel()) + "已连接");
    
        }
        private synchronized void removeClient(ClientHandler clientHandler) {
            connectedClients.remove(clientHandler);
            AsynchronousSocketChannel clientChannel = clientHandler.getClientChannel();
            System.out.println(getClientName(clientChannel) + "已断开连接");
            close(clientChannel);
        }
    
        // 转发和接收
        private synchronized String receive(ByteBuffer buffer) {
            return String.valueOf(charset.decode(buffer));
        }
        private synchronized void forwardMessage(AsynchronousSocketChannel clientChannel, String fwdMsg) {
            for (ClientHandler connectedHandler : connectedClients) {
                AsynchronousSocketChannel client = connectedHandler.getClientChannel();
                if (!client.equals(clientChannel)) {
                    // 注意这个try-catch是自己加的,避免因写操作问题而引发服务器宕dang机
                    try {
                        // 将要转发的信息写入到缓冲区中
                        ByteBuffer buffer = charset.encode(getClientName(client) + ": " + fwdMsg);
    //                    ByteBuffer buffer = ByteBuffer.allocate(BUFFER);
    //                    buffer.put(charset.encode(getClientName(client) + ": " + fwdMsg));
    //                    buffer.flip();
                        // 写给其他客户
                        client.write(buffer, null, connectedHandler);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        private void start() {
    
            try {
                // 创建自定义Group
                ExecutorService executorService = Executors.newFixedThreadPool(THREADPOOL_SIZE); // 创建固定数量的线程池
                channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
                // 创建AsynchronousServerSocketChannel,绑定服务端监听端口
                serverChannel = AsynchronousServerSocketChannel.open(channelGroup); // 传入自定义的Group
                serverChannel.bind(new InetSocketAddress(LOCALHOST, port));
                System.out.println("启动服务器,监听端口:" + port + "...");
    
                // 持续监听客户端的连接请求,如果不加循环,第一次异步调用accept后,还没等到回调函数,系统就退出了
                while (true) {
                    serverChannel.accept(null, new AcceptHandler()); // 使用AcceptHandler()的回调函数异步处理accept()
                    // 因为不想浪费资源,过于频繁的调用accept
                    // 希望通过AcceptHandler的回调函数来进一步持续监听从客户端发来的请求
                    System.in.read(); // 阻塞,等待System.in流上的输入数据
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                close(serverChannel);
            }
        }
    
        // CompletionHandler接口泛型的类型,第一个表示异步调用函数应该返回的类型,即accept()返回结果的类型
        // 第二个泛型类型指的是,attachment对象的类型
        private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object> {
    
            @Override
            public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
                if (serverChannel.isOpen()) {
                    serverChannel.accept(null, this); // 让服务器接着等待下一个客户端的连接
                }
                // 接收本次连接的客户端所发送的消息
                if (clientChannel != null && clientChannel.isOpen()) {
                    ClientHandler handler = new ClientHandler(clientChannel); // handler处理特定的clientChannel
                    // 通过Buffer实现和Channel之间的数据交互
                    ByteBuffer buffer = ByteBuffer.allocate(BUFFER);
                    // 将新用户添加到在线用户列表
                    addClient(handler);
    
                    // 第一个buffer参数:读取channel数据写入buffer里; 第二个buffer参数:把buffer当做attachment
                    // read()返回的数据类型是Integer,表示从channel中读到了多少数据。
                    // 如果在回调函数想要得到具体的数据内容是什么,把buffer当做attachment,因为attachment会作为参数传递到回调函数
                    clientChannel.read(buffer,buffer, handler);
                }
            }
    
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("连接失败: " + exc.getMessage());
            }
        }
    
        // 处理在clientChannel上的异步读写操作的结果
        private class ClientHandler implements CompletionHandler<Integer,Object>{
            private AsynchronousSocketChannel clientChannel;
            public ClientHandler(AsynchronousSocketChannel clientChannel) {
                this.clientChannel = clientChannel;
            }
    
            public AsynchronousSocketChannel getClientChannel() {
                return clientChannel;
            }
    
            @Override
            public void completed(Integer result, Object attachment) {
                // 判断完成的是read操作还是write操作,如果是read操作,attachment对象就是包含客户端数据的buffer
                ByteBuffer buffer = (ByteBuffer) attachment;
                // 处理read()的异步调用
                if (buffer != null) {
                    if (result <= 0) {
                        // 没有读到有效数据,客户端异常
                        // 将客户移除在线列表(列表里存放的是每个clientChannel对应的ClientHandler)
                        removeClient(this);
                    }else {
                        buffer.flip(); // 写模式变为读模式
                        // buffer中是编码后的数据,把Byte解码成字符串
                        String fwdMsg = receive(buffer);
                        System.out.println(getClientName(clientChannel) + ": " + fwdMsg);
    
                        // 转发消息
                        forwardMessage(clientChannel, fwdMsg);
                        buffer.clear();
    
                        if (readyToQuit(fwdMsg)) {
                            removeClient(this);
                        }else {
                            clientChannel.read(buffer, buffer, this);
                        }
                    }
                }
            }
    
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("读写操作失败:" + exc.getMessage());
            }
        }
    
        public static void main(String[] args) {
            ChatServer server = new ChatServer();
            server.start();
        }
    }

       3.2客户端

    public class ChatClient {
        private static final String LOCAL_HOST = "localhost";
        private static final int DEFAULT_PORT = 8888;
        private static final String QUIT = "quit";
        private static final int BUFFER = 1024;
    
        private AsynchronousSocketChannel clientChannel;
        private Charset charset = Charset.forName("UTF-8");
    
        public boolean readyToQuit(String msg) {
            return QUIT.equals(msg);
        }
    
        public void close(Closeable closeable) {
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        // 向服务器发送消息
        public void send(String msg) throws ExecutionException, InterruptedException {
            if (msg.isEmpty()) {
                return;
            }
            ByteBuffer buffer = charset.encode(msg);
            Future<Integer> writeResult = clientChannel.write(buffer);
            writeResult.get();
        }
    
        private void start() {
            try {
                // 创建异步通道channel,并发起连接请求
                clientChannel = AsynchronousSocketChannel.open();
                Future<Void> future = clientChannel.connect(new InetSocketAddress(LOCAL_HOST, DEFAULT_PORT));
    
                future.get();  // 阻塞式调用,直到建立连接再进行下一步操作
                System.out.println("已连接到服务器");
    
                // 创建线程处理用户输入
                new Thread(new UserInputHandler(this)).start();
    
                // 主线程中循环读取服务器转发过来的其他客户的消息
                ByteBuffer buffer = ByteBuffer.allocate(BUFFER);
                while (true) {
                    Future<Integer> readResult = clientChannel.read(buffer);
                    int result = readResult.get(); // 阻塞式读取数据
                    if (result <= 0) {
                        // 发生异常,没有读取到数据
                        close(clientChannel);
                        System.out.println("与服务器连接异常");
                        System.exit(1); // 停止程序,否则quit后,会出现报错
                    }else {
                        // 正常打印消息
                        buffer.flip();
                        String msg = String.valueOf(charset.decode(buffer));
                        buffer.clear();
                        System.out.println(msg);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                close(clientChannel);
            }
        }
    
        public static void main(String[] args) {
            ChatClient client = new ChatClient();
            client.start();
        }
    }
    public class UserInputHandler implements Runnable {
        private ChatClient client;
    
        public UserInputHandler(ChatClient chatClient) {
            this.client = chatClient;
        }
    
        @Override
        public void run() {
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                try {
                    String msg = consoleReader.readLine();
                    client.send(msg);
                    if (client.readyToQuit(msg)) {
                        System.out.println("成功推出聊天室");
                        break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    参考

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

  • 相关阅读:
    Python的with语句(文件打开方式)
    python代码异常范围检查方法(非常实用)
    python一标准异常总结大全(非常全)
    python里pickle模块
    pyhon文件操作典型代码实现(非常经典!)
    codeblocks中cocos2dx项目添加新的.cpp和.h文件后编译运行的方法
    ubuntu安装cocos2dx
    学习资料整理
    Spring学习笔记--在SpEL中筛选集合
    Spring学习笔记--Spring表达式语言SpEL
  • 原文地址:https://www.cnblogs.com/HuangYJ/p/14457179.html
Copyright © 2020-2023  润新知