package jiekou;
|
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import java.io.*;
|
import java.net.Socket;
|
import java.nio.charset.StandardCharsets;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
/**
|
* TCP客户端工具类
|
* 实现发卡机与服务器的所有通信接口
|
*/
|
public class TcpClientUtil {
|
private static final Logger logger = LoggerFactory.getLogger(TcpClientUtil.class);
|
|
private Socket socket;
|
private OutputStream outputStream;
|
private InputStream inputStream;
|
private BufferedReader reader;
|
private volatile boolean running = false;
|
private MessageListener messageListener;
|
|
// 重连相关配置
|
private String serverIp;
|
private int serverPort;
|
private String deviceId;
|
private final AtomicBoolean reconnecting = new AtomicBoolean(false);
|
private static final int RECONNECT_INTERVAL = 3000; // 重连间隔3秒
|
|
// JSON解析模式
|
private static final Pattern JSON_PATTERN = Pattern.compile("\"([^\"]+)\"\\s*:\\s*(\"[^\"]*\"|[^,\\}\\s]*)");
|
private static final Pattern ARRAY_PATTERN = Pattern.compile("\\{([^}]*)\\}");
|
|
/**
|
* 连接TCP服务器
|
* @param ip 服务器IP地址
|
* @param port 服务器端口
|
* @return 连接是否成功
|
*/
|
public boolean connect(String ip, int port) {
|
try {
|
// 保存连接参数用于重连
|
this.serverIp = ip;
|
this.serverPort = port;
|
|
// 1. 创建Socket连接服务器
|
this.socket = new Socket(ip, port);
|
// 移除超时设置,让监听线程可以无限等待
|
socket.setSoTimeout(0);
|
|
// 2. 获取输入输出流
|
this.outputStream = socket.getOutputStream();
|
this.inputStream = socket.getInputStream();
|
this.reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
|
|
logger.info("成功连接到服务器: {}:{}", ip, port);
|
reconnecting.set(false); // 重置重连状态
|
return true;
|
|
} catch (IOException e) {
|
logger.error("TCP连接异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 连接并登录服务器
|
* @param ip 服务器IP地址
|
* @param port 服务器端口
|
* @param deviceId 设备ID
|
* @return 登录是否成功
|
*/
|
public boolean connectAndLogin(String ip, int port, String deviceId) {
|
this.deviceId = deviceId; // 保存设备ID用于重连后重新登录
|
if (!connect(ip, port)) {
|
return false;
|
}
|
|
Map<String, Object> response = sendLoginRequest(deviceId);
|
return response != null &&
|
"logined".equals(response.get("cmd")) &&
|
"100".equals(String.valueOf(response.get("code")));
|
}
|
|
/**
|
* 发送登录请求
|
* @param deviceId 设备ID
|
* @return 登录响应数据
|
*/
|
public Map<String, Object> sendLoginRequest(String deviceId) {
|
try {
|
// 构建登录请求JSON
|
String loginRequest = buildLoginRequestJson(deviceId);
|
logger.debug("发送登录请求: {}", loginRequest);
|
|
// 发送登录请求
|
boolean sendSuccess = sendMessage(loginRequest);
|
if (!sendSuccess) {
|
return null;
|
}
|
|
// 读取服务器响应
|
String response = reader.readLine();
|
logger.info("收到服务器登录响应: {}", response);
|
|
// 解析响应并返回
|
return parseJson(response);
|
|
} catch (IOException e) {
|
logger.error("登录请求异常: {}", e.getMessage(), e);
|
return null;
|
}
|
}
|
|
/**
|
* 请求人员数据
|
* @param deviceId 设备ID
|
* @return 人员数据列表
|
*/
|
public List<Map<String, Object>> requestPersonData(String deviceId) {
|
try {
|
// 构建人员数据请求JSON
|
String batchGetRequest = buildBatchGetRequestJson(deviceId);
|
logger.debug("发送人员数据请求: {}", batchGetRequest);
|
|
// 发送请求
|
boolean sendSuccess = sendMessage(batchGetRequest);
|
if (!sendSuccess) {
|
return null;
|
}
|
|
// 读取服务器响应(人员数据)
|
String response = reader.readLine();
|
logger.info("收到人员数据: {}", response);
|
|
// 解析响应
|
Map<String, Object> responseMap = parseJson(response);
|
if ("send".equals(responseMap.get("cmd")) && "101".equals(String.valueOf(responseMap.get("code")))) {
|
// 解析人员数据
|
String dataStr = (String) responseMap.get("data");
|
List<Map<String, Object>> personList = parsePersonDataArray(dataStr);
|
|
// 发送成功应答
|
sendSentAck();
|
|
return personList;
|
}
|
|
return null;
|
|
} catch (IOException e) {
|
logger.error("请求人员数据异常: {}", e.getMessage(), e);
|
return null;
|
}
|
}
|
|
/**
|
* 发送人员数据接收成功应答
|
*/
|
public boolean sendSentAck() {
|
try {
|
String ackJson = "{\"code\":101,\"cmd\":\"sent\",\"data\":null}";
|
return sendMessage(ackJson);
|
} catch (Exception e) {
|
logger.error("发送成功应答异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 发送删除人员成功应答
|
* @param userId 用户ID
|
* @return 发送是否成功
|
*/
|
public boolean sendDeleteAck(String userId) {
|
try {
|
String ackJson = "{\"code\":102,\"cmd\":\"deleted\",\"data\":null}";
|
boolean success = sendMessage(ackJson);
|
if (success) {
|
logger.info("发送删除人员成功应答,用户ID: {}", userId);
|
}
|
return success;
|
} catch (Exception e) {
|
logger.error("发送删除人员成功应答异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 发送开锁成功应答
|
* @param doorNo 门编号
|
* @return 发送是否成功
|
*/
|
public boolean sendOpenDoorAck(String doorNo) {
|
try {
|
String ackJson = "{\"code\":103,\"cmd\":\"opened\",\"data\":null}";
|
boolean success = sendMessage(ackJson);
|
if (success) {
|
logger.info("发送开锁成功应答,门编号: {}", doorNo);
|
}
|
return success;
|
} catch (Exception e) {
|
logger.error("发送开锁成功应答异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 发送升级成功应答
|
* @param updateUrl 升级文件URL
|
* @return 发送是否成功
|
*/
|
public boolean sendUpdateAck(String updateUrl) {
|
try {
|
String ackJson = "{\"code\":104,\"cmd\":\"upDated\",\"data\":null}";
|
boolean success = sendMessage(ackJson);
|
if (success) {
|
logger.info("发送升级成功应答,升级URL: {}", updateUrl);
|
}
|
return success;
|
} catch (Exception e) {
|
logger.error("发送升级成功应答异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 发送自定义命令
|
* @param code 命令代码
|
* @param cmd 命令类型
|
* @param data 数据
|
* @return 发送是否成功
|
*/
|
public boolean sendCommand(String code, String cmd, String data) {
|
try {
|
String json = buildCommandJson(code, cmd, data);
|
boolean success = sendMessage(json);
|
if (success) {
|
logger.debug("发送命令: {}", json);
|
}
|
return success;
|
} catch (Exception e) {
|
logger.error("发送命令异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 通用消息发送方法
|
* @param message 要发送的消息
|
* @return 发送是否成功
|
*/
|
public boolean sendMessage(String message) {
|
if (!isConnected()) {
|
logger.error("连接未建立,无法发送消息");
|
return false;
|
}
|
|
try {
|
outputStream.write((message + "\n").getBytes(StandardCharsets.UTF_8));
|
outputStream.flush();
|
return true;
|
} catch (IOException e) {
|
logger.error("发送消息异常: {}", e.getMessage(), e);
|
return false;
|
}
|
}
|
|
/**
|
* 读取服务器响应
|
* @return 服务器响应数据
|
*/
|
public String readResponse() {
|
try {
|
return reader.readLine();
|
} catch (IOException e) {
|
logger.error("读取响应异常: {}", e.getMessage(), e);
|
return null;
|
}
|
}
|
|
/**
|
* 读取并解析服务器响应
|
* @return 解析后的响应Map
|
*/
|
public Map<String, Object> readAndParseResponse() {
|
String response = readResponse();
|
if (response != null) {
|
return parseJson(response);
|
}
|
return null;
|
}
|
|
/**
|
* 断线重连功能
|
*/
|
private void reconnect() {
|
if (reconnecting.getAndSet(true)) {
|
logger.debug("重连操作已在进行中,跳过本次重连");
|
return;
|
}
|
|
logger.info("开始断线重连...");
|
while (running) {
|
try {
|
logger.info("尝试重连,目标服务器: {}:{}", serverIp, serverPort);
|
|
// 关闭旧连接
|
closeResources(reader, inputStream, outputStream, socket);
|
|
// 等待一段时间后重连
|
Thread.sleep(RECONNECT_INTERVAL);
|
|
// 尝试重新连接
|
if (connect(serverIp, serverPort)) {
|
// 重新登录
|
if (deviceId != null) {
|
Map<String, Object> loginResponse = sendLoginRequest(deviceId);
|
if (loginResponse != null &&
|
"logined".equals(loginResponse.get("cmd")) &&
|
"100".equals(String.valueOf(loginResponse.get("code")))) {
|
|
logger.info("重连并登录成功");
|
reconnecting.set(false);
|
|
// 重新启动消息监听
|
if (messageListener != null) {
|
startMessageListener(messageListener);
|
}
|
return;
|
}
|
} else {
|
// 如果没有设备ID,只连接不登录
|
logger.info("重连成功");
|
reconnecting.set(false);
|
if (messageListener != null) {
|
startMessageListener(messageListener);
|
}
|
return;
|
}
|
}
|
|
logger.warn("重连失败,{} 毫秒后重试", RECONNECT_INTERVAL);
|
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
logger.warn("重连过程被中断");
|
break;
|
} catch (Exception e) {
|
logger.error("重连异常: {}", e.getMessage());
|
}
|
}
|
|
reconnecting.set(false);
|
if (running) {
|
logger.error("重连循环退出,将在下次断开后重新尝试");
|
}
|
}
|
|
/**
|
* 启动消息监听器
|
* @param listener 消息监听回调接口
|
*/
|
public void startMessageListener(MessageListener listener) {
|
this.messageListener = listener;
|
running = true;
|
new Thread(() -> {
|
try {
|
while (running && socket != null && !socket.isClosed()) {
|
try {
|
String message = reader.readLine();
|
if (message != null) {
|
logger.debug("收到服务器消息: {}", message);
|
|
// 解析消息
|
Map<String, Object> parsedMessage = parseJson(message);
|
|
// 回调给监听器
|
if (messageListener != null) {
|
messageListener.onMessageReceived(message, parsedMessage);
|
}
|
} else {
|
// 读到null表示连接已关闭
|
logger.warn("连接被服务器关闭");
|
break;
|
}
|
} catch (IOException e) {
|
if (running) {
|
logger.error("读取消息异常: {}", e.getMessage());
|
// 如果不是主动停止,尝试重连
|
if (running && !reconnecting.get()) {
|
reconnect();
|
}
|
break;
|
}
|
}
|
}
|
} finally {
|
logger.info("消息监听线程结束");
|
// 如果还在运行状态但连接已断开,尝试重连
|
if (running && !reconnecting.get()) {
|
reconnect();
|
}
|
}
|
}, "TCP-Client-Listener").start();
|
}
|
|
/**
|
* 停止消息监听
|
*/
|
public void stopMessageListener() {
|
running = false;
|
messageListener = null;
|
reconnecting.set(false); // 停止重连
|
}
|
|
/**
|
* 解析简单JSON字符串为Map
|
* @param json JSON字符串
|
* @return 解析后的Map
|
*/
|
public Map<String, Object> parseJson(String json) {
|
Map<String, Object> result = new HashMap<>();
|
if (json == null || json.trim().isEmpty()) {
|
return result;
|
}
|
|
// 移除首尾的{}
|
String content = json.trim();
|
if (content.startsWith("{") && content.endsWith("}")) {
|
content = content.substring(1, content.length() - 1).trim();
|
}
|
|
Matcher matcher = JSON_PATTERN.matcher(content);
|
while (matcher.find()) {
|
String key = matcher.group(1);
|
String value = matcher.group(2).trim();
|
|
// 移除值的引号(如果存在)
|
if (value.startsWith("\"") && value.endsWith("\"")) {
|
value = value.substring(1, value.length() - 1);
|
}
|
|
// 处理null值
|
if ("null".equals(value)) {
|
result.put(key, null);
|
} else {
|
result.put(key, value);
|
}
|
}
|
|
return result;
|
}
|
|
/**
|
* 解析人员数据数组
|
* @param dataStr 数据字符串
|
* @return 人员列表
|
*/
|
private List<Map<String, Object>> parsePersonDataArray(String dataStr) {
|
List<Map<String, Object>> result = new ArrayList<>();
|
if (dataStr == null || dataStr.trim().isEmpty()) {
|
return result;
|
}
|
|
// 使用正则表达式匹配数组中的每个对象
|
Matcher matcher = ARRAY_PATTERN.matcher(dataStr);
|
while (matcher.find()) {
|
String objStr = matcher.group(1);
|
Map<String, Object> person = parsePersonObject(objStr);
|
if (!person.isEmpty()) {
|
result.add(person);
|
}
|
}
|
|
return result;
|
}
|
|
/**
|
* 解析单个人员对象
|
* @param objStr 对象字符串
|
* @return 人员信息Map
|
*/
|
private Map<String, Object> parsePersonObject(String objStr) {
|
Map<String, Object> person = new HashMap<>();
|
if (objStr == null || objStr.trim().isEmpty()) {
|
return person;
|
}
|
|
// 分割键值对
|
String[] pairs = objStr.split(",");
|
for (String pair : pairs) {
|
String[] keyValue = pair.split(":", 2);
|
if (keyValue.length == 2) {
|
String key = keyValue[0].trim();
|
String value = keyValue[1].trim();
|
|
// 移除值的引号(如果存在)
|
if (value.startsWith("'") && value.endsWith("'")) {
|
value = value.substring(1, value.length() - 1);
|
} else if (value.startsWith("\"") && value.endsWith("\"")) {
|
value = value.substring(1, value.length() - 1);
|
}
|
|
person.put(key, value);
|
}
|
}
|
|
return person;
|
}
|
|
/**
|
* 构建登录请求JSON
|
* @return JSON字符串
|
*/
|
private String buildLoginRequestJson(String deviceId) {
|
return "{\"code\":100,\"cmd\":\"login\",\"deviceId\":\"" + deviceId + "\"}";
|
}
|
|
/**
|
* 构建人员数据请求JSON
|
* @return JSON字符串
|
*/
|
private String buildBatchGetRequestJson(String deviceId) {
|
return "{\"code\":101,\"cmd\":\"batchGet\",\"deviceId\":\"" + deviceId + "\"}";
|
}
|
|
/**
|
* 构建通用命令JSON
|
* @param code 命令代码
|
* @param cmd 命令类型
|
* @param data 数据
|
* @return JSON字符串
|
*/
|
private String buildCommandJson(String code, String cmd, String data) {
|
if (data != null) {
|
return "{\"code\":\"" + code + "\",\"cmd\":\"" + cmd + "\",\"data\":\"" + data + "\"}";
|
} else {
|
return "{\"code\":\"" + code + "\",\"cmd\":\"" + cmd + "\",\"data\":null}";
|
}
|
}
|
|
/**
|
* 关闭连接
|
*/
|
public void disconnect() {
|
stopMessageListener();
|
closeResources(reader, inputStream, outputStream, socket);
|
logger.info("断开与服务器的连接");
|
}
|
|
/**
|
* 检查连接状态
|
* @return 是否连接
|
*/
|
public boolean isConnected() {
|
return socket != null && !socket.isClosed() && socket.isConnected();
|
}
|
|
/**
|
* 关闭所有资源
|
*/
|
private static void closeResources(Closeable... resources) {
|
for (Closeable resource : resources) {
|
if (resource != null) {
|
try {
|
resource.close();
|
} catch (IOException e) {
|
logger.error("关闭资源时发生异常: {}", e.getMessage(), e);
|
}
|
}
|
}
|
}
|
|
/**
|
* 消息监听回调接口
|
*/
|
public interface MessageListener {
|
/**
|
* 当收到服务器消息时回调
|
* @param rawMessage 原始消息字符串
|
* @param parsedMessage 解析后的消息Map
|
*/
|
void onMessageReceived(String rawMessage, Map<String, Object> parsedMessage);
|
|
}
|
}
|