You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
6.1 KiB

  1. package com.qyft.ms.device.server;
  2. import com.qyft.ms.device.config.TcpConfig;
  3. import io.netty.bootstrap.ServerBootstrap;
  4. import io.netty.buffer.Unpooled;
  5. import io.netty.channel.*;
  6. import io.netty.channel.group.ChannelGroup;
  7. import io.netty.channel.group.DefaultChannelGroup;
  8. import io.netty.channel.nio.NioEventLoopGroup;
  9. import io.netty.channel.socket.SocketChannel;
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;
  11. import io.netty.handler.codec.json.JsonObjectDecoder;
  12. import io.netty.util.CharsetUtil;
  13. import io.netty.util.concurrent.GlobalEventExecutor;
  14. import jakarta.annotation.PostConstruct;
  15. import lombok.RequiredArgsConstructor;
  16. import lombok.extern.slf4j.Slf4j;
  17. import org.springframework.stereotype.Component;
  18. @Slf4j
  19. @Component
  20. @RequiredArgsConstructor
  21. public class TcpServer {
  22. private final TcpConfig tcpConfig;
  23. private EventLoopGroup bossGroup;
  24. private EventLoopGroup workerGroup;
  25. private final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  26. /**
  27. * 初始化方法在Spring容器启动后调用
  28. */
  29. @PostConstruct
  30. public void init() {
  31. if (tcpConfig.isEnable()) {
  32. new Thread(this::start).start(); // 在独立线程中启动TCP服务器
  33. }
  34. }
  35. /**
  36. * 启动TCP服务器的方法
  37. */
  38. public void start() {
  39. bossGroup = new NioEventLoopGroup(1); // 创建Boss线程组
  40. workerGroup = new NioEventLoopGroup(); // 创建Worker线程组
  41. try {
  42. ServerBootstrap bootstrap = new ServerBootstrap();
  43. bootstrap.group(bossGroup, workerGroup)
  44. .channel(NioServerSocketChannel.class)
  45. .childHandler(new ChannelInitializer<SocketChannel>() {
  46. @Override
  47. protected void initChannel(SocketChannel ch) {
  48. // 添加JSON对象解码器到ChannelPipeline
  49. ch.pipeline().addLast(new JsonObjectDecoder());
  50. // 添加TCP消息处理器到ChannelPipeline
  51. ch.pipeline().addLast(new TcpMessageHandler());
  52. // 添加客户端到ChannelGroup
  53. ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
  54. @Override
  55. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  56. clients.add(ctx.channel()); // 将新连接的客户端添加到ChannelGroup
  57. super.channelActive(ctx);
  58. }
  59. @Override
  60. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  61. clients.remove(ctx.channel()); // 从ChannelGroup中移除断开连接的客户端
  62. super.channelInactive(ctx);
  63. }
  64. });
  65. }
  66. })
  67. .option(ChannelOption.SO_BACKLOG, 128)
  68. .childOption(ChannelOption.SO_KEEPALIVE, true);
  69. // 绑定并开始接受传入连接
  70. ChannelFuture future = bootstrap.bind(tcpConfig.getPort()).sync();
  71. log.info("TCP服务器已启动,监听端口: {}", tcpConfig.getPort());
  72. // 等待服务器套接字关闭
  73. future.channel().closeFuture().sync();
  74. } catch (InterruptedException e) {
  75. log.error("TCP服务器启动过程中发生中断: {}", e.getMessage(), e);
  76. } finally {
  77. // 优雅地关闭EventLoopGroup
  78. workerGroup.shutdownGracefully();
  79. bossGroup.shutdownGracefully();
  80. }
  81. }
  82. /**
  83. * 服务器端推送消息的方法
  84. * @param message 要推送的消息内容
  85. */
  86. public void sendMessage(String message) {
  87. clients.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8)); // 将消息推送给所有客户端
  88. }
  89. /**
  90. * 停止TCP服务器的方法
  91. */
  92. public void stop() {
  93. if (workerGroup != null) {
  94. workerGroup.shutdownGracefully(); // 优雅地关闭Worker线程组
  95. }
  96. if (bossGroup != null) {
  97. bossGroup.shutdownGracefully(); // 优雅地关闭Boss线程组
  98. }
  99. }
  100. // 内部类,处理TCP消息事件
  101. private static class TcpMessageHandler extends ChannelInboundHandlerAdapter {
  102. /**
  103. * 处理接收到的消息
  104. * @param ctx 通道处理上下文
  105. * @param msg 接收到的消息
  106. */
  107. @Override
  108. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  109. // 记录接收到的消息
  110. log.info("接收到消息: {}", msg.toString());
  111. // 将ByteBuf转换为字符串
  112. String messageStr = ((io.netty.buffer.ByteBuf) msg).toString(CharsetUtil.UTF_8);
  113. // 解析JSON对象
  114. cn.hutool.json.JSONObject jsonObject = cn.hutool.json.JSONUtil.parseObj(messageStr);
  115. // 可以在这里添加消息处理逻辑
  116. // 例如:处理特定字段
  117. String someField = jsonObject.getStr("someField");
  118. log.info("解析后的字段值: {}", someField);
  119. // 新增:五秒后推送消息
  120. Runnable task = () -> {
  121. String responseMessage = "服务器推送消息: " + someField;
  122. ctx.writeAndFlush(Unpooled.copiedBuffer(responseMessage, CharsetUtil.UTF_8)); // 推送消息给客户端
  123. log.info("五秒后推送消息: {}", responseMessage);
  124. };
  125. }
  126. /**
  127. * 处理异常事件
  128. * @param ctx 通道处理上下文
  129. * @param cause 异常对象
  130. */
  131. @Override
  132. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
  133. // 记录异常日志
  134. log.error("TCP连接发生异常: {}", cause.getMessage(), cause);
  135. // 关闭当前channel
  136. ctx.close();
  137. }
  138. }
  139. }