Netty网络框架学习笔记-6(Netty简单实现一个群聊_2022.03.14)

  • 实现多人群聊 / 一对一私聊
  • 服务器端:可以监测用户上线,离线,并实现消息转发功能

1.0 编写netty服务端

@Slf4j
public class GroupChatServer {

    public static void main(String[] args) {
        NioEventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(2);
        NioEventLoopGroup workerEventLoopGroup = new NioEventLoopGroup();

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossEventLoopGroup, workerEventLoopGroup)
                .channel(NioServerSocketChannel.class)
                .childOption(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        // 添加框架提供的字符串编解码处理器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new GroupChatServerChannelHandler());
                    }
                });

        try {
            ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress("localhost", 8888)).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        log.info("GroupChatServer==, 服务器已经启动成功, 等待连接中!");
                    }
                }
            });
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("GroupChatServer===, 发生异常:{}", e);
        } finally {
            bossEventLoopGroup.shutdownGracefully();
            workerEventLoopGroup.shutdownGracefully();
        }

    }
}

1.1 服务端处理器

@Slf4j
public class GroupChatServerChannelHandler extends SimpleChannelInboundHandler<String> {

    private static ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap();

    //定义一个 channle 组,管理所有的 channel
    //GlobalEventExecutor.INSTANCE) 是全局的事件执行器,是一个单例
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("GroupChatServerChannelHandler-channelActive 通道上线了!");
        log.info("GroupChatServerChannelHandler-channelActive 远程地址{}!", ctx.channel().remoteAddress());
    }

    /**
     * handlerAdded 表示连接建立,一旦连接,第一个被执行, 添加通道组
     *
     * @param ctx
     * @author: ZhiHao
     * @date: 2022/3/14
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        channelGroup.add(ctx.channel());
    }

    /**
     * 断开连接, 将 xx 客户离开信息推送给当前在线的客户
     *
     * @param ctx
     * @author: ZhiHao
     * @date: 2022/3/14
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //channelGroup.remove(channel); // 自动会删除, 无需调用!
        String id = channel.id().asLongText();
        String number = concurrentHashMap.get(id);
        concurrentHashMap.remove(id);
        //将该客户离线的信息推送给其它在线的客户端, 该方法会将 channelGroup 中所有的 channel 遍历,并发送 消息
        channelGroup.writeAndFlush("[ 客 户 端 ]" + number + "号 离开 聊 天 " + " \n");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("GroupChatServerChannelHandler-channelInactive 通道离线了!", ctx.channel().remoteAddress());
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("GroupChatServerChannelHandler-channelRead0, 读取到数据{}!", msg);
        Channel channel = ctx.channel();
        String id = channel.id().asLongText();
        // 是否包含此客户端, 不包含则添加
        if (!concurrentHashMap.containsKey(id) && NumberUtil.isNumber(msg)) {
            concurrentHashMap.put(id, msg);
            //将该客户加入聊天的信息推送给其它在线的客户端, 该方法会将 channelGroup 中所有的(包含自己) channel 遍历,并发送 消息
            channelGroup.writeAndFlush("[ 客 户 端 ]" + msg + "号 加 入 聊 天 " + " \n");
            return;
        }
        // 否则是将消息转发给其他客户端
        channelGroup.forEach((ch)->{
            if (channel != ch){
                ch.writeAndFlush(msg);
            }else {
                // 自己回显发送的信息
                channel.writeAndFlush(msg);
            }
        });
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        log.info("GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

2.0 编写客户端

@Slf4j
public class GroupChatClient {

    private static int number = 1;

    public static void main(String[] args) {
        NioEventLoopGroup worker = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(worker)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE,true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        // 添加框架提供的字符串编解码处理器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new GroupChatClientChannelHandler());
                    }
                });

        try {

            ChannelFuture channelFuture = bootstrap.
                    connect(new InetSocketAddress("localhost", 8888)).sync();
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        Channel channel = future.channel();
                        log.info("GroupChatClient==, 服务端连接成功{}!", channel.remoteAddress());
                        // 这里的线程不能阻塞, 因为这个通道与这个线程绑定了, 后面的读写都是使用这个线程
                        channel.writeAndFlush(String.valueOf(RandomUtil.getRandom().nextInt(8)));
                    }
                }
            });

            worker.execute(()->{
                Scanner scan = new Scanner(System.in);
                while (scan.hasNextLine()) {
                    log.info("GroupChatClient==, 请输入发送信息!");
                    String next = scan.nextLine();
                    channelFuture.channel().writeAndFlush(next);
                }
            });

            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("GroupChatClient==, 发生异常:{}",e);
        }finally {
            worker.shutdownGracefully();
        }
    }
}

2.1 客户端处理器

@Slf4j
public class GroupChatClientChannelHandler extends SimpleChannelInboundHandler<String> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("GroupChatClientChannelHandler- 通道激活了!");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        log.info("GroupChatClientChannelHandler-channelRead0 读取到的数据:{}!",msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

3.0 结果

先启动服务端, 然后启动多个客户端进行测试

12:13:17.541 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatServer - GroupChatServer==, 服务器已经启动成功, 等待连接中!
12:13:20.947 [nioEventLoopGroup-2-1] WARN io.netty.bootstrap.ServerBootstrap - Unknown channel option 'SO_BACKLOG' for channel '[id: 0xeb527441, L:/127.0.0.1:8888 - R:/127.0.0.1:59538]'
12:13:20.971 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 通道上线了!
12:13:20.971 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 远程地址/127.0.0.1:59538!
12:13:20.994 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelRead0, 读取到数据7!
12:13:21.006 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!
12:13:25.084 [nioEventLoopGroup-2-1] WARN io.netty.bootstrap.ServerBootstrap - Unknown channel option 'SO_BACKLOG' for channel '[id: 0x1e4d20bf, L:/127.0.0.1:8888 - R:/127.0.0.1:59558]'
12:13:25.086 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 通道上线了!
12:13:25.086 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 远程地址/127.0.0.1:59558!
12:13:25.114 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelRead0, 读取到数据3!
12:13:25.116 [nioEventLoopGroup-3-2] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!
12:13:30.519 [nioEventLoopGroup-2-1] WARN io.netty.bootstrap.ServerBootstrap - Unknown channel option 'SO_BACKLOG' for channel '[id: 0x2fa2dae6, L:/127.0.0.1:8888 - R:/127.0.0.1:59578]'
12:13:30.550 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 通道上线了!
12:13:30.550 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelActive 远程地址/127.0.0.1:59578!
12:13:30.550 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelRead0, 读取到数据3!
12:13:30.558 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!
12:13:56.023 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelRead0, 读取到数据你们好, 我是7号!
12:13:56.026 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!
12:14:16.829 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelRead0, 读取到数据我是三号!
12:14:16.830 [nioEventLoopGroup-3-3] INFO com.zhihao.netty.groupchat.GroupChatServerChannelHandler - GroupChatServerChannelHandler-channelReadComplete 通道读取数据完毕!
// -----------------------------------------------------------------------
12:13:20.933 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler- 通道激活了!
12:13:20.935 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 服务端连接成功localhost/127.0.0.1:8888!
12:13:21.007 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:[ 客 户 端 ]7号 加 入 聊 天  
!
12:13:25.117 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:[ 客 户 端 ]3号 加 入 聊 天  
!
12:13:30.554 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:[ 客 户 端 ]3号 加 入 聊 天  
!

12:13:46.437 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 请输入发送信息!
你们好, 我是7号
12:13:56.022 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 请输入发送信息!
12:13:56.026 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:你们好, 我是7号!
12:14:16.830 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:我是三号!
// -----------------------------------------------------------------------
12:13:30.522 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler- 通道激活了!
12:13:30.524 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 服务端连接成功localhost/127.0.0.1:8888!
12:13:30.554 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:[ 客 户 端 ]3号 加 入 聊 天  
!
12:13:56.026 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:你们好, 我是7号!

12:14:11.116 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 请输入发送信息!
我是三号
12:14:16.828 [nioEventLoopGroup-2-2] INFO com.zhihao.netty.groupchat.GroupChatClient - GroupChatClient==, 请输入发送信息!
12:14:16.830 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.groupchat.GroupChatClientChannelHandler - GroupChatClientChannelHandler-channelRead0 读取到的数据:我是三号!

1