专栏首页彤哥读源码彤哥说netty系列之Java NIO实现群聊(自己跟自己聊上瘾了)

彤哥说netty系列之Java NIO实现群聊(自己跟自己聊上瘾了)

简介

上一章我们一起学习了Java中的BIO/NIO/AIO的故事,本章将带着大家一起使用纯纯的NIO实现一个越聊越上瘾的“群聊系统”。

业务逻辑分析

首先,我们先来分析一下群聊的功能点:

(1)加入群聊,并通知其他人;

(2)发言,并通知其他人;

(3)退出群聊,并通知其他人;

一个简单的群聊系统差不多这三个功能足够了,为了方便记录用户信息,当用户加入群聊的时候自动给他分配一个用户ID。

业务实现

上代码:

// 这是一个内部类private static class ChatHolder {    // 我们只用了一个线程,用普通的HashMap也可以    static final Map<SocketChannel, String> USER_MAP = new ConcurrentHashMap<>();
    /**     * 加入群聊     * @param socketChannel     */    static void join(SocketChannel socketChannel) {        // 有人加入就给他分配一个id,本文来源于公从号“彤哥读源码”        String userId = "用户"+ ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);        send(socketChannel, "您的id为:" + userId + "\n\r");
        for (SocketChannel channel : USER_MAP.keySet()) {            send(channel, userId + " 加入了群聊" + "\n\r");        }
        // 将当前用户加入到map中        USER_MAP.put(socketChannel, userId);    }
    /**     * 退出群聊     * @param socketChannel     */    static void quit(SocketChannel socketChannel) {        String userId = USER_MAP.get(socketChannel);        send(socketChannel, "您退出了群聊" + "\n\r");        USER_MAP.remove(socketChannel);
        for (SocketChannel channel : USER_MAP.keySet()) {            if (channel != socketChannel) {                send(channel, userId + " 退出了群聊" + "\n\r");            }        }    }
    /**     * 扩散说话的内容     * @param socketChannel     * @param content     */    public static void propagate(SocketChannel socketChannel, String content) {        String userId = USER_MAP.get(socketChannel);        for (SocketChannel channel : USER_MAP.keySet()) {            if (channel != socketChannel) {                send(channel, userId + ": " + content + "\n\r");            }        }    }
    /**     * 发送消息     * @param socketChannel     * @param msg     */    static void send(SocketChannel socketChannel, String msg) {        try {            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);            writeBuffer.put(msg.getBytes());            writeBuffer.flip();            socketChannel.write(writeBuffer);        } catch (Exception e) {            e.printStackTrace();        }    }}

服务端代码

服务端代码直接使用上一章NIO的实现,只不过这里要把上面实现的业务逻辑适时地插入到相应的事件中。

(1)accept事件,即连接建立的时候,说明加入了群聊;

(2)read事件,即读取数据的时候,说明有人说话了;

(3)连接断开的时候,说明退出了群聊;

OK,直接上代码,为了与上一章的代码作区分,彤哥特意加入了一些标记:

public class ChatServer {    public static void main(String[] args) throws IOException {        Selector selector = Selector.open();        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();        serverSocketChannel.bind(new InetSocketAddress(8080));        serverSocketChannel.configureBlocking(false);        // 将accept事件绑定到selector上        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {            // 阻塞在select上            selector.select();            Set<SelectionKey> selectionKeys = selector.selectedKeys();            // 遍历selectKeys            Iterator<SelectionKey> iterator = selectionKeys.iterator();            while (iterator.hasNext()) {                SelectionKey selectionKey = iterator.next();                // 如果是accept事件                if (selectionKey.isAcceptable()) {                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();                    SocketChannel socketChannel = ssc.accept();                    System.out.println("accept new conn: " + socketChannel.getRemoteAddress());                    socketChannel.configureBlocking(false);                    socketChannel.register(selector, SelectionKey.OP_READ);                    // 加入群聊,本文来源于公从号“彤哥读源码”                    ChatHolder.join(socketChannel);                } else if (selectionKey.isReadable()) {                    // 如果是读取事件                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();                    ByteBuffer buffer = ByteBuffer.allocate(1024);                    // 将数据读入到buffer中                    int length = socketChannel.read(buffer);                    if (length > 0) {                        buffer.flip();                        byte[] bytes = new byte[buffer.remaining()];                        // 将数据读入到byte数组中                        buffer.get(bytes);
                        // 换行符会跟着消息一起传过来                        String content = new String(bytes, "UTF-8").replace("\r\n", "");                        if (content.equalsIgnoreCase("quit")) {                            // 退出群聊,本文来源于公从号“彤哥读源码”                            ChatHolder.quit(socketChannel);                            selectionKey.cancel();                            socketChannel.close();                        } else {                            // 扩散,本文来源于公从号“彤哥读源码”                            ChatHolder.propagate(socketChannel, content);                        }                    }                }                iterator.remove();            }        }    }}

测试

打开四个XSHELL客户端,分别连接 telnet127.0.0.18080,然后就可以开始群聊了。

彤哥发现,自己跟自己聊天也是会上瘾的,完全停不下来,不行了,我再去自聊一会儿^^

总结

本文彤哥跟着大家一起实现了“群聊系统”,去掉注释也就100行左右的代码,是不是非常简单?这就是NIO网络编程的魅力,我发现写网络编程也上瘾了^^

问题

这两章我们都没有用NIO实现客户端,你知道怎么实现吗?

提示:服务端需要监听accept事件,所以需要有一个ServerSocketChannel,而客户端是直接去连服务器了,所以直接用SocketChannel就可以了,一个SocketChannel就相当于一个Connection。

本文分享自微信公众号 - 彤哥读源码(gh_63d1b83b9e01),作者:丹卿

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关于JDK源码:我想聊聊如何更高效地阅读

    一,JDK源码是其它所有源码的基础,看懂了JDK源码再看其它的源码会达到事半功倍的效果。

    彤哥
  • 死磕 java集合之PriorityQueue源码分析

    优先级队列,是0个或多个元素的集合,集合中的每个元素都有一个权重值,每次出队都弹出优先级最大或最小的元素。

    彤哥
  • 死磕 java集合之ConcurrentHashMap源码分析(二)

    (3)扩容门槛写死的是桶数组大小的0.75倍,桶数组大小即map的容量,也就是最多存储多少个元素。

    彤哥
  • SPRING框架中ModelAndView、Model、ModelMap区别

    注意:如果方法声明了注解@ResponseBody ,则会直接将返回值输出到页面。 首先介绍ModelMap[Model]和ModelAndView的作用 Mo...

    二十三年蝉
  • 给Python新人练手准备的十个简单趣味脚本

    ? 01. 目录文件分类 前言 有时候,想要对一个目录里的文件进行搜索或者分类操作往往是一件痛苦的事情,下面这个程序的目的是将目录下的文件树以某种分类规则进行...

    小小科
  • 4️⃣ 核酸序列特征分析(4):内含子/外显子剪切位点的识别及Spidey工具应用实例

    真核生物的基因大都为断裂基因,编码序列通常被内含子隔开。内含子和外显子边界和周围序列是前体mRNA内的有保守性的一些特殊核苷酸序列。

    Y大宽
  • 推券客修改登记

    /public_html/App/M/Action/PddAction.class_bak.php

    用户1191760
  • SAP S/4HANA的扩展字段的渲染逻辑

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • Axure tabstrip and different control

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang
  • SAP CRM partner function在客户项目中的实际用途

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    Jerry Wang

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动