通信框架之Netty第四话 - Netty深入了解之简易群聊功能的实现

到这里为止,应该是能实现非长链接的Netty服务提供了吧!
但是!Netty是高性能Nio的框架,大多使用到的场景应该是长链接的,那么本篇文章使用Netty来实现一个实时的群聊房间。也就是服务端和客服端都由Netty实现

1.服务架构

在这里插入图片描述
一个客户端和业务处理的handler、一个服务端和业务处理的handler

上篇文章已经分析过自定义的编解码器,本文就用Netty自带的String协议了
简单的定一个消息协议 name#msg,仅供了解,正常肯定会详细点

2.Netty服务端

2.1 main方法启动服务

public static void main(String[] args) {

        NioEventLoopGroup b1 = new NioEventLoopGroup();
        NioEventLoopGroup b2 = new NioEventLoopGroup();

        ServerBootstrap bs = new ServerBootstrap()
                .group(b1, b2)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        //使用自带的String编解码器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //服务端业务处理的handler
                        pipeline.addLast(new MyChatServerHandler());
                    }
                });

        ChannelFuture f = bs.bind(19900).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("已启动netty服务:" + 19900);
            }
        });
        try {
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            b1.shutdownGracefully();
            b2.shutdownGracefully();
        }
    }

2.2 服务端业务处理Handler

public class MyChatServerHandler extends SimpleChannelInboundHandler<String> {
	//保存长链接的channel通道
    static Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<>();
    /**
     * 协议 name#msg
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("服务端收到消息:" + msg);
        String[] split = msg.split("#");
        if (split.length == 1) {
            //输了昵称才算新加入的
            if (!channelMap.containsKey(ctx.channel().id())) {
                channelMap.put(ctx.channel().id(), ctx.channel());
            }
        }
        //消息群发
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = sdf.format(new Date());
        for (Map.Entry<ChannelId, Channel> entry : channelMap.entrySet()) {
            if (split.length == 1) {
                entry.getValue().writeAndFlush(dateStr + ":欢迎" + msg + "加入群聊!");
            } else {
                entry.getValue().writeAndFlush(dateStr + ":" + msg.replace("#", " : "));
            }
        }
    }
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("新的客户端连接" + ctx.channel());
    }
}

3.Netty客户端

3.1 main方法启动客户端

public static void main(String[] args) {
		//客户端只需要一个事件分组
        NioEventLoopGroup group = new NioEventLoopGroup();
        //构建的类也不同
        Bootstrap bs = new Bootstrap()
                .group(group)
                //选择的channel也不同
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        //handler处理链是一致的
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new MyChatClientHandler());
                    }
                });
		//连接到服务端的指定端口
        ChannelFuture future = bs.connect("127.0.0.1", 19900);
        try {
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            group.shutdownGracefully();
        }
    }

3.2 客户端业务处理Handler

public class MyChatClientHandler extends SimpleChannelInboundHandler<String> {

	//保存第一次连接成功输入的昵称
    static String name;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    	//打印消息
        System.out.println(msg);
    }


    /**
     * 协议 name#msg
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与服务端连接成功:" + ctx.channel() + ",请输入你的昵称:");
        //启动一个发送消息线程 只要键盘输入过就发送到服务端
        new Thread(() -> {
            Scanner sc = new Scanner(System.in);
            while (true) {
                String next = sc.nextLine();
                if (name == null) {
                    //第一次发送则初始化昵称 协议仅供理解,硬是要输昵称带#的忽略
                    name = next;
                    ctx.writeAndFlush(name);
                } else {
                	//发送规定协议的报文
                    ctx.writeAndFlush(name + "#" + next);
                }
            }
        }).start();
    }
}

4.测试

分别启动一个服务端,两个客户端(可多个)。不带时间的为键盘输入日志
客户端1日志:

与服务端连接成功:[id: 0x1b671875, L:/127.0.0.1:53066 - R:/127.0.0.1:19900],请输入你的昵称:
张三
2022-05-09 14:44:43:欢迎张三加入群聊!
2022-05-09 14:44:48:欢迎王五加入群聊!
2022-05-09 14:44:55:王五 : 你好,我是王五
你好,我是张三
2022-05-09 14:45:04:张三 : 你好,我是张三

客户端2日志:

与服务端连接成功:[id: 0x8d57f13c, L:/127.0.0.1:53091 - R:/127.0.0.1:19900],请输入你的昵称:
王五
2022-05-09 14:44:48:欢迎王五加入群聊!
你好,我是王五
2022-05-09 14:44:55:王五 : 你好,我是王五
2022-05-09 14:45:04:张三 : 你好,我是张三

服务端日志:

已启动netty服务:19900
新的客户端连接[id: 0x289276c8, L:/127.0.0.1:19900 - R:/127.0.0.1:53066]
新的客户端连接[id: 0xba54775d, L:/127.0.0.1:19900 - R:/127.0.0.1:53091]
服务端收到消息:张三
服务端收到消息:王五
服务端收到消息:王五#你好,我是王五
服务端收到消息:张三#你好,我是张三

接下来就可以在群聊中任意聊天了

5.总结

上述内容仅供了解使用,正式开发下肯定是要考虑多台机器的,可能channel不在同一台机,这个时候就要使用到分布式中间件了。

预告:
在微服务崛起的时代,服务调用肯定必不可少,RPC通信问题也随之展开,下文将使用Netty来了解什么是RPC

以上就是本章的全部内容了。

上一篇:通信框架之Netty第三话 - Netty的使用以及使用Netty改造Tomcat
下一篇:通信框架之Netty第五话 - 一文了解RPC通信原理并使用Netty实现一个PRC

不见只今汾水上,唯有年年秋雁飞

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值