前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彤哥说netty系列之Java NIO实现群聊(自己跟自己聊上瘾了)

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

作者头像
彤哥
发布2019-11-25 23:06:24
4530
发布2019-11-25 23:06:24
举报
文章被收录于专栏:彤哥读源码彤哥读源码

简介

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

业务逻辑分析

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

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

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

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

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

业务实现

上代码:

代码语言:javascript
复制
// 这是一个内部类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,直接上代码,为了与上一章的代码作区分,彤哥特意加入了一些标记:

代码语言:javascript
复制
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。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 彤哥读源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 业务逻辑分析
  • 业务实现
  • 服务端代码
  • 测试
  • 总结
  • 问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档