11 changed files with 1597 additions and 2 deletions
@ -0,0 +1,359 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.io.OutputStream; |
||||
|
import java.net.Socket; |
||||
|
import java.net.SocketTimeoutException; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* 客户端处理器类 |
||||
|
* 负责处理单个网关客户端的连接和通信 |
||||
|
*/ |
||||
|
public class ClientHandler { |
||||
|
|
||||
|
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class); |
||||
|
|
||||
|
private final Socket socket; |
||||
|
private final String clientId; |
||||
|
private final MessageHandler messageHandler; |
||||
|
private final GatewayServerConfig config; |
||||
|
|
||||
|
private OutputStream outputStream; |
||||
|
private InputStream inputStream; |
||||
|
private volatile boolean connected = false; |
||||
|
private ScheduledExecutorService heartbeatService; |
||||
|
private long lastCommunicationTime; |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
* @param socket 客户端Socket |
||||
|
* @param clientId 客户端ID |
||||
|
* @param messageHandler 消息处理器 |
||||
|
* @param config 配置信息 |
||||
|
*/ |
||||
|
public ClientHandler(Socket socket, String clientId, MessageHandler messageHandler, GatewayServerConfig config) { |
||||
|
this.socket = socket; |
||||
|
this.clientId = clientId; |
||||
|
this.messageHandler = messageHandler; |
||||
|
this.config = config; |
||||
|
this.lastCommunicationTime = System.currentTimeMillis(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理客户端通信 |
||||
|
*/ |
||||
|
public void handle() { |
||||
|
try { |
||||
|
// 初始化输入输出流
|
||||
|
outputStream = socket.getOutputStream(); |
||||
|
inputStream = socket.getInputStream(); |
||||
|
connected = true; |
||||
|
|
||||
|
logger.info("客户端[{}]连接已建立,开始通信", clientId); |
||||
|
|
||||
|
// 启动心跳检测
|
||||
|
startHeartbeat(); |
||||
|
|
||||
|
// 开始接收和处理消息
|
||||
|
processMessages(); |
||||
|
|
||||
|
} catch (IOException e) { |
||||
|
if (connected) { // 只有在连接正常时记录错误
|
||||
|
logger.error("客户端[{}]通信错误", clientId, e); |
||||
|
} |
||||
|
} finally { |
||||
|
close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理接收到的消息 |
||||
|
* @throws IOException 如果发生IO错误 |
||||
|
*/ |
||||
|
private void processMessages() throws IOException { |
||||
|
StringBuilder buffer = new StringBuilder(); |
||||
|
boolean stxReceived = false; |
||||
|
|
||||
|
logger.info("客户端[{}]开始接收消息", clientId); |
||||
|
|
||||
|
while (connected && !socket.isClosed()) { |
||||
|
try { |
||||
|
// 读取单个字符
|
||||
|
int readChar = inputStream.read(); |
||||
|
|
||||
|
// 检查是否到达流末尾
|
||||
|
if (readChar == -1) { |
||||
|
logger.info("客户端[{}]关闭了连接", clientId); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
char c = (char) readChar; |
||||
|
|
||||
|
// 记录收到的每个字符的十六进制值,方便调试
|
||||
|
logger.debug("客户端[{}]收到字符: {}, 十六进制: 0x{}", clientId, c, String.format("%02X", (byte) c)); |
||||
|
|
||||
|
// 等待STX开始字符
|
||||
|
if (!stxReceived) { |
||||
|
if (c == config.getStx()) { |
||||
|
logger.info("客户端[{}]收到STX字符,开始接收消息内容", clientId); |
||||
|
stxReceived = true; |
||||
|
buffer.setLength(0); |
||||
|
buffer.append(c); |
||||
|
} else { |
||||
|
logger.debug("客户端[{}]收到非STX字符: {}, 继续等待", clientId, String.format("%02X", (byte) c)); |
||||
|
} |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// 已收到STX,继续接收直到ETX
|
||||
|
buffer.append(c); |
||||
|
|
||||
|
// 检查是否收到ETX结束字符
|
||||
|
if (c == config.getEtx()) { |
||||
|
logger.info("客户端[{}]收到ETX字符,消息接收完成", clientId); |
||||
|
stxReceived = false; |
||||
|
String message = buffer.toString(); |
||||
|
|
||||
|
// 更新最后通信时间
|
||||
|
lastCommunicationTime = System.currentTimeMillis(); |
||||
|
|
||||
|
// 记录完整消息的十六进制表示
|
||||
|
StringBuilder hexBuilder = new StringBuilder(); |
||||
|
for (char ch : message.toCharArray()) { |
||||
|
hexBuilder.append(String.format("%02X ", (byte) ch)); |
||||
|
} |
||||
|
logger.info("客户端[{}]收到完整消息,HEX: {}", clientId, hexBuilder.toString().trim()); |
||||
|
|
||||
|
// 处理接收到的完整消息
|
||||
|
handleMessage(message); |
||||
|
buffer.setLength(0); // 清空缓冲区,准备接收下一条消息
|
||||
|
} |
||||
|
|
||||
|
// 检查消息长度是否超过合理范围,防止内存溢出
|
||||
|
if (buffer.length() > 1024) { |
||||
|
logger.warn("客户端[{}]发送的消息过长(超过1024字节),可能格式错误,重置缓冲区", clientId); |
||||
|
buffer.setLength(0); |
||||
|
stxReceived = false; |
||||
|
} |
||||
|
|
||||
|
} catch (SocketTimeoutException e) { |
||||
|
// 超时,检查连接是否仍然有效
|
||||
|
if (!isConnectionValid()) { |
||||
|
logger.warn("客户端[{}]通信超时,连接可能已断开", clientId); |
||||
|
break; |
||||
|
} |
||||
|
// 继续等待数据
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
logger.info("客户端[{}]消息接收线程结束", clientId); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理接收到的消息 |
||||
|
* @param message 接收到的消息 |
||||
|
*/ |
||||
|
private void handleMessage(String message) { |
||||
|
logger.debug("客户端[{}]收到消息: {}", clientId, message); |
||||
|
|
||||
|
try { |
||||
|
// 解析消息
|
||||
|
MessageHandler.Message parsedMessage = messageHandler.parseMessage(message); |
||||
|
|
||||
|
// 检查序列号是否有效
|
||||
|
boolean sequenceValid = messageHandler.isValidSequence(clientId, parsedMessage.getSequence()); |
||||
|
|
||||
|
// 检查是否是通信异常(连续6次重复序列号)
|
||||
|
if (!sequenceValid) { |
||||
|
logger.error("客户端[{}]通信异常,停止响应", clientId); |
||||
|
// 关闭连接
|
||||
|
close(); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 检查是否是重复序列号
|
||||
|
String lastSeq = messageHandler.getLastReceivedSequence(clientId); |
||||
|
boolean isDuplicate = lastSeq != null && lastSeq.equals(parsedMessage.getSequence()); |
||||
|
|
||||
|
if (isDuplicate) { |
||||
|
// 重复序列号处理:不处理数据,但返回肯定响应
|
||||
|
logger.info("客户端[{}]收到重复消息,序列号: {}", clientId, parsedMessage.getSequence()); |
||||
|
String response = messageHandler.formatResponse(parsedMessage.getSequence(), config.getResponseOk(), null); |
||||
|
sendMessage(response); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 正常消息处理:根据消息模式进行处理
|
||||
|
String response; |
||||
|
if (config.getModeSystemStart().equals(parsedMessage.getMode())) { |
||||
|
// 处理系统开始指示
|
||||
|
response = messageHandler.handleSystemStartMessage(parsedMessage, clientId); |
||||
|
} else if (config.getModeNormal().equals(parsedMessage.getMode())) { |
||||
|
// 处理正常数据
|
||||
|
response = messageHandler.handleNormalDataMessage(parsedMessage, clientId); |
||||
|
} else if (config.getModeUnsent().equals(parsedMessage.getMode())) { |
||||
|
// 处理未发送数据
|
||||
|
response = messageHandler.handleUnsentDataMessage(parsedMessage, clientId); |
||||
|
} else { |
||||
|
// 未知消息模式 - 不良数据处理:返回否定响应
|
||||
|
logger.warn("客户端[{}]发送未知消息模式: {}", clientId, parsedMessage.getMode()); |
||||
|
response = messageHandler.createErrorMessage(parsedMessage.getSequence()); |
||||
|
} |
||||
|
|
||||
|
// 发送响应
|
||||
|
if (response != null) { |
||||
|
sendMessage(response); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
logger.error("处理客户端[{}]消息时发生错误", clientId, e); |
||||
|
|
||||
|
try { |
||||
|
// 不良数据处理:发送错误响应
|
||||
|
String errorResponse = messageHandler.createErrorMessage("0000"); // 使用默认序列号
|
||||
|
sendMessage(errorResponse); |
||||
|
} catch (IOException ex) { |
||||
|
logger.error("发送错误响应失败", ex); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送消息到客户端 |
||||
|
* @param message 要发送的消息 |
||||
|
* @throws IOException 如果发送失败 |
||||
|
*/ |
||||
|
public synchronized void sendMessage(String message) throws IOException { |
||||
|
if (!connected || outputStream == null) { |
||||
|
throw new IOException("客户端连接已关闭"); |
||||
|
} |
||||
|
|
||||
|
outputStream.write(message.getBytes()); |
||||
|
outputStream.flush(); |
||||
|
|
||||
|
// 更新最后通信时间
|
||||
|
lastCommunicationTime = System.currentTimeMillis(); |
||||
|
|
||||
|
logger.debug("客户端[{}]发送消息: {}", clientId, message); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 启动心跳检测 |
||||
|
*/ |
||||
|
private void startHeartbeat() { |
||||
|
if (!config.isHeartbeatEnabled()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
heartbeatService = Executors.newSingleThreadScheduledExecutor( |
||||
|
r -> { |
||||
|
Thread t = new Thread(r, "heartbeat-" + clientId); |
||||
|
t.setDaemon(true); |
||||
|
return t; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
heartbeatService.scheduleAtFixedRate(this::checkHeartbeat, |
||||
|
config.getHeartbeatInterval(), |
||||
|
config.getHeartbeatInterval(), |
||||
|
TimeUnit.MILLISECONDS); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查心跳 |
||||
|
*/ |
||||
|
private void checkHeartbeat() { |
||||
|
long currentTime = System.currentTimeMillis(); |
||||
|
long timeSinceLastCommunication = currentTime - lastCommunicationTime; |
||||
|
|
||||
|
if (timeSinceLastCommunication > config.getIdleTimeout()) { |
||||
|
logger.warn("客户端[{}]空闲时间过长({}ms),关闭连接", |
||||
|
clientId, timeSinceLastCommunication); |
||||
|
close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查连接是否有效 |
||||
|
* @return 连接是否有效 |
||||
|
*/ |
||||
|
private boolean isConnectionValid() { |
||||
|
try { |
||||
|
socket.sendUrgentData(0xFF); |
||||
|
return true; |
||||
|
} catch (IOException e) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 关闭客户端连接 |
||||
|
*/ |
||||
|
public void close() { |
||||
|
connected = false; |
||||
|
|
||||
|
// 停止心跳检测
|
||||
|
if (heartbeatService != null) { |
||||
|
heartbeatService.shutdown(); |
||||
|
} |
||||
|
|
||||
|
// 关闭流和socket
|
||||
|
try { |
||||
|
if (outputStream != null) { |
||||
|
outputStream.close(); |
||||
|
} |
||||
|
if (inputStream != null) { |
||||
|
inputStream.close(); |
||||
|
} |
||||
|
if (socket != null && !socket.isClosed()) { |
||||
|
socket.close(); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
logger.error("关闭客户端[{}]连接时发生错误", clientId, e); |
||||
|
} |
||||
|
|
||||
|
logger.info("客户端[{}]连接已关闭", clientId); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取客户端ID |
||||
|
* @return 客户端ID |
||||
|
*/ |
||||
|
public String getClientId() { |
||||
|
return clientId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取客户端地址 |
||||
|
* @return 客户端地址字符串 |
||||
|
*/ |
||||
|
public String getClientAddress() { |
||||
|
if (socket != null && !socket.isClosed()) { |
||||
|
return socket.getRemoteSocketAddress().toString(); |
||||
|
} |
||||
|
return "未知地址"; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查是否已连接 |
||||
|
* @return 是否已连接 |
||||
|
*/ |
||||
|
public boolean isConnected() { |
||||
|
return connected && socket != null && !socket.isClosed() && socket.isConnected(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取最后通信时间 |
||||
|
* @return 最后通信时间戳 |
||||
|
*/ |
||||
|
public long getLastCommunicationTime() { |
||||
|
return lastCommunicationTime; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,256 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* 网关服务器配置类 |
||||
|
* 包含TCP服务器相关的配置参数 |
||||
|
*/ |
||||
|
@ConfigurationProperties(prefix = "gateway.server") |
||||
|
public class GatewayServerConfig { |
||||
|
|
||||
|
private boolean autoStart = true; |
||||
|
|
||||
|
// 服务器IP地址
|
||||
|
private String ip = "127.0.0.1"; |
||||
|
|
||||
|
// 监听端口
|
||||
|
private int port = 9000; |
||||
|
|
||||
|
// 最大连接数
|
||||
|
private int maxConnections = 100; |
||||
|
|
||||
|
// 连接超时时间(毫秒)
|
||||
|
private int connectionTimeout = 5000; |
||||
|
|
||||
|
// 读取超时时间(毫秒)
|
||||
|
private int readTimeout = 30000; |
||||
|
|
||||
|
// 写入超时时间(毫秒)
|
||||
|
private int writeTimeout = 10000; |
||||
|
|
||||
|
// 空闲超时时间(毫秒)
|
||||
|
private int idleTimeout = 60000; |
||||
|
|
||||
|
// 线程池配置
|
||||
|
private int corePoolSize = 10; |
||||
|
private int maxPoolSize = 50; |
||||
|
private int threadKeepaliveSeconds = 60; |
||||
|
private int threadQueueCapacity = 1000; |
||||
|
|
||||
|
// 数据格式配置
|
||||
|
private char stx = 0x02; // STX字符
|
||||
|
private char etx = 0x03; // ETX字符
|
||||
|
private int sequenceLength = 4; // 序列号长度
|
||||
|
private int modeLength = 2; // 模式标识长度
|
||||
|
private int stxEtxLength = 1; |
||||
|
|
||||
|
// 模式标识常量
|
||||
|
private String modeNormal = "00"; // 正常数据模式
|
||||
|
private String modeUnsent = "01"; // 未发送数据模式
|
||||
|
private String modeSystemStart = "02"; // 系统开始指示模式
|
||||
|
|
||||
|
// 响应标识常量
|
||||
|
private String responseOk = "10"; // 肯定响应
|
||||
|
private String responseNg = "11"; // 否定响应
|
||||
|
|
||||
|
// 获取器和设置器
|
||||
|
public int getPort() { |
||||
|
return port; |
||||
|
} |
||||
|
|
||||
|
public void setPort(int port) { |
||||
|
this.port = port; |
||||
|
} |
||||
|
|
||||
|
public String getIp() { |
||||
|
return ip; |
||||
|
} |
||||
|
|
||||
|
public void setIp(String ip) { |
||||
|
this.ip = ip; |
||||
|
} |
||||
|
|
||||
|
public int getMaxConnections() { |
||||
|
return maxConnections; |
||||
|
} |
||||
|
|
||||
|
public void setMaxConnections(int maxConnections) { |
||||
|
this.maxConnections = maxConnections; |
||||
|
} |
||||
|
|
||||
|
public int getConnectionTimeout() { |
||||
|
return connectionTimeout; |
||||
|
} |
||||
|
|
||||
|
public void setConnectionTimeout(int connectionTimeout) { |
||||
|
this.connectionTimeout = connectionTimeout; |
||||
|
} |
||||
|
|
||||
|
public int getReadTimeout() { |
||||
|
return readTimeout; |
||||
|
} |
||||
|
|
||||
|
public void setReadTimeout(int readTimeout) { |
||||
|
this.readTimeout = readTimeout; |
||||
|
} |
||||
|
|
||||
|
public int getWriteTimeout() { |
||||
|
return writeTimeout; |
||||
|
} |
||||
|
|
||||
|
public void setWriteTimeout(int writeTimeout) { |
||||
|
this.writeTimeout = writeTimeout; |
||||
|
} |
||||
|
|
||||
|
public int getIdleTimeout() { |
||||
|
return idleTimeout; |
||||
|
} |
||||
|
|
||||
|
public void setIdleTimeout(int idleTimeout) { |
||||
|
this.idleTimeout = idleTimeout; |
||||
|
} |
||||
|
|
||||
|
public int getCorePoolSize() { |
||||
|
return corePoolSize; |
||||
|
} |
||||
|
|
||||
|
public void setCorePoolSize(int corePoolSize) { |
||||
|
this.corePoolSize = corePoolSize; |
||||
|
} |
||||
|
|
||||
|
public int getMaxPoolSize() { |
||||
|
return maxPoolSize; |
||||
|
} |
||||
|
|
||||
|
public void setMaxPoolSize(int maxPoolSize) { |
||||
|
this.maxPoolSize = maxPoolSize; |
||||
|
} |
||||
|
|
||||
|
public int getThreadKeepaliveSeconds() { |
||||
|
return threadKeepaliveSeconds; |
||||
|
} |
||||
|
|
||||
|
public void setThreadKeepaliveSeconds(int threadKeepaliveSeconds) { |
||||
|
this.threadKeepaliveSeconds = threadKeepaliveSeconds; |
||||
|
} |
||||
|
|
||||
|
public int getThreadQueueCapacity() { |
||||
|
return threadQueueCapacity; |
||||
|
} |
||||
|
|
||||
|
public void setThreadQueueCapacity(int threadQueueCapacity) { |
||||
|
this.threadQueueCapacity = threadQueueCapacity; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
public char getStx() { |
||||
|
return stx; |
||||
|
} |
||||
|
|
||||
|
public void setStx(char stx) { |
||||
|
this.stx = stx; |
||||
|
} |
||||
|
|
||||
|
public char getEtx() { |
||||
|
return etx; |
||||
|
} |
||||
|
|
||||
|
public void setEtx(char etx) { |
||||
|
this.etx = etx; |
||||
|
} |
||||
|
|
||||
|
public int getSequenceLength() { |
||||
|
return sequenceLength; |
||||
|
} |
||||
|
|
||||
|
public void setSequenceLength(int sequenceLength) { |
||||
|
this.sequenceLength = sequenceLength; |
||||
|
} |
||||
|
|
||||
|
public int getModeLength() { |
||||
|
return modeLength; |
||||
|
} |
||||
|
|
||||
|
public void setModeLength(int modeLength) { |
||||
|
this.modeLength = modeLength; |
||||
|
} |
||||
|
|
||||
|
public String getModeNormal() { |
||||
|
return modeNormal; |
||||
|
} |
||||
|
|
||||
|
public void setModeNormal(String modeNormal) { |
||||
|
this.modeNormal = modeNormal; |
||||
|
} |
||||
|
|
||||
|
public String getModeUnsent() { |
||||
|
return modeUnsent; |
||||
|
} |
||||
|
|
||||
|
public void setModeUnsent(String modeUnsent) { |
||||
|
this.modeUnsent = modeUnsent; |
||||
|
} |
||||
|
|
||||
|
public String getModeSystemStart() { |
||||
|
return modeSystemStart; |
||||
|
} |
||||
|
|
||||
|
public void setModeSystemStart(String modeSystemStart) { |
||||
|
this.modeSystemStart = modeSystemStart; |
||||
|
} |
||||
|
|
||||
|
public String getResponseOk() { |
||||
|
return responseOk; |
||||
|
} |
||||
|
|
||||
|
public void setResponseOk(String responseOk) { |
||||
|
this.responseOk = responseOk; |
||||
|
} |
||||
|
|
||||
|
public String getResponseNg() { |
||||
|
return responseNg; |
||||
|
} |
||||
|
|
||||
|
public void setResponseNg(String responseNg) { |
||||
|
this.responseNg = responseNg; |
||||
|
} |
||||
|
|
||||
|
// 心跳检测配置
|
||||
|
private boolean heartbeatEnabled = true; |
||||
|
private int heartbeatInterval = 30000; |
||||
|
|
||||
|
public boolean isHeartbeatEnabled() { |
||||
|
return heartbeatEnabled; |
||||
|
} |
||||
|
|
||||
|
public void setHeartbeatEnabled(boolean heartbeatEnabled) { |
||||
|
this.heartbeatEnabled = heartbeatEnabled; |
||||
|
} |
||||
|
|
||||
|
public int getHeartbeatInterval() { |
||||
|
return heartbeatInterval; |
||||
|
} |
||||
|
|
||||
|
public void setHeartbeatInterval(int heartbeatInterval) { |
||||
|
this.heartbeatInterval = heartbeatInterval; |
||||
|
} |
||||
|
|
||||
|
public int getStxEtxLength() { |
||||
|
return stxEtxLength; |
||||
|
} |
||||
|
|
||||
|
public void setStxEtxLength(int stxEtxLength) { |
||||
|
this.stxEtxLength = stxEtxLength; |
||||
|
} |
||||
|
|
||||
|
public boolean isAutoStart() { |
||||
|
return autoStart; |
||||
|
} |
||||
|
|
||||
|
public void setAutoStart(boolean autoStart) { |
||||
|
this.autoStart = autoStart; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
||||
|
|
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
/** |
||||
|
* 网关服务器配置类 |
||||
|
* 负责配置Spring Bean和依赖关系 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
@EnableConfigurationProperties(GatewayServerConfig.class) |
||||
|
public class GatewayServerConfiguration { |
||||
|
|
||||
|
private final GatewayServerConfig serverConfig; |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
* @param serverConfig 服务器配置 |
||||
|
*/ |
||||
|
@Autowired |
||||
|
public GatewayServerConfiguration(GatewayServerConfig serverConfig) { |
||||
|
this.serverConfig = serverConfig; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建序列号管理器Bean |
||||
|
* @return 序列号管理器 |
||||
|
*/ |
||||
|
@Bean |
||||
|
public SequenceNumberManager sequenceNumberManager() { |
||||
|
return new SequenceNumberManager(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建消息处理器Bean |
||||
|
* @return 消息处理器 |
||||
|
*/ |
||||
|
@Bean |
||||
|
public MessageHandler messageHandler() { |
||||
|
return new MessageHandler(serverConfig); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建TCP服务器线程池 |
||||
|
* @return 线程池 |
||||
|
*/ |
||||
|
@Bean(destroyMethod = "shutdown") |
||||
|
public ExecutorService tcpServerExecutor() { |
||||
|
return Executors.newFixedThreadPool(serverConfig.getMaxPoolSize()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建TCP服务器Bean |
||||
|
* @param messageHandler 消息处理器 |
||||
|
* @param executorService 线程池 |
||||
|
* @return TCP服务器 |
||||
|
*/ |
||||
|
@Bean(destroyMethod = "stop") |
||||
|
public TcpServer tcpServer(MessageHandler messageHandler, ExecutorService executorService) { |
||||
|
return new TcpServer(serverConfig, messageHandler, executorService); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建客户端处理线程池 |
||||
|
* @return 线程池 |
||||
|
*/ |
||||
|
@Bean(destroyMethod = "shutdown") |
||||
|
public ThreadPoolTaskExecutor clientHandlerTaskExecutor() { |
||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
||||
|
executor.setCorePoolSize(serverConfig.getCorePoolSize()); |
||||
|
executor.setMaxPoolSize(serverConfig.getMaxPoolSize()); |
||||
|
executor.setKeepAliveSeconds(serverConfig.getThreadKeepaliveSeconds()); |
||||
|
executor.setThreadNamePrefix("gateway-client-"); |
||||
|
executor.initialize(); |
||||
|
return executor; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,360 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.locks.ReentrantLock; |
||||
|
|
||||
|
/** |
||||
|
* 消息处理器类 |
||||
|
* 负责处理网关消息的解析、验证和响应生成 |
||||
|
*/ |
||||
|
public class MessageHandler { |
||||
|
|
||||
|
private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class); |
||||
|
private final GatewayServerConfig config; |
||||
|
private final SequenceNumberManager sequenceManager; |
||||
|
private final Map<String, String> lastReceivedSequence = new HashMap<>(); |
||||
|
private final Map<String, Integer> duplicateSequenceCount = new HashMap<>(); |
||||
|
private final ReentrantLock sequenceLock = new ReentrantLock(); |
||||
|
|
||||
|
/** |
||||
|
* 消息内部类 |
||||
|
*/ |
||||
|
public static class Message { |
||||
|
private String sequence; |
||||
|
private String mode; |
||||
|
private String data; |
||||
|
|
||||
|
public String getSequence() { |
||||
|
return sequence; |
||||
|
} |
||||
|
|
||||
|
public void setSequence(String sequence) { |
||||
|
this.sequence = sequence; |
||||
|
} |
||||
|
|
||||
|
public String getMode() { |
||||
|
return mode; |
||||
|
} |
||||
|
|
||||
|
public void setMode(String mode) { |
||||
|
this.mode = mode; |
||||
|
} |
||||
|
|
||||
|
public String getData() { |
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
public void setData(String data) { |
||||
|
this.data = data; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Message{" + |
||||
|
"sequence='" + sequence + '\'' + |
||||
|
", mode='" + mode + '\'' + |
||||
|
", data='" + data + '\'' + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
* @param config 配置信息 |
||||
|
*/ |
||||
|
public MessageHandler(GatewayServerConfig config) { |
||||
|
this.config = config; |
||||
|
this.sequenceManager = new SequenceNumberManager(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析接收到的消息 |
||||
|
* @param rawMessage 原始消息字符串 |
||||
|
* @return 解析后的消息对象 |
||||
|
* @throws IllegalArgumentException 如果消息格式无效 |
||||
|
*/ |
||||
|
public Message parseMessage(String rawMessage) { |
||||
|
logger.info("parseMessage收到消息,长度: {}, 原始内容: {}", |
||||
|
rawMessage.length(), rawMessage.replaceAll("[\\u0000-\\u001F]", "[控制字符]")); |
||||
|
|
||||
|
// 验证消息基本格式
|
||||
|
// STX + 序列号(4位) + 模式(2位) + ETX = 最少8个字符
|
||||
|
int minimumLength = 1 + config.getSequenceLength() + config.getModeLength() + 1; |
||||
|
if (rawMessage == null || rawMessage.length() < minimumLength) { |
||||
|
logger.error("消息长度不足,当前长度: {}, 最小长度要求: {}", |
||||
|
rawMessage != null ? rawMessage.length() : 0, minimumLength); |
||||
|
throw new IllegalArgumentException("消息长度不足,格式无效"); |
||||
|
} |
||||
|
|
||||
|
// 验证STX和ETX字符
|
||||
|
if (rawMessage.charAt(0) != config.getStx() || |
||||
|
rawMessage.charAt(rawMessage.length() - 1) != config.getEtx()) { |
||||
|
throw new IllegalArgumentException("消息必须以STX开始并以ETX结束"); |
||||
|
} |
||||
|
|
||||
|
// 提取消息内容(去除STX和ETX)
|
||||
|
String content = rawMessage.substring(1, rawMessage.length() - 1); |
||||
|
|
||||
|
// 验证内容长度
|
||||
|
if (content.length() < config.getSequenceLength() + config.getModeLength()) { |
||||
|
throw new IllegalArgumentException("消息内容长度不足"); |
||||
|
} |
||||
|
|
||||
|
// 提取序列号
|
||||
|
String sequence = content.substring(0, config.getSequenceLength()); |
||||
|
if (!sequence.matches("\\d{" + config.getSequenceLength() + "}")) { |
||||
|
throw new IllegalArgumentException("序列号必须为" + config.getSequenceLength() + "位数字"); |
||||
|
} |
||||
|
|
||||
|
// 提取模式标识
|
||||
|
String mode = content.substring(config.getSequenceLength(), |
||||
|
config.getSequenceLength() + config.getModeLength()); |
||||
|
if (!isValidMode(mode)) { |
||||
|
throw new IllegalArgumentException("无效的模式标识: " + mode); |
||||
|
} |
||||
|
|
||||
|
// 提取数据部分(如果有)
|
||||
|
String data = null; |
||||
|
if (content.length() > config.getSequenceLength() + config.getModeLength()) { |
||||
|
data = content.substring(config.getSequenceLength() + config.getModeLength()); |
||||
|
} |
||||
|
|
||||
|
// 创建并返回消息对象
|
||||
|
Message message = new Message(); |
||||
|
message.setSequence(sequence); |
||||
|
message.setMode(mode); |
||||
|
message.setData(data); |
||||
|
|
||||
|
return message; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证模式标识是否有效 |
||||
|
* @param mode 模式标识 |
||||
|
* @return 是否有效 |
||||
|
*/ |
||||
|
private boolean isValidMode(String mode) { |
||||
|
return config.getModeNormal().equals(mode) || |
||||
|
config.getModeUnsent().equals(mode) || |
||||
|
config.getModeSystemStart().equals(mode); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理系统开始指示消息 |
||||
|
* @param message 消息对象 |
||||
|
* @param clientId 客户端ID |
||||
|
* @return 响应消息 |
||||
|
*/ |
||||
|
public String handleSystemStartMessage(Message message, String clientId) { |
||||
|
logger.info("客户端[{}]发送系统开始指示,序列号: {}", clientId, message.getSequence()); |
||||
|
|
||||
|
// 验证并更新序列号
|
||||
|
if (!isValidSequence(clientId, message.getSequence())) { |
||||
|
logger.warn("客户端[{}]系统开始消息序列号无效: {}", clientId, message.getSequence()); |
||||
|
return createErrorMessage(message.getSequence()); |
||||
|
} |
||||
|
|
||||
|
// 创建成功响应
|
||||
|
return formatResponse(message.getSequence(), config.getResponseOk(), null); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理正常数据消息 |
||||
|
* @param message 消息对象 |
||||
|
* @param clientId 客户端ID |
||||
|
* @return 响应消息 |
||||
|
*/ |
||||
|
public String handleNormalDataMessage(Message message, String clientId) { |
||||
|
logger.info("客户端[{}]发送正常数据,序列号: {}, 数据长度: {}", |
||||
|
clientId, message.getSequence(), |
||||
|
message.getData() != null ? message.getData().length() : 0); |
||||
|
|
||||
|
// 验证序列号
|
||||
|
if (!isValidSequence(clientId, message.getSequence())) { |
||||
|
logger.warn("客户端[{}]正常数据消息序列号无效: {}", clientId, message.getSequence()); |
||||
|
return createErrorMessage(message.getSequence()); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 这里可以添加实际的数据处理逻辑
|
||||
|
// 例如:保存到数据库、转发到其他服务等
|
||||
|
|
||||
|
// 假设数据处理成功
|
||||
|
return formatResponse(message.getSequence(), config.getResponseOk(), null); |
||||
|
} catch (Exception e) { |
||||
|
logger.error("处理客户端[{}]正常数据时发生错误", clientId, e); |
||||
|
return createErrorMessage(message.getSequence()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理未发送数据消息 |
||||
|
* @param message 消息对象 |
||||
|
* @param clientId 客户端ID |
||||
|
* @return 响应消息 |
||||
|
*/ |
||||
|
public String handleUnsentDataMessage(Message message, String clientId) { |
||||
|
logger.info("客户端[{}]发送未发送数据,序列号: {}, 数据长度: {}", |
||||
|
clientId, message.getSequence(), |
||||
|
message.getData() != null ? message.getData().length() : 0); |
||||
|
|
||||
|
// 验证序列号
|
||||
|
if (!isValidSequence(clientId, message.getSequence())) { |
||||
|
logger.warn("客户端[{}]未发送数据消息序列号无效: {}", clientId, message.getSequence()); |
||||
|
return createErrorMessage(message.getSequence()); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 处理未发送数据(通常需要特殊处理以避免重复处理)
|
||||
|
// 例如:检查数据是否已经存在,如果不存在则处理,否则直接返回成功
|
||||
|
|
||||
|
return formatResponse(message.getSequence(), config.getResponseOk(), null); |
||||
|
} catch (Exception e) { |
||||
|
logger.error("处理客户端[{}]未发送数据时发生错误", clientId, e); |
||||
|
return createErrorMessage(message.getSequence()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建错误响应消息 |
||||
|
* @param sequence 原始请求的序列号 |
||||
|
* @return 错误响应消息 |
||||
|
*/ |
||||
|
public String createErrorMessage(String sequence) { |
||||
|
return formatResponse(sequence, config.getResponseNg(), null); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 格式化响应消息 |
||||
|
* @param sequence 序列号 |
||||
|
* @param responseCode 响应代码 |
||||
|
* @param data 响应数据(可选) |
||||
|
* @return 格式化后的响应消息 |
||||
|
*/ |
||||
|
public String formatResponse(String sequence, String responseCode, String data) { |
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
|
||||
|
// 添加STX
|
||||
|
sb.append(config.getStx()); |
||||
|
|
||||
|
// 添加序列号
|
||||
|
sb.append(sequence); |
||||
|
|
||||
|
// 添加响应代码
|
||||
|
sb.append(responseCode); |
||||
|
|
||||
|
// 添加数据(如果有)
|
||||
|
if (data != null && !data.isEmpty()) { |
||||
|
sb.append(data); |
||||
|
} |
||||
|
|
||||
|
// 添加ETX
|
||||
|
sb.append(config.getEtx()); |
||||
|
|
||||
|
return sb.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证序列号是否有效 |
||||
|
* @param clientId 客户端ID |
||||
|
* @param sequence 序列号 |
||||
|
* @return 是否有效 |
||||
|
*/ |
||||
|
public boolean isValidSequence(String clientId, String sequence) { |
||||
|
sequenceLock.lock(); |
||||
|
try { |
||||
|
// 获取该客户端上次收到的序列号
|
||||
|
String lastSeq = lastReceivedSequence.get(clientId); |
||||
|
|
||||
|
// 如果是新客户端,保存序列号
|
||||
|
if (lastSeq == null) { |
||||
|
lastReceivedSequence.put(clientId, sequence); |
||||
|
// 重置重复计数
|
||||
|
duplicateSequenceCount.put(clientId, 0); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 检查是否是重复序列号
|
||||
|
if (lastSeq.equals(sequence)) { |
||||
|
// 增加重复计数
|
||||
|
int count = duplicateSequenceCount.getOrDefault(clientId, 0) + 1; |
||||
|
duplicateSequenceCount.put(clientId, count); |
||||
|
|
||||
|
logger.warn("客户端[{}]发送重复序列号: {}, 重复次数: {}", clientId, sequence, count); |
||||
|
|
||||
|
// 如果连续收到6次相同序列号的数据,判定为通信异常
|
||||
|
if (count >= 6) { |
||||
|
logger.error("客户端[{}]连续6次发送相同序列号: {}, 判定为通信异常", clientId, sequence); |
||||
|
// 返回false表示通信异常,应该停止响应
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 返回true表示是重复数据,但不是通信异常
|
||||
|
// 调用方会处理这种情况:不处理数据但返回肯定响应
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 检查序列号是否有效(递增或循环)
|
||||
|
boolean isValid = sequenceManager.isSequenceValid(lastSeq, sequence); |
||||
|
|
||||
|
if (isValid) { |
||||
|
// 更新序列号
|
||||
|
lastReceivedSequence.put(clientId, sequence); |
||||
|
// 重置重复计数
|
||||
|
duplicateSequenceCount.put(clientId, 0); |
||||
|
} |
||||
|
|
||||
|
return isValid; |
||||
|
} finally { |
||||
|
sequenceLock.unlock(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成下一个序列号 |
||||
|
* @return 格式化的序列号字符串 |
||||
|
*/ |
||||
|
public String generateNextSequence() { |
||||
|
return sequenceManager.getNextSequence(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重置指定客户端的序列号状态 |
||||
|
* @param clientId 客户端ID |
||||
|
*/ |
||||
|
public String getLastReceivedSequence(String clientId) { |
||||
|
sequenceLock.lock(); |
||||
|
try { |
||||
|
return lastReceivedSequence.get(clientId); |
||||
|
} finally { |
||||
|
sequenceLock.unlock(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void resetClientSequence(String clientId) { |
||||
|
sequenceLock.lock(); |
||||
|
try { |
||||
|
lastReceivedSequence.remove(clientId); |
||||
|
duplicateSequenceCount.remove(clientId); |
||||
|
} finally { |
||||
|
sequenceLock.unlock(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前活跃的客户端数量 |
||||
|
* @return 客户端数量 |
||||
|
*/ |
||||
|
public int getActiveClientCount() { |
||||
|
sequenceLock.lock(); |
||||
|
try { |
||||
|
return lastReceivedSequence.size(); |
||||
|
} finally { |
||||
|
sequenceLock.unlock(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
/** |
||||
|
* 序列号管理器类 |
||||
|
* 负责生成、验证和管理序列号 |
||||
|
*/ |
||||
|
public class SequenceNumberManager { |
||||
|
|
||||
|
private static final int MIN_SEQUENCE = 0; |
||||
|
private static final int MAX_SEQUENCE = 9999; |
||||
|
private static final int DEFAULT_SEQUENCE_LENGTH = 4; |
||||
|
|
||||
|
private int currentSequence = MIN_SEQUENCE; |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
*/ |
||||
|
public SequenceNumberManager() { |
||||
|
// 默认初始化为0
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取下一个序列号 |
||||
|
* @return 格式化的序列号字符串 |
||||
|
*/ |
||||
|
public synchronized String getNextSequence() { |
||||
|
// 增加序列号
|
||||
|
currentSequence = (currentSequence + 1) % (MAX_SEQUENCE + 1); |
||||
|
|
||||
|
// 格式化序列号为4位,不足前面补0
|
||||
|
return String.format("%04d", currentSequence); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证序列号是否有效 |
||||
|
* @param lastSequence 上一个序列号 |
||||
|
* @param newSequence 新序列号 |
||||
|
* @return 是否有效 |
||||
|
*/ |
||||
|
public boolean isSequenceValid(String lastSequence, String newSequence) { |
||||
|
try { |
||||
|
// 将序列号转换为整数
|
||||
|
int last = Integer.parseInt(lastSequence); |
||||
|
int current = Integer.parseInt(newSequence); |
||||
|
|
||||
|
// 正常情况:序列号递增1
|
||||
|
if (current == last + 1) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 循环情况:序列号从9999变为0000
|
||||
|
if (last == MAX_SEQUENCE && current == MIN_SEQUENCE) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 其他情况(包括跳号、重复等)都视为无效
|
||||
|
return false; |
||||
|
} catch (NumberFormatException e) { |
||||
|
// 序列号不是有效的数字
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 重置序列号 |
||||
|
*/ |
||||
|
public synchronized void resetSequence() { |
||||
|
currentSequence = MIN_SEQUENCE; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 格式化序列号 |
||||
|
* @param sequence 序列号整数 |
||||
|
* @return 格式化的序列号字符串 |
||||
|
*/ |
||||
|
public String formatSequence(int sequence) { |
||||
|
return String.format("%04d", sequence); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析序列号 |
||||
|
* @param sequenceStr 序列号字符串 |
||||
|
* @return 序列号整数 |
||||
|
* @throws NumberFormatException 如果序列号无效 |
||||
|
*/ |
||||
|
public int parseSequence(String sequenceStr) { |
||||
|
return Integer.parseInt(sequenceStr); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前序列号 |
||||
|
* @return 当前序列号 |
||||
|
*/ |
||||
|
public synchronized int getCurrentSequence() { |
||||
|
return currentSequence; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证序列号是否有效 |
||||
|
* @param sequence 序列号字符串 |
||||
|
* @return 是否有效 |
||||
|
*/ |
||||
|
public boolean isValidSequence(String sequence) { |
||||
|
if (sequence == null || sequence.length() != DEFAULT_SEQUENCE_LENGTH) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
int seq = Integer.parseInt(sequence); |
||||
|
return seq >= MIN_SEQUENCE && seq <= MAX_SEQUENCE; |
||||
|
} catch (NumberFormatException e) { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,262 @@ |
|||||
|
package com.techsor.datacenter.receiver.clients.f10; |
||||
|
|
||||
|
import com.techsor.datacenter.receiver.clients.f10.GatewayServerConfig; |
||||
|
import jakarta.annotation.PreDestroy; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import java.io.IOException; |
||||
|
import java.net.ServerSocket; |
||||
|
import java.net.Socket; |
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.LinkedBlockingQueue; |
||||
|
import java.util.concurrent.ThreadPoolExecutor; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
/** |
||||
|
* TCP服务器实现类 |
||||
|
* 负责监听端口、接受客户端连接并处理通信 |
||||
|
*/ |
||||
|
|
||||
|
public class TcpServer { |
||||
|
|
||||
|
private static final Logger logger = LoggerFactory.getLogger(TcpServer.class); |
||||
|
|
||||
|
private final GatewayServerConfig config; |
||||
|
private final MessageHandler messageHandler; |
||||
|
private ServerSocket serverSocket; |
||||
|
private ExecutorService clientThreadPool; |
||||
|
|
||||
|
/** |
||||
|
* 构造函数 |
||||
|
* @param config 服务器配置 |
||||
|
* @param messageHandler 消息处理器 |
||||
|
* @param executorService 客户端处理线程池 |
||||
|
*/ |
||||
|
public TcpServer(GatewayServerConfig config, MessageHandler messageHandler, ExecutorService executorService) { |
||||
|
this.config = config; |
||||
|
this.messageHandler = messageHandler; |
||||
|
this.clientThreadPool = executorService; |
||||
|
} |
||||
|
private ExecutorService acceptThreadPool; |
||||
|
private volatile boolean running = false; |
||||
|
|
||||
|
// 存储活跃的客户端连接
|
||||
|
private final Map<String, ClientHandler> activeClients = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
/** |
||||
|
* 启动TCP服务器 |
||||
|
* @throws IOException 如果启动失败 |
||||
|
*/ |
||||
|
public void start() throws IOException { |
||||
|
if (running) { |
||||
|
logger.warn("服务器已经在运行中"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 使用注入的线程池(如果为null则创建新线程池)
|
||||
|
if (clientThreadPool == null) { |
||||
|
clientThreadPool = new ThreadPoolExecutor( |
||||
|
config.getCorePoolSize(), |
||||
|
config.getMaxPoolSize(), |
||||
|
config.getThreadKeepaliveSeconds(), |
||||
|
TimeUnit.SECONDS, |
||||
|
new LinkedBlockingQueue<>(config.getThreadQueueCapacity()), |
||||
|
r -> new Thread(r, "client-handler-" + System.nanoTime()), |
||||
|
new ThreadPoolExecutor.CallerRunsPolicy() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
acceptThreadPool = Executors.newSingleThreadExecutor( |
||||
|
r -> { |
||||
|
Thread t = new Thread(r, "accept-thread"); |
||||
|
t.setDaemon(true); |
||||
|
return t; |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 创建并启动ServerSocket
|
||||
|
serverSocket = new ServerSocket(config.getPort()); |
||||
|
running = true; |
||||
|
|
||||
|
logger.info("TCP服务器已启动,监听端口: {}", config.getPort()); |
||||
|
|
||||
|
// 开始接受客户端连接
|
||||
|
acceptThreadPool.submit(this::acceptClients); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 停止TCP服务器 |
||||
|
*/ |
||||
|
@PreDestroy |
||||
|
public void stop() { |
||||
|
logger.info("正在停止TCP服务器..."); |
||||
|
running = false; |
||||
|
|
||||
|
try { |
||||
|
// 关闭ServerSocket
|
||||
|
if (serverSocket != null && !serverSocket.isClosed()) { |
||||
|
serverSocket.close(); |
||||
|
} |
||||
|
|
||||
|
// 关闭所有客户端连接
|
||||
|
activeClients.values().forEach(ClientHandler::close); |
||||
|
activeClients.clear(); |
||||
|
|
||||
|
// 关闭线程池
|
||||
|
if (clientThreadPool != null) { |
||||
|
clientThreadPool.shutdown(); |
||||
|
clientThreadPool.awaitTermination(5, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
if (acceptThreadPool != null) { |
||||
|
acceptThreadPool.shutdown(); |
||||
|
acceptThreadPool.awaitTermination(5, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
logger.info("TCP服务器已成功停止"); |
||||
|
} catch (Exception e) { |
||||
|
logger.error("停止TCP服务器时发生错误", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 接受客户端连接的方法 |
||||
|
*/ |
||||
|
private void acceptClients() { |
||||
|
logger.info("开始接受客户端连接..."); |
||||
|
|
||||
|
while (running) { |
||||
|
try { |
||||
|
// 接受新的客户端连接
|
||||
|
Socket clientSocket = serverSocket.accept(); |
||||
|
|
||||
|
// 检查连接数是否超过限制
|
||||
|
if (activeClients.size() >= config.getMaxConnections()) { |
||||
|
logger.warn("连接数已达上限({}),拒绝新连接: {}", |
||||
|
config.getMaxConnections(), clientSocket.getRemoteSocketAddress()); |
||||
|
clientSocket.close(); |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// 设置Socket参数
|
||||
|
clientSocket.setSoTimeout(config.getReadTimeout()); |
||||
|
clientSocket.setKeepAlive(true); |
||||
|
|
||||
|
String clientId = generateClientId(clientSocket); |
||||
|
logger.info("新的客户端连接: {}, ID: {}", |
||||
|
clientSocket.getRemoteSocketAddress(), clientId); |
||||
|
|
||||
|
// 创建客户端处理器
|
||||
|
ClientHandler clientHandler = new ClientHandler(clientSocket, clientId, messageHandler, config); |
||||
|
activeClients.put(clientId, clientHandler); |
||||
|
|
||||
|
// 提交到线程池处理
|
||||
|
clientThreadPool.submit(() -> { |
||||
|
try { |
||||
|
clientHandler.handle(); |
||||
|
} finally { |
||||
|
// 连接关闭后从活跃列表中移除
|
||||
|
activeClients.remove(clientId); |
||||
|
logger.info("客户端连接已关闭: {}, 当前活跃连接数: {}", |
||||
|
clientId, activeClients.size()); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} catch (IOException e) { |
||||
|
if (running) { // 只有在服务器运行中时记录错误
|
||||
|
logger.error("接受客户端连接时发生错误", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
logger.info("停止接受客户端连接"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成客户端ID |
||||
|
* @param socket 客户端Socket |
||||
|
* @return 客户端ID |
||||
|
*/ |
||||
|
private String generateClientId(Socket socket) { |
||||
|
String address = socket.getInetAddress().getHostAddress(); |
||||
|
int port = socket.getPort(); |
||||
|
long timestamp = System.currentTimeMillis(); |
||||
|
return String.format("%s:%d:%d", address, port, timestamp); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前活跃的客户端数量 |
||||
|
* @return 活跃客户端数量 |
||||
|
*/ |
||||
|
public int getActiveClientCount() { |
||||
|
return activeClients.size(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取所有活跃客户端的ID列表 |
||||
|
* @return 客户端ID列表 |
||||
|
*/ |
||||
|
public Map<String, String> getActiveClients() { |
||||
|
Map<String, String> clients = new ConcurrentHashMap<>(); |
||||
|
activeClients.forEach((id, handler) -> { |
||||
|
clients.put(id, handler.getClientAddress()); |
||||
|
}); |
||||
|
return clients; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 向指定客户端发送消息 |
||||
|
* @param clientId 客户端ID |
||||
|
* @param message 要发送的消息 |
||||
|
* @return 是否发送成功 |
||||
|
*/ |
||||
|
public boolean sendMessage(String clientId, String message) { |
||||
|
ClientHandler handler = activeClients.get(clientId); |
||||
|
if (handler != null) { |
||||
|
try { |
||||
|
handler.sendMessage(message); |
||||
|
return true; |
||||
|
} catch (IOException e) { |
||||
|
logger.error("向客户端发送消息失败: {}", clientId, e); |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 向所有客户端广播消息 |
||||
|
* @param message 要广播的消息 |
||||
|
*/ |
||||
|
public void broadcastMessage(String message) { |
||||
|
activeClients.forEach((id, handler) -> { |
||||
|
try { |
||||
|
handler.sendMessage(message); |
||||
|
} catch (IOException e) { |
||||
|
logger.error("向客户端广播消息失败: {}", id, e); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 关闭指定客户端连接 |
||||
|
* @param clientId 客户端ID |
||||
|
*/ |
||||
|
public void disconnectClient(String clientId) { |
||||
|
ClientHandler handler = activeClients.remove(clientId); |
||||
|
if (handler != null) { |
||||
|
handler.close(); |
||||
|
logger.info("已手动关闭客户端连接: {}", clientId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查服务器是否正在运行 |
||||
|
* @return 是否运行中 |
||||
|
*/ |
||||
|
public boolean isRunning() { |
||||
|
return running; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
package com.techsor.datacenter; |
||||
|
|
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.io.OutputStream; |
||||
|
import java.net.Socket; |
||||
|
import java.net.SocketTimeoutException; |
||||
|
import java.util.Arrays; |
||||
|
|
||||
|
/** |
||||
|
* TCP客户端测试程序,用于发送符合协议格式的消息到服务器 |
||||
|
*/ |
||||
|
public class TcpClientTest { |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
String serverIp = "127.0.0.1"; |
||||
|
int serverPort = 8888; |
||||
|
|
||||
|
try (Socket socket = new Socket(serverIp, serverPort); |
||||
|
OutputStream outputStream = socket.getOutputStream(); |
||||
|
InputStream inputStream = socket.getInputStream()) { |
||||
|
|
||||
|
System.out.println("成功连接到服务器: " + serverIp + ":" + serverPort); |
||||
|
|
||||
|
// 发送符合协议格式的消息:02 30 30 30 31 30 32 03 (STX + "0001" + "02" + ETX)
|
||||
|
// 表示系统开始指示消息,序列号为0001
|
||||
|
byte[] message = new byte[]{0x02, 0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x03}; |
||||
|
|
||||
|
System.out.println("发送消息: " + bytesToHex(message)); |
||||
|
outputStream.write(message); |
||||
|
outputStream.flush(); |
||||
|
|
||||
|
// 等待并接收服务器响应
|
||||
|
System.out.println("等待服务器响应..."); |
||||
|
socket.setSoTimeout(5000); // 设置5秒超时
|
||||
|
|
||||
|
try { |
||||
|
byte[] responseBuffer = new byte[1024]; |
||||
|
int bytesRead = inputStream.read(responseBuffer); |
||||
|
|
||||
|
if (bytesRead > 0) { |
||||
|
byte[] response = Arrays.copyOf(responseBuffer, bytesRead); |
||||
|
System.out.println("收到服务器响应: " + bytesToHex(response)); |
||||
|
System.out.println("响应ASCII: " + new String(response, java.nio.charset.StandardCharsets.US_ASCII)); |
||||
|
} else { |
||||
|
System.out.println("未收到服务器响应"); |
||||
|
} |
||||
|
} catch (SocketTimeoutException e) { |
||||
|
System.out.println("接收服务器响应超时"); |
||||
|
} |
||||
|
|
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将字节数组转换为十六进制字符串 |
||||
|
*/ |
||||
|
private static String bytesToHex(byte[] bytes) { |
||||
|
StringBuilder hexString = new StringBuilder(); |
||||
|
for (byte b : bytes) { |
||||
|
String hex = Integer.toHexString(0xFF & b); |
||||
|
if (hex.length() == 1) { |
||||
|
hexString.append('0'); |
||||
|
} |
||||
|
hexString.append(hex).append(' '); |
||||
|
} |
||||
|
return hexString.toString().trim(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue