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.
158 lines
6.1 KiB
158 lines
6.1 KiB
package com.qyft.ms.device.server;
|
|
|
|
import com.qyft.ms.device.config.TcpConfig;
|
|
import io.netty.bootstrap.ServerBootstrap;
|
|
import io.netty.buffer.Unpooled;
|
|
import io.netty.channel.*;
|
|
import io.netty.channel.group.ChannelGroup;
|
|
import io.netty.channel.group.DefaultChannelGroup;
|
|
import io.netty.channel.nio.NioEventLoopGroup;
|
|
import io.netty.channel.socket.SocketChannel;
|
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
|
import io.netty.handler.codec.json.JsonObjectDecoder;
|
|
import io.netty.util.CharsetUtil;
|
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
|
import jakarta.annotation.PostConstruct;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
@Slf4j
|
|
@Component
|
|
@RequiredArgsConstructor
|
|
public class TcpServer {
|
|
|
|
private final TcpConfig tcpConfig;
|
|
|
|
private EventLoopGroup bossGroup;
|
|
private EventLoopGroup workerGroup;
|
|
|
|
private final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
|
|
|
/**
|
|
* 初始化方法,在Spring容器启动后调用
|
|
*/
|
|
@PostConstruct
|
|
public void init() {
|
|
if (tcpConfig.isEnable()) {
|
|
new Thread(this::start).start(); // 在独立线程中启动TCP服务器
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 启动TCP服务器的方法
|
|
*/
|
|
public void start() {
|
|
bossGroup = new NioEventLoopGroup(1); // 创建Boss线程组
|
|
workerGroup = new NioEventLoopGroup(); // 创建Worker线程组
|
|
try {
|
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
|
bootstrap.group(bossGroup, workerGroup)
|
|
.channel(NioServerSocketChannel.class)
|
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
|
@Override
|
|
protected void initChannel(SocketChannel ch) {
|
|
// 添加JSON对象解码器到ChannelPipeline
|
|
ch.pipeline().addLast(new JsonObjectDecoder());
|
|
// 添加TCP消息处理器到ChannelPipeline
|
|
ch.pipeline().addLast(new TcpMessageHandler());
|
|
// 添加客户端到ChannelGroup
|
|
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
|
@Override
|
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
clients.add(ctx.channel()); // 将新连接的客户端添加到ChannelGroup
|
|
super.channelActive(ctx);
|
|
}
|
|
|
|
@Override
|
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
clients.remove(ctx.channel()); // 从ChannelGroup中移除断开连接的客户端
|
|
super.channelInactive(ctx);
|
|
}
|
|
});
|
|
}
|
|
})
|
|
.option(ChannelOption.SO_BACKLOG, 128)
|
|
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
|
|
|
// 绑定并开始接受传入连接
|
|
ChannelFuture future = bootstrap.bind(tcpConfig.getPort()).sync();
|
|
log.info("TCP服务器已启动,监听端口: {}", tcpConfig.getPort());
|
|
|
|
// 等待服务器套接字关闭
|
|
future.channel().closeFuture().sync();
|
|
} catch (InterruptedException e) {
|
|
log.error("TCP服务器启动过程中发生中断: {}", e.getMessage(), e);
|
|
} finally {
|
|
// 优雅地关闭EventLoopGroup
|
|
workerGroup.shutdownGracefully();
|
|
bossGroup.shutdownGracefully();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 服务器端推送消息的方法
|
|
* @param message 要推送的消息内容
|
|
*/
|
|
public void sendMessage(String message) {
|
|
clients.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8)); // 将消息推送给所有客户端
|
|
}
|
|
|
|
/**
|
|
* 停止TCP服务器的方法
|
|
*/
|
|
public void stop() {
|
|
if (workerGroup != null) {
|
|
workerGroup.shutdownGracefully(); // 优雅地关闭Worker线程组
|
|
}
|
|
if (bossGroup != null) {
|
|
bossGroup.shutdownGracefully(); // 优雅地关闭Boss线程组
|
|
}
|
|
}
|
|
|
|
// 内部类,处理TCP消息事件
|
|
private static class TcpMessageHandler extends ChannelInboundHandlerAdapter {
|
|
|
|
/**
|
|
* 处理接收到的消息
|
|
* @param ctx 通道处理上下文
|
|
* @param msg 接收到的消息
|
|
*/
|
|
@Override
|
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
|
// 记录接收到的消息
|
|
log.info("接收到消息: {}", msg.toString());
|
|
|
|
// 将ByteBuf转换为字符串
|
|
String messageStr = ((io.netty.buffer.ByteBuf) msg).toString(CharsetUtil.UTF_8);
|
|
|
|
// 解析JSON对象
|
|
cn.hutool.json.JSONObject jsonObject = cn.hutool.json.JSONUtil.parseObj(messageStr);
|
|
|
|
// 可以在这里添加消息处理逻辑
|
|
// 例如:处理特定字段
|
|
String someField = jsonObject.getStr("someField");
|
|
log.info("解析后的字段值: {}", someField);
|
|
|
|
// 新增:五秒后推送消息
|
|
Runnable task = () -> {
|
|
String responseMessage = "服务器推送消息: " + someField;
|
|
ctx.writeAndFlush(Unpooled.copiedBuffer(responseMessage, CharsetUtil.UTF_8)); // 推送消息给客户端
|
|
log.info("五秒后推送消息: {}", responseMessage);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 处理异常事件
|
|
* @param ctx 通道处理上下文
|
|
* @param cause 异常对象
|
|
*/
|
|
@Override
|
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
|
// 记录异常日志
|
|
log.error("TCP连接发生异常: {}", cause.getMessage(), cause);
|
|
// 关闭当前channel
|
|
ctx.close();
|
|
}
|
|
}
|
|
}
|