Netty群聊系统

前言

当前需求基于Netty基础进行开发,需要补充Netty基础可以参考资料 Netty基本介绍 和 线程模型

一、需求

  1. 编写一个 Netty 群聊系统,实现服务器端和客户端之间的数据简单通讯

  2. 实现多人群聊

  3. 服务器端:可以监测用户上线,离线,并实现消息转发功能

  4. 客户端:可以发送消息给其它所有用户,同时可以接受其它用户发送的消息

二、 服务器端编写

  • 编写步骤:

    • 服务端

      1. 创建bossGroup线程组: 处理网络事件–连接事件

      2. 创建workerGroup线程组: 处理网络事件–读写事件

      3. 创建服务端启动助手

      4. 设置bossGroup线程组和workerGroup线程组

      5. 设置服务端通道实现为NIO

      6. 参数设置

      7. 创建一个通道初始化对象

      8. 向pipeline中添加自定义业务处理handler

      9. 启动服务端并绑定端口,同时将异步改为同步

      10. 关闭通道和关闭连接池

    • 自定义handler

      1. 创建自定义handler,继承SimpleChannelInboundHandler
      2. 创建Channel的list集合 作为 成员变量,管理所有客户端连接
      3. 重写 通道就绪事件channelActive(ChannelHandlerContext ctx) ,当有客户端连接时,将通道放入集合
      4. 重写 通道读取事件channelRead0(ChannelHandlerContext ctx, String msg) ,当服务端读取到某个客户端发送的数据时,向其他客户端进行转发
      5. 重写 异常处理事件exceptionCaught(ChannelHandlerContext ctx, Throwable cause) ,当发生异常时,从集合中移除channel
      6. 重写 通道未就绪事件channelInactive(ChannelHandlerContext ctx),当channel下线时会触发此事件,需要从集合中移除channel
  • 相关代码:

    1. ChatServer

      public class ChatServer {
      
          //端口号
          private int port;
      
          public ChatServer(int port){
              this.port = port;
          }
      
          public void run() throws InterruptedException{
      
              EventLoopGroup bossGroup = null;
              EventLoopGroup workerGroup = null;
              try{
                  //1. 创建bossGroup线程组: 处理网络事件--连接事件
                  bossGroup = new NioEventLoopGroup(1);
                  //2. 创建workerGroup线程组: 处理网络事件--读写事件
                  workerGroup = new NioEventLoopGroup();
                  //3. 创建服务端启动助手
                  ServerBootstrap serverBootstrap = new ServerBootstrap();
                  //4. 设置bossGroup线程组和workerGroup线程组
                  serverBootstrap.group(bossGroup, workerGroup)
                          .channel(NioServerSocketChannel.class)//5. 设置服务端通道实现为NIO
                          .option(ChannelOption.SO_BACKLOG, 128)//6. 参数设置,设置当前等待队列为128
                          .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)//6. 参数设置,设置检测socket连接是否活跃
                          .childHandler(new ChannelInitializer<>() {//7. 创建一个通道初始化对象
                              @Override
                              protected void initChannel(Channel channel) throws Exception {
                                  //8. 向pipeline中添加自定义业务处理handler
                                  //添加编解码器
                                  channel.pipeline().addLast(new StringDecoder());
                                  channel.pipeline().addLast(new StringEncoder());
                                  //添加自定义处理handler
                                  channel.pipeline().addLast(new ChatServerHandler());
                              }
                          });
      
                  //9. 启动服务端并绑定端口
                  ChannelFuture channelFuture = serverBootstrap.bind(port);
                  channelFuture.addListener(new ChannelFutureListener() {
                      @Override
                      public void operationComplete(ChannelFuture channelFuture) throws Exception {
                          if (channelFuture.isSuccess()){
                              System.out.println("============>>>端口绑定成功");
                          }else {
                              System.out.println("============>>>端口绑定失败");
                          }
                      }
                  });
                  System.out.println("服务器启动成功......");
                  //10. 关闭通道和关闭连接池
                  channelFuture.channel().closeFuture().sync();
              }finally {
                  bossGroup.shutdownGracefully();
                  workerGroup.shutdownGracefully();
      
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              new ChatServer(9988).run();
          }
      }
      
    2. ChatServerHandler

      public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
      
          public static List<Channel> channelList = new ArrayList<>();
      
          //通道就绪事件
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              Channel channel = ctx.channel();
              //当有新的客户端连接,将通道放入集合
              channelList.add(channel);
              System.out.println("[Server]:" +
                      channel.remoteAddress().toString().substring(1) + "在线。");
          }
      
      
          //通道读取事件
          @Override
          protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
              //排除当前发送消息的通道,依次发送给其他客户端
              Channel channel = channelHandlerContext.channel();
              for (Channel channel1 : channelList) {
                  if (channel1 != channel){
                      channel1.writeAndFlush("[" + channel.remoteAddress().toString().substring(1) + "]说:" + s);
                  }
              }
      
          }
      
          //通道未就绪事件
          @Override
          public void channelInactive(ChannelHandlerContext ctx) throws Exception {
              Channel channel = ctx.channel();
              //当有客户端断开连接的时候,移除响应的通道
              channelList.remove(channel);
              System.out.println("[Server]:" +
                      channel.remoteAddress().toString().substring(1) + "下线.");
          }
      
          //异常处理事件
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              cause.printStackTrace();
              Channel channel = ctx.channel();
              //移除集合
              channelList.remove(channel);
              System.out.println("[Server]:" +
                      channel.remoteAddress().toString().substring(1) + "异常.");
          }
      
      }
      

二、 客户端编写

  • 编写步骤:

    • 客户端

      1. 创建线程组

      2. 创建客户端启动助手

      3. 设置线程组

      4. 设置客户端通道实现为NIO

      5. 创建一个通道初始化对象

      6. 向pipeline中添加自定义业务处理handler

      7. 启动客户端,等待连接服务端,同时将异步改为同步

      8. 获取控制台输入信息,向服务端发送消息

      9. 关闭通道和关闭连接池

    • 自定义handler

      1. 重写 通道读就绪事件channelRead0(ChannelHandlerContext channelHandlerContext, String s),打印接收到的消息
  • 相关代码:

    1. ChatClient

      public class ChatClient {
      
          private String ip;//服务端IP
          private int port;//服务端端口号
      
          public ChatClient(String ip, int port){
              this.ip = ip;
              this.port = port;
          }
      
          public void run() throws InterruptedException{
      
              EventLoopGroup group = null;
      
              try {
                  //1. 创建线程组
                  group = new NioEventLoopGroup();
                  //2. 创建客户端启动助手
                  Bootstrap bootstrap = new Bootstrap();
                  //3. 设置线程组
                  bootstrap.group(group)
                          .channel(NioSocketChannel.class)//4. 设置客户端通道实现为NIO
                          .handler(new ChannelInitializer<>() {//5. 创建一个通道初始化对象
                              @Override
                              protected void initChannel(Channel channel) throws Exception {
                                  //6. 向pipeline中添加自定义业务处理handler
                                  //添加编解码器
                                  channel.pipeline().addLast(new StringDecoder());
                                  channel.pipeline().addLast(new StringEncoder());
                                  //添加自定义客户端处理类
                                  channel.pipeline().addLast(new ChatClientHandler());
                              }
                          });
      
      
      
                  //7. 启动客户端,等待连接服务端,同时将异步改为同步
                  ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
      
                  //8.获取控制台输入信息,向服务端发送消息
                  Channel channel = channelFuture.channel();
                  System.out.println("-------" + channel.localAddress().toString().substring(1) + "--------");
                  Scanner scanner = new Scanner(System.in);
                  while (scanner.hasNextLine()){
                      String msg = scanner.nextLine();
                      //发送消息
                      channel.writeAndFlush(msg);
                  }
      
                  // 9.关闭通道和关闭连接池
                  channel.closeFuture().sync();
              }finally {
                  group.shutdownGracefully();
              }
      
          }
      
          public static void main(String[] args) throws InterruptedException {
              new ChatClient("127.0.0.1", 9988).run();
          }
      }
      
    2. ChatClientHandler

      public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
          //通道读就绪事件
          @Override
          protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
              System.out.println(s);
          }
      }
      

三、测试

  • 客户端上线

    请添加图片描述

  • 客户端发送消息

    请添加图片描述

  • 某一客户端离线

    请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值