package com.ard.work.component.netty; import com.ard.work.component.netty.command.BaseCmd; import com.ard.work.component.netty.command.register.HeartbeatCmd; import com.ard.work.component.netty.handler.ClientHandler; import com.ard.work.component.netty.handler.MessageProtocolDecoder; import com.ard.work.component.netty.handler.MessageProtocolEncoder; import com.ard.work.domain.cache.TyTcpServerCache; import com.ard.work.domain.entity.ArdServerInfo; import com.ard.work.service.IArdServerInfoService; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AttributeKey; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @Component @Slf4j public class TyTcpClientManager { /** * 以 serverId 作为唯一 key */ public static final Map channels = new ConcurrentHashMap<>(); private final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); @Resource private IArdServerInfoService ardServerService; // ← 从数据库读取边缘服务器配置 @Resource private ClientHandler clientHandler; /** * 初始化 Bootstrap */ private Bootstrap getBootstrap() { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast("heart", new IdleStateHandler(0, 3, 0, TimeUnit.SECONDS)); pipeline.addLast("encoder", new MessageProtocolEncoder()); pipeline.addLast("decoder", new MessageProtocolDecoder()); pipeline.addLast("handler", clientHandler); } }); return bootstrap; } /** * 新增或重连服务器 */ public void connectServer(ArdServerInfo serverInfo) { String serverId = serverInfo.getId(); String host = serverInfo.getHost(); Integer port = serverInfo.getTcpPort(); // 如果已有旧连接,先断开 disconnectServer(serverId); Bootstrap bootstrap = getBootstrap(); ChannelFuture future = bootstrap.connect(host, port).addListener((ChannelFuture f) -> { if (f.isSuccess()) { log.info("✅ 服务器 [{}] 连接成功", serverId); Channel channel = f.channel(); channel.attr(AttributeKey.valueOf("serverId")).set(serverId); TyTcpServerCache.putTcpServerCache(serverId, true); } else { log.warn("❌ 服务器 [{}] 连接失败", serverId); TyTcpServerCache.putTcpServerCache(serverId, false); } }); TcpClientSession session = TcpClientSession.builder() .ClientId("TY_" + serverId) .channelFuture(future) .isReConnect(true) .serverId(serverId) .host(host) .port(port) .build(); channels.put(serverId, session); } /** * 断开服务器连接 */ public void disconnectServer(String serverId) { TcpClientSession session = channels.remove(serverId); if (session != null && session.getChannelFuture() != null) { session.getChannelFuture().channel().close(); log.info("断开服务器 [{}] 连接", serverId); } TyTcpServerCache.removeTcpServerCache(serverId); } /** * 向指定边缘服务器发送消息 */ public void sendMessage(Long serverId, BaseCmd msg) { try { TcpClientSession session = channels.get(serverId); if (session == null) { log.error("未找到六合服务器 [{}] 的连接", serverId); return; } ChannelFuture future = session.getChannelFuture(); if (future == null || !future.channel().isActive()) { log.warn("六合服务器 [{}] 连接未激活,无法发送消息", serverId); return; } InetSocketAddress remote = (InetSocketAddress) future.channel().remoteAddress(); future.channel().writeAndFlush(msg); log.debug("→ 向六合服务器 [{}] ({}:{}) 发送消息,命令码:0x{}", serverId, remote.getHostString(), remote.getPort(), String.format("%02X", msg.getCmdCode() & 0xFF)); } catch (Exception e) { log.error("发送消息到六合服务器 [{}] 异常:{}", serverId, e.getMessage()); } } /** * 启动时从数据库加载所有边缘服务器并建立 TCP 连接 */ @PostConstruct public void initConnectServers() { try { var serverList = ardServerService.selectArdServerInfoList(new ArdServerInfo()); // ← 从数据库查询所有服务器配置 if (serverList == null || serverList.isEmpty()) { log.warn("未在数据库中找到边缘服务器配置,跳过连接初始化"); return; } for (ArdServerInfo serverInfo : serverList) { connectServer(serverInfo); } log.info("共初始化 {} 个边缘服务器连接", serverList.size()); } catch (Exception e) { log.error("创建边缘服务器连接异常:{}", e.getMessage(), e); } } @Scheduled(fixedDelay = 10000) // 每 10 秒检查一次 public void reconnectFailedServers() { TyTcpServerCache.getTcpServerCacheMap().forEach((key, state) -> { if (Boolean.FALSE.equals(state)) { // 连接失败的服务器 ArdServerInfo serverInfo = ardServerService.selectArdServerInfoById(key); if (serverInfo != null) { connectServer(serverInfo); } } }); } /** * 定时向所有已连接的六合服务器发送心跳 */ @Scheduled(fixedRate = 5000) // 每5秒执行一次 public void sendHeartBeat() { long timestamp = System.currentTimeMillis(); channels.values().stream() .filter(session -> { ChannelFuture future = session.getChannelFuture(); // 只保留活跃连接 return future != null && future.channel().isActive(); }) .forEach(session -> { HeartbeatCmd heartbeat = new HeartbeatCmd(timestamp); ChannelFuture future = session.getChannelFuture(); future.channel().writeAndFlush(heartbeat); log.info("→ 向边缘服务器 [{}] ({}:{}) 发送心跳,时间戳={}", session.getServerId(), session.getHost(), session.getPort(), timestamp); }); } }