| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- package com.usky.ems.protocol;
- import com.usky.ems.util.AesUtil;
- import org.dom4j.Document;
- import org.dom4j.DocumentHelper;
- import org.dom4j.Element;
- 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.util.concurrent.atomic.AtomicBoolean;
- /**
- * TCP客户端
- * 用于与能耗监管系统建立连接并发送数据
- *
- * @author system
- * @since 2024-01-01
- */
- public class TcpClient {
- private static final Logger logger = LoggerFactory.getLogger(TcpClient.class);
- private String host;
- private int port;
- private String authKey;
- private Socket socket;
- private InputStream inputStream;
- private OutputStream outputStream;
- private AtomicBoolean connected = new AtomicBoolean(false);
- private AtomicBoolean authenticated = new AtomicBoolean(false);
- public TcpClient(String host, int port, String authKey) {
- this.host = host;
- this.port = port;
- this.authKey = authKey;
- }
- /**
- * 建立TCP连接
- *
- * @return 是否连接成功
- */
- public boolean connect() {
- try {
- if (socket != null && !socket.isClosed()) {
- socket.close();
- }
- socket = new Socket(host, port);
- socket.setSoTimeout(30000); // 30秒超时
- inputStream = socket.getInputStream();
- outputStream = socket.getOutputStream();
- connected.set(true);
- authenticated.set(false);
- logger.info("TCP连接成功: {}:{}", host, port);
- return true;
- } catch (IOException e) {
- logger.error("TCP连接失败: {}:{}", host, port, e);
- connected.set(false);
- return false;
- }
- }
- /**
- * 关闭连接
- */
- public void close() {
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- if (outputStream != null) {
- outputStream.close();
- }
- if (socket != null && !socket.isClosed()) {
- socket.close();
- }
- connected.set(false);
- authenticated.set(false);
- logger.info("TCP连接已关闭");
- } catch (IOException e) {
- logger.error("关闭TCP连接失败", e);
- }
- }
- /**
- * 身份认证
- *
- * @param buildingId 建筑ID
- * @param gatewayId 网关ID
- * @return 是否认证成功
- */
- public boolean authenticate(String buildingId, String gatewayId) {
- if (!connected.get()) {
- logger.error("未建立TCP连接,无法进行身份认证");
- return false;
- }
- try {
- // 1. 发送身份认证请求
- String requestXml = com.usky.ems.util.XmlBuilder.buildAuthRequest(buildingId, gatewayId);
- sendPacket(NetworkPacket.TYPE_AUTH, requestXml, false);
- // 2. 接收服务端返回的随机序列
- NetworkPacket response = receivePacket();
- if (response == null || response.getType() != NetworkPacket.TYPE_AUTH) {
- logger.error("接收身份认证响应失败");
- return false;
- }
- // 认证响应不加密
- String responseXml = new String(response.getData(), java.nio.charset.StandardCharsets.UTF_8);
- String sequence = parseSequenceFromXml(responseXml);
- if (sequence == null || sequence.isEmpty()) {
- logger.error("解析随机序列失败");
- return false;
- }
- // 3. 计算MD5:密钥 + 随机序列
- String md5 = com.usky.ems.util.Md5Util.md5(authKey + sequence);
- // 4. 发送MD5值
- String md5Xml = com.usky.ems.util.XmlBuilder.buildAuthMd5Request(buildingId, gatewayId, md5);
- sendPacket(NetworkPacket.TYPE_AUTH, md5Xml, false);
- // 5. 接收认证结果
- NetworkPacket result = receivePacket();
- if (result == null || result.getType() != NetworkPacket.TYPE_AUTH) {
- logger.error("接收认证结果失败");
- return false;
- }
- // 认证响应不加密
- String resultXml = new String(result.getData(), java.nio.charset.StandardCharsets.UTF_8);
- boolean success = parseAuthResult(resultXml);
- if (success) {
- authenticated.set(true);
- logger.info("身份认证成功");
- } else {
- logger.error("身份认证失败");
- }
- return success;
- } catch (Exception e) {
- logger.error("身份认证过程异常", e);
- return false;
- }
- }
- /**
- * 发送心跳
- *
- * @param buildingId 建筑ID
- * @param gatewayId 网关ID
- * @return 是否发送成功
- */
- public boolean sendHeartbeat(String buildingId, String gatewayId) {
- if (!connected.get() || !authenticated.get()) {
- logger.warn("连接未建立或未认证,无法发送心跳");
- return false;
- }
- try {
- String heartbeatXml = com.usky.ems.util.XmlBuilder.buildHeartbeatRequest(buildingId, gatewayId);
- sendPacket(NetworkPacket.TYPE_HEARTBEAT, heartbeatXml, false);
- logger.debug("心跳发送成功");
- return true;
- } catch (Exception e) {
- logger.error("发送心跳失败", e);
- return false;
- }
- }
- /**
- * 发送能耗数据
- *
- * @param xmlData XML数据(未加密)
- * @return 是否发送成功
- */
- public boolean sendEnergyData(String xmlData) {
- if (!connected.get() || !authenticated.get()) {
- logger.warn("连接未建立或未认证,无法发送能耗数据");
- return false;
- }
- try {
- sendPacket(NetworkPacket.TYPE_ENERGY_DATA, xmlData, true);
- logger.info("能耗数据发送成功");
- // 接收服务端响应
- NetworkPacket response = receivePacket();
- if (response != null && response.getType() == NetworkPacket.TYPE_ENERGY_DATA) {
- // 能耗数据响应需要解密
- try {
- byte[] responseData = response.getData();
- String responseXml = null;
- Exception lastException = null;
- // 方法1:首先尝试直接解密字节数组
- try {
- responseXml = AesUtil.decrypt(authKey,responseData);
- logger.debug("直接解密字节数组成功");
- } catch (Exception e1) {
- lastException = e1;
- logger.debug("直接解密字节数组失败: {}", e1.getMessage());
- // 方法2:如果直接解密失败,尝试作为Base64字符串处理
- // 先检查数据是否可能是Base64字符串(只包含Base64字符)
- if (responseXml == null) {
- try {
- String dataString = new String(responseData, java.nio.charset.StandardCharsets.UTF_8);
- // 检查是否只包含Base64字符(A-Z, a-z, 0-9, +, /, =)
- if (dataString.matches("^[A-Za-z0-9+/=]+$")) {
- // Base64解码后再解密
- byte[] decodedBytes = java.util.Base64.getDecoder().decode(dataString);
- responseXml = AesUtil.decrypt(authKey,decodedBytes);
- logger.debug("Base64解码后解密成功");
- } else {
- logger.debug("数据不是Base64格式,跳过Base64解码");
- }
- } catch (Exception e2) {
- lastException = e2;
- logger.debug("Base64解码后解密失败: {}", e2.getMessage());
- }
- }
- }
- if (responseXml != null) {
- logger.debug("服务端响应: {}", responseXml);
- } else {
- throw lastException != null ? lastException : new Exception("所有解密方法都失败");
- }
- } catch (Exception e) {
- logger.warn("解密服务端响应失败", e);
- logger.debug("响应数据长度: {}, 前100字节: {}",
- response.getData() != null ? response.getData().length : 0,
- response.getData() != null && response.getData().length > 0
- ? java.util.Arrays.toString(java.util.Arrays.copyOf(response.getData(), Math.min(100, response.getData().length)))
- : "null");
- }
- return true;
- }
- return true;
- } catch (Exception e) {
- logger.error("发送能耗数据失败", e);
- return false;
- }
- }
- /**
- * 发送数据包
- *
- * @param type 消息类型
- * @param xmlData XML数据
- * @param encrypt 是否加密
- */
- private void sendPacket(byte type, String xmlData, boolean encrypt) throws IOException {
- byte[] data;
- if (encrypt) {
- // 能耗数据需要AES加密
- try {
- data = AesUtil.encryptEnergyXml(xmlData, authKey);
- } catch (Exception e) {
- throw new IOException("加密能耗数据失败", e);
- }
- } else {
- // 身份认证和心跳不加密
- data = xmlData.getBytes(java.nio.charset.StandardCharsets.UTF_8);
- }
- NetworkPacket packet = new NetworkPacket();
- packet.setType(type);
- packet.setData(data);
- byte[] packetBytes = packet.encode();
- outputStream.write(packetBytes);
- outputStream.flush();
- }
- /**
- * 接收数据包
- *
- * @return 接收到的数据包
- */
- private NetworkPacket receivePacket() throws IOException {
- // 先读取7字节(Head + Type + Length)
- byte[] header = new byte[7];
- int bytesRead = 0;
- while (bytesRead < 7) {
- int n = inputStream.read(header, bytesRead, 7 - bytesRead);
- if (n == -1) {
- throw new IOException("连接已关闭");
- }
- bytesRead += n;
- }
- // 解析Length
- int length = ((header[3] & 0xFF) << 24) |
- ((header[4] & 0xFF) << 16) |
- ((header[5] & 0xFF) << 8) |
- (header[6] & 0xFF);
- // 读取Data
- byte[] data = new byte[length];
- bytesRead = 0;
- while (bytesRead < length) {
- int n = inputStream.read(data, bytesRead, length - bytesRead);
- if (n == -1) {
- throw new IOException("连接已关闭");
- }
- bytesRead += n;
- }
- // 组装完整数据包
- byte[] packetBytes = new byte[7 + length];
- System.arraycopy(header, 0, packetBytes, 0, 7);
- System.arraycopy(data, 0, packetBytes, 7, length);
- return NetworkPacket.decode(packetBytes);
- }
- /**
- * 从XML中解析随机序列
- */
- private String parseSequenceFromXml(String xml) {
- try {
- Document document = DocumentHelper.parseText(xml);
- Element root = document.getRootElement();
- Element idValidate = root.element("id_validate");
- if (idValidate != null) {
- Element sequence = idValidate.element("sequence");
- if (sequence != null) {
- return sequence.getTextTrim();
- }
- }
- } catch (Exception e) {
- logger.error("解析随机序列失败", e);
- }
- return null;
- }
- /**
- * 从XML中解析认证结果
- */
- private boolean parseAuthResult(String xml) {
- try {
- Document document = DocumentHelper.parseText(xml);
- Element root = document.getRootElement();
- Element idValidate = root.element("id_validate");
- if (idValidate != null && "result".equals(idValidate.attributeValue("operation"))) {
- Element result = idValidate.element("result");
- if (result != null) {
- return "pass".equals(result.getTextTrim());
- }
- }
- } catch (Exception e) {
- logger.error("解析认证结果失败", e);
- }
- return false;
- }
- public boolean isConnected() {
- return connected.get() && socket != null && !socket.isClosed();
- }
- public boolean isAuthenticated() {
- return authenticated.get();
- }
- }
|