Netty剖析之NIO群聊系统

前言

在前面我们详细讲解了BIO、NIO,本次我们将使用NIO来完成一个群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞);

群聊系统基本要求:

  • 服务器端可监测客户端上线、离线、并转发客户端消息;
  • 客户端可群发消息给其它用户,也可接收其它客户发送的消息;

代码示例

服务端:

public class GroupChatServer {

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;

    public GroupChatServer() {
    }

    public GroupChatServer(String host, int port) {
        try {
            // 创建选择器
            selector = Selector.open();
            // 创建ServerSocketChannel
            serverSocketChannel = ServerSocketChannel.open();
            // 绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(host, port));
            // 设置非阻塞
            serverSocketChannel.configureBlocking(false);
            // 将ServerSocketChannel注册到selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listen() throws Exception {
        while (true) {
            if (selector.select() > 0) { // 监控是否有事件发生
                // 获取有事件发生的SelectionKey
                Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
                while (selectionKeyIterator.hasNext()) {
                    SelectionKey selectionKey = selectionKeyIterator.next();
                    if (selectionKey.isAcceptable()) { // 连接事件
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);

                        // 注册到selector
                        socketChannel.register(selector, SelectionKey.OP_READ);

                        System.out.println(socketChannel.getRemoteAddress() + "上线啦~");
                    } else if (selectionKey.isReadable()) { // 读取事件
                        // 反向获取Channel
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        // 创建buffer
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                        try {
                            if (socketChannel.read(byteBuffer) > 0) {
                                // 读取到的消息
                                String msg = new String(byteBuffer.array());
                                System.out.println("接收到[" + socketChannel.getRemoteAddress() + "]发送的消息:" + msg);

                                // 群发消息
                                this.sendMsgToOtherClient(msg, socketChannel);
                            }
                        } catch (IOException e) {
                            System.out.println(socketChannel.getRemoteAddress() + "离线啦~");
                            selectionKey.channel(); // 取消此通道注册
                            socketChannel.close(); // 关闭此通道
                        }
                    }
                    selectionKeyIterator.remove();
                }
            }
        }
    }

    /**
     * 转发消息给其它通道
     *
     * @param msg  转发的消息
     * @param self 当前通道,也就是群发排除的通道
     * @throws IOException
     */
    private void sendMsgToOtherClient(String msg, SocketChannel self) throws IOException {
        // 向其它客户端群发消息
        System.out.println("服务端开始转发消息。。。");

        // 获取所有注册到selector上的SocketChannel
        Set<SelectionKey> keySet = selector.keys();
        for (SelectionKey key : keySet) {
            Channel keyChannel = key.channel();
            // 群发时排除自己
            if (keyChannel instanceof SocketChannel && keyChannel != self) {
                SocketChannel targetChannel = (SocketChannel) keyChannel;

                // 将读取到的消息写入到SocketChannel
                targetChannel.write(ByteBuffer.wrap(msg.getBytes()));
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new GroupChatServer("127.0.0.1", 8899).listen();
    }
}

客户端:

public class GroupChatClient {

    private Selector selector;
    private SocketChannel socketChannel;
    private String clientName;

    public GroupChatClient() {
    }

    public GroupChatClient(String serverHost, int serverPort) {
        try {
            // 创建一个selector
            selector = Selector.open();
            // 连接服务端
            socketChannel = SocketChannel.open(new InetSocketAddress(serverHost, serverPort));
            // 设置非阻塞
            socketChannel.configureBlocking(false);
            // 将客户端通道注册到selector
            socketChannel.register(selector, SelectionKey.OP_READ);

            clientName = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(clientName + "准备就绪~");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息
     *
     * @param msg 消息
     * @throws IOException
     */
    public void sendMsg(String msg) throws IOException {
        msg = clientName + "说 :" + msg;
        socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
    }

    /**
     * 读取消息
     */
    public void readMsg() throws IOException {
        if (selector.select() > 0) { // 有事件发生的通道
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
            while (selectionKeyIterator.hasNext()) {
                SelectionKey selectionKey = selectionKeyIterator.next();
                if (selectionKey.isReadable()) { // 读取事件
                    // 反向获取channel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 创建一个buffer
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    // 读取消息
                    socketChannel.read(byteBuffer);

                    System.out.println("收到一条消息:" + new String(byteBuffer.array()));
                }

                selectionKeyIterator.remove();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1", 8899);

        // 独立一个线程进行消息读取
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        groupChatClient.readMsg();

                        Thread.currentThread().sleep(2000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            // 获取控制台输入
            String inputMsg = scanner.nextLine();
            groupChatClient.sendMsg(inputMsg);
        }
    }
}

通讯演示:
服务端:

/127.0.0.1:10481上线啦~
/127.0.0.1:10499上线啦~
/127.0.0.1:10537上线啦~
接收到[/127.0.0.1:10481]发送的消息:127.0.0.1:10481说 :client1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
服务端开始转发消息。。。
接收到[/127.0.0.1:10499]发送的消息:127.0.0.1:10499说 :client2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
服务端开始转发消息。。。
接收到[/127.0.0.1:10537]发送的消息:127.0.0.1:10537说 :client3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
服务端开始转发消息。。。
/127.0.0.1:10481离线啦~
/127.0.0.1:10499离线啦~
/127.0.0.1:10537离线啦~

client1:

127.0.0.1:10481准备就绪~
client1
收到一条消息:127.0.0.1:10499说 :client2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
收到一条消息:127.0.0.1:10537说 :client3   

client2:

127.0.0.1:10499准备就绪~
收到一条消息:127.0.0.1:10481说 :client1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
client2
收到一条消息:127.0.0.1:10537说 :client3    

client3:

127.0.0.1:10537准备就绪~
收到一条消息:127.0.0.1:10481说 :client1                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
收到一条消息:127.0.0.1:10499说 :client2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
client3
发布了107 篇原创文章 · 获赞 19 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/chen_changying/article/details/104137987