【Java网络编程】基于BIO/NIO/AIO的多人聊天室(五):AIO聊天室

课程《一站式学习Java网络编程 全面理解BIO/NIO/AIO》的学习笔记(五):
异步调用机制 & AIO编程模型 & 基于AIO的多人聊天室实现

源码地址:https://github.com/NoxWang/web-program

【Java网络编程】基于BIO/NIO/AIO的多人聊天室(一):java IO与内核IO
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(二):BIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(三):NIO概述与实践
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(四):NIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(六):思维导图

一、异步调用机制

1.1 AIO中的异步操作

  • AsynchronousSocketChannel:客户端Socket通道
  • AsynchronousServerSocketChannel:服务端Socket通道
  • connect / accept:建立连接(支持异步操作)
  • read / write:读写操作(支持异步操作)
    AIO异步操作

1.2 异步调用机制

1.2.1 Future

Channel异步调用connect / accept,read / write等方法,方法返回一个Future对象。获得Future对象后,我们可以对它进行如下操作:

  • 调用Future的get()方法:该方法是阻塞式调用,它会一直阻塞,直到Future所对应的任务完成并返回。
  • 调用Future的isDone()方法:再一个循环中调用该方法,不停询问任务是否已完成,可设置超时时间。

1.2.2 CompletionHandler

Channel异步调用connect / accept,read / write等方法时,将一个CompletionHandler作为参数传入。

CompletionHandler是一个接口,它提供两个回调函数(callback),completed()failed(),我们可以自己实现这两个方法。当异步操作完成后,系统会调用回调函数(操作成功则调用completed(),发生异常则调用failed()),完成相关业务功能。

二、AIO编程模型

2.1 AsynchronousChannelGroup

我们使用的异步通道属于一个AsynchronousChannelGroup,即异步通道组,是一组可以被多个异步通道共享的资源群组。Group可以自定义,如果不特别指定,系统会使用默认群组。

需要ChannelGroup的原因:AIO机制下,操作系统帮我们做了很多事,比如当调用完成时,操作系统会自动回调函数。操作系统完成这些功能需要一定的系统资源,比如线程池。Group中会包含线程池等资源,操作系统可以复用线程池中的线程来实现回调函数等功能。

2.2 异步实现

  1. 创建AsynchronousServerSocketChannel后,需要创建一个AcceptHandler,用于处理accept()的异步调用,Client1连接后,会触发该Handler;
  2. AcceptHandler会获得连接后返回的AsynrounousSocketChannel。服务端Channel需要进行读写操作,这些操作也是异步调用的,因此需要再创建一个ClientHandler用于处理客户端的读写调用;
  3. 每当有新客户端连接,都会再次触发AcceptHandler,完成相应操作(即为新的客户端Channel注册一个ClientHandler)
  4. 当某个客户端有IO操作,会触发对应的ClientHandler,完成特定的业务操作
    AIO调用机制

三、基于AIO的多人聊天室实现

3.1 服务端

在服务端,我们使用CompletionHandler来实现异步调用

package server;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatServer {

    private static final String LOCALHOST = "localhost";
    private static final int DEFAULT_PORT = 8888;
    private int port;

    private static final int BUFFER_SIZE = 1024;
    private static final int THREADPOOL_SIZE = 8;

    private static final String QUIT = "\\quit";
    private Charset charset = Charset.forName("UTF-8");

    private AsynchronousChannelGroup channelGroup;
    private AsynchronousServerSocketChannel serverChannel;

    private List<ClientHandler> connectedClients;

    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 void start() {
        try {
            // 创建线程池
            ExecutorService executorService = Executors.newFixedThreadPool(THREADPOOL_SIZE);
            // 创建自定义线程池的ChannelGroup
            channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
            // 创建自定义ChannelGroup的异步服务端Channel
            serverChannel = AsynchronousServerSocketChannel.open(channelGroup);

            serverChannel.bind(new InetSocketAddress(LOCALHOST, port));
            System.out.println("启动服务器,监听端口: " + port + "...");

            while (true) {
                serverChannel.accept(null, new AcceptHandler());
                System.in.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(serverChannel);
        }
    }

    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);

                // 添加用户至在线列表
                addClient(handler);

                ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                clientChannel.read(buffer, buffer, handler);
            }
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("连接失败: " + exc);
        }
    }

    private synchronized void addClient(ClientHandler handler) {
        connectedClients.add(handler);
        System.out.println(getClientName(handler.clientChannel) + "已连接");
    }

    private synchronized void removeClient(ClientHandler handler) {
        connectedClients.remove(handler);
        System.out.println(getClientName(handler.clientChannel) + "已断开");
        close(handler.clientChannel);
    }

    private class ClientHandler implements CompletionHandler<Integer, Object> {

        AsynchronousSocketChannel clientChannel;

        public ClientHandler (AsynchronousSocketChannel channel) {
            this.clientChannel = channel;
        }
        @Override
        public void completed(Integer result, Object attachment) {
            ByteBuffer buffer = (ByteBuffer) attachment;
            if (buffer != null) {   // 说明此时需要处理的是读
                if (result <= 0) {
                    // 客户端异常,移出在线列表
                    removeClient(this);
                } else {
                    // 获取并打印客户端发送来的信息
                    buffer.flip();
                    String fwdMsg = receive(buffer);

                    // 用户准备退出
                    if (readyToQuit(fwdMsg)) {
                        removeClient(this);
                        return;
                    }

                    System.out.println(getClientName(clientChannel) + ": " + fwdMsg);

                    // 给其他客户端发送消息
                    forwardMessage(clientChannel, fwdMsg);
                    buffer.clear();

                    // 持续监听该客户端channel的输入
                    clientChannel.read(buffer, buffer, this);
                }
            }
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("读写失败:" + exc);
        }
    }

    private synchronized void forwardMessage(AsynchronousSocketChannel clientChannel, String fwdMsg) {
        for (ClientHandler handler : connectedClients) {
            if (!clientChannel.equals(handler.clientChannel)) {
                try {
                    ByteBuffer buffer = charset.encode(getClientName(clientChannel) + ": " + fwdMsg);
                    handler.clientChannel.write(buffer, null, handler);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String getClientName(AsynchronousSocketChannel clientChannel) {
        int clientPort = -1;
        try {
            InetSocketAddress address = (InetSocketAddress) clientChannel.getRemoteAddress();
            clientPort = address.getPort();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "客户端[" + clientPort + "]";
    }

    private String receive(ByteBuffer buffer) {
        CharBuffer charBuffer = charset.decode(buffer);
        return String.valueOf(charBuffer);
    }

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        chatServer.start();
    }
}

3.2 客户端

在客户端,我们使用Future来实现异步调用

ChatClient.java

package client;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class ChatClient {

    private static final String LOCALHOST = "localhost";
    private String host;
    private static final int DEFAULT_PORT = 8888;
    private int port;

    private static final int BUFFER_SIZE = 1024;

    private static final String QUIT = "\\quit";
    private Charset charset = Charset.forName("UTF-8");

    private AsynchronousSocketChannel clientChannel;

    public ChatClient() {
        this(LOCALHOST, DEFAULT_PORT);
    }

    public ChatClient (String host, int port) {
        this.host = host;
        this.port = port;
    }

    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 start() {
        try {
            // 创建客户端channel
            clientChannel = AsynchronousSocketChannel.open();
            // 连接服务端,异步调用
            Future<Void> future = clientChannel.connect(new InetSocketAddress(host, port));
            future.get();

            // 处理用户输入
            new Thread(new UserInputHandler(this)).start();

            ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
            while (true) {
                Future<Integer> readResult = clientChannel.read(buffer);
                int result = readResult.get();

                if (result <= 0) {
                    // 说明出现异常,无法再从服务器得到有效信息
                    System.out.println("服务器断开");
                    close(clientChannel);
                    System.exit(1);

                } else {
                    buffer.flip();
                    String msg = String.valueOf(charset.decode(buffer));
                    buffer.clear();
                    System.out.println(msg);
                }
            }

        } catch (IOException | InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public void send(String msg) {
        if (msg.isEmpty()) {
            return;
        }

        ByteBuffer buffer = charset.encode(msg);
        Future<Integer> writeResult = clientChannel.write(buffer);
        try {
            writeResult.get();
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("发送消息失败");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ChatClient client = new ChatClient();
        client.start();
    }
}

UserInputHandler.java

package client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class UserInputHandler implements Runnable {

    private ChatClient chatClient;

    public UserInputHandler (ChatClient client) {
        this.chatClient = client;
    }

    @Override
    public void run() {
        try {
            BufferedReader consoleReader = new BufferedReader(
                    new InputStreamReader(System.in)
            );
            String msg = null;
            while ((msg = consoleReader.readLine()) != null) {
                chatClient.send(msg);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值