From 4003f4b0280adcbec03fe6e1a43ad1f1de891759 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 26 Jan 2026 19:20:05 +0800 Subject: [PATCH] feat:【iot】TCP 协议:1)合并单测,通过 codec 切换;2)兼容下行的时候,基于连接的 codec 处理 --- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java | 4 +--- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java | 5 +---- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java | 21 +++++---------------- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java | 23 +++++++---------------- yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java | 7 +++---- yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java | 385 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md | 193 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java | 205 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md | 191 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 639 insertions(+), 1017 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java delete mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java delete mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md delete mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java delete mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md create mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md create mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java index 47c7a79..1b475e9 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java @@ -115,11 +115,9 @@ public class IotGatewayConfiguration { @Bean public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler, IotDeviceMessageService messageService, - IotDeviceService deviceService, IotTcpConnectionManager connectionManager, IotMessageBus messageBus) { - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, deviceService, connectionManager, - messageBus); + return new IotTcpDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus); } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java index e4d46b3..aec6718 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager; import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler; -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; @@ -25,8 +24,6 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber { private String getMessageCodecType(Buffer buffer, NetSocket socket) { // 1. 如果已认证,优先使用缓存的编解码类型 IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); - if (connectionInfo != null && connectionInfo.isAuthenticated() && - StrUtil.isNotBlank(connectionInfo.getCodecType())) { + if (connectionInfo != null + && StrUtil.isNotBlank(connectionInfo.getCodecType())) { return connectionInfo.getCodecType(); } @@ -255,8 +255,7 @@ public class IotTcpUpstreamHandler implements Handler { .setProductKey(device.getProductKey()) .setDeviceName(device.getDeviceName()) .setClientId(clientId) - .setCodecType(codecType) - .setAuthenticated(true); + .setCodecType(codecType); // 注册连接 connectionManager.registerConnection(socket, device.getId(), connectionInfo); } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java new file mode 100644 index 0000000..ec93952 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java @@ -0,0 +1,238 @@ +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.IdUtil; +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; +import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; +import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec; +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * IoT 直连设备 TCP 协议集成测试(手动测试) + * + *

测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 协议直接连接平台 + * + *

支持两种编解码格式: + *

    + *
  • {@link IotTcpJsonDeviceMessageCodec} - JSON 格式
  • + *
  • {@link IotTcpBinaryDeviceMessageCodec} - 二进制格式
  • + *
+ * + *

使用步骤: + *

    + *
  1. 启动 yudao-module-iot-gateway 服务(TCP 端口 8091)
  2. + *
  3. 修改 {@link #CODEC} 选择测试的编解码格式
  4. + *
  5. 运行以下测试方法: + *
      + *
    • {@link #testAuth()} - 设备认证
    • + *
    • {@link #testPropertyPost()} - 设备属性上报
    • + *
    • {@link #testEventPost()} - 设备事件上报
    • + *
    + *
  6. + *
+ * + *

注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息 + * + * @author 芋道源码 + */ +@Slf4j +public class IotDirectDeviceTcpProtocolIntegrationTest { + + private static final String SERVER_HOST = "127.0.0.1"; + private static final int SERVER_PORT = 8091; + private static final int TIMEOUT_MS = 5000; + + // ===================== 编解码器选择(修改此处切换 JSON / Binary) ===================== +// private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec(); + private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec(); + + // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== + private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; + private static final String DEVICE_NAME = "small"; + private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; + + // ===================== 认证测试 ===================== + + /** + * 认证测试:设备认证 + */ + @Test + public void testAuth() throws Exception { + // 1.1 构建认证消息 + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() + .setClientId(authInfo.getClientId()) + .setUsername(authInfo.getUsername()) + .setPassword(authInfo.getPassword()); + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null); + // 1.2 编码 + byte[] payload = CODEC.encode(request); + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length); + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) { + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload)); + } + + // 2.1 发送请求 + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { + socket.setSoTimeout(TIMEOUT_MS); + byte[] responseBytes = sendAndReceive(socket, payload); + // 2.2 解码响应 + if (responseBytes != null) { + IotDeviceMessage response = CODEC.decode(responseBytes); + log.info("[testAuth][响应消息: {}]", response); + } else { + log.warn("[testAuth][未收到响应]"); + } + } + } + + // ===================== 直连设备属性上报测试 ===================== + + /** + * 属性上报测试 + */ + @Test + public void testPropertyPost() throws Exception { + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { + socket.setSoTimeout(TIMEOUT_MS); + + // 1. 先进行认证 + IotDeviceMessage authResponse = authenticate(socket); + log.info("[testPropertyPost][认证响应: {}]", authResponse); + + // 2.1 构建属性上报消息 + IotDeviceMessage request = IotDeviceMessage.of( + IdUtil.fastSimpleUUID(), + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), + IotDevicePropertyPostReqDTO.of(MapUtil.builder() + .put("width", 1) + .put("height", "2") + .build()), + null, null, null); + // 2.2 编码 + byte[] payload = CODEC.encode(request); + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); + + // 3.1 发送请求 + byte[] responseBytes = sendAndReceive(socket, payload); + // 3.2 解码响应 + if (responseBytes != null) { + IotDeviceMessage response = CODEC.decode(responseBytes); + log.info("[testPropertyPost][响应消息: {}]", response); + } else { + log.warn("[testPropertyPost][未收到响应]"); + } + } + } + + // ===================== 直连设备事件上报测试 ===================== + + /** + * 事件上报测试 + */ + @Test + public void testEventPost() throws Exception { + try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { + socket.setSoTimeout(TIMEOUT_MS); + + // 1. 先进行认证 + IotDeviceMessage authResponse = authenticate(socket); + log.info("[testEventPost][认证响应: {}]", authResponse); + + // 2.1 构建事件上报消息 + IotDeviceMessage request = IotDeviceMessage.of( + IdUtil.fastSimpleUUID(), + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(), + IotDeviceEventPostReqDTO.of( + "eat", + MapUtil.builder().put("rice", 3).build(), + System.currentTimeMillis()), + null, null, null); + // 2.2 编码 + byte[] payload = CODEC.encode(request); + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request); + + // 3.1 发送请求 + byte[] responseBytes = sendAndReceive(socket, payload); + // 3.2 解码响应 + if (responseBytes != null) { + IotDeviceMessage response = CODEC.decode(responseBytes); + log.info("[testEventPost][响应消息: {}]", response); + } else { + log.warn("[testEventPost][未收到响应]"); + } + } + } + + // ===================== 辅助方法 ===================== + + /** + * 执行设备认证 + * + * @param socket TCP 连接 + * @return 认证响应消息 + */ + private IotDeviceMessage authenticate(Socket socket) throws Exception { + IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); + IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() + .setClientId(authInfo.getClientId()) + .setUsername(authInfo.getUsername()) + .setPassword(authInfo.getPassword()); + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null); + byte[] payload = CODEC.encode(request); + byte[] responseBytes = sendAndReceive(socket, payload); + if (responseBytes != null) { + log.info("[authenticate][响应数据长度: {} 字节,首字节: 0x{}, HEX: {}]", + responseBytes.length, + String.format("%02X", responseBytes[0]), + HexUtil.encodeHexStr(responseBytes)); + return CODEC.decode(responseBytes); + } + return null; + } + + /** + * 发送 TCP 请求并接收响应 + * + * @param socket TCP Socket + * @param payload 请求数据 + * @return 响应数据 + */ + private byte[] sendAndReceive(Socket socket, byte[] payload) throws Exception { + // 1. 发送请求 + OutputStream out = socket.getOutputStream(); + InputStream in = socket.getInputStream(); + out.write(payload); + out.flush(); + + // 2.1 等待一小段时间让服务器处理 + Thread.sleep(100); + // 2.2 接收响应 + byte[] buffer = new byte[4096]; + try { + int length = in.read(buffer); + if (length > 0) { + byte[] response = new byte[length]; + System.arraycopy(buffer, 0, response, 0, length); + return response; + } + return null; + } catch (java.net.SocketTimeoutException e) { + log.warn("[sendAndReceive][接收响应超时]"); + return null; + } + } + +} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java deleted file mode 100644 index 2efd77f..0000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/IotDirectDeviceTcpBinaryProtocolIntegrationTest.java +++ /dev/null @@ -1,385 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.binary; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; -import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; -import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import io.vertx.core.buffer.Buffer; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -/** - * IoT 直连设备 TCP 二进制协议集成测试(手动测试) - * - *

测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP 二进制协议直接连接平台 - * - *

使用步骤: - *

    - *
  1. 启动 yudao-module-iot-gateway 服务(TCP 端口 8091)
  2. - *
  3. 运行 {@link #testAuth()} 获取设备认证,认证成功后连接保持
  4. - *
  5. 运行以下测试方法: - *
      - *
    • {@link #testPropertyPost()} - 设备属性上报
    • - *
    • {@link #testEventPost()} - 设备事件上报
    • - *
    - *
  6. - *
- * - *

注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息 - * - *

二进制协议格式说明请参考:{@code tcp-binary-packet-examples.md} - * - * @author 芋道源码 - */ -@Slf4j -public class IotDirectDeviceTcpBinaryProtocolIntegrationTest { - - private static final String SERVER_HOST = "127.0.0.1"; - private static final int SERVER_PORT = 8091; - private static final int TIMEOUT_MS = 5000; - - // ===================== 二进制协议常量 ===================== - /** - * 协议魔术字,用于协议识别 - */ - private static final byte MAGIC_NUMBER = (byte) 0x7E; - - /** - * 协议版本号 - */ - private static final byte PROTOCOL_VERSION = (byte) 0x01; - - /** - * 请求消息类型 - */ - private static final byte REQUEST = (byte) 0x01; - - // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== - private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; - private static final String DEVICE_NAME = "small"; - private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; - - // ===================== 认证测试 ===================== - - /** - * 认证测试:设备认证(二进制格式) - */ - @Test - public void testAuth() throws Exception { - // 1.1 构建认证参数 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() - .setClientId(authInfo.getClientId()) - .setUsername(authInfo.getUsername()) - .setPassword(authInfo.getPassword()); - // 1.2 构建二进制请求 - String messageId = IdUtil.fastSimpleUUID(); - String method = "auth"; - byte[] payload = buildBinaryRequest(messageId, method, authReqDTO); - // 1.3 输出请求 - log.info("[testAuth][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(authReqDTO)); - log.info("[testAuth][二进制数据包长度: {} 字节]", payload.length); - log.info("[testAuth][二进制数据包(HEX): {}]", bytesToHex(payload)); - - // 2.1 发送请求 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - byte[] response = sendAndReceiveBinary(socket, payload); - // 2.2 输出结果 - if (response != null) { - log.info("[testAuth][响应数据包长度: {} 字节]", response.length); - log.info("[testAuth][响应数据包(HEX): {}]", bytesToHex(response)); - parseBinaryResponse(response); - } else { - log.warn("[testAuth][未收到响应]"); - } - } - } - - // ===================== 直连设备属性上报测试 ===================== - - /** - * 属性上报测试(二进制格式) - * - * 注意:TCP 协议需要先认证,这里为了简化测试,在同一连接上先认证再上报 - */ - @Test - public void testPropertyPost() throws Exception { - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - - // 1. 先进行认证 - byte[] authResponse = authenticateBinary(socket); - log.info("[testPropertyPost][认证响应长度: {} 字节]", authResponse != null ? authResponse.length : 0); - if (authResponse != null) { - parseBinaryResponse(authResponse); - } - - // 2.1 构建属性上报请求 - String messageId = IdUtil.fastSimpleUUID(); - String method = IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); - Object params = IotDevicePropertyPostReqDTO.of(MapUtil.builder() - .put("width", 1) - .put("height", "2") - .build()); - byte[] payload = buildBinaryRequest(messageId, method, params); - // 2.2 输出请求 - log.info("[testPropertyPost][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(params)); - log.info("[testPropertyPost][二进制数据包长度: {} 字节]", payload.length); - - // 3.1 发送请求 - byte[] response = sendAndReceiveBinary(socket, payload); - // 3.2 输出结果 - if (response != null) { - log.info("[testPropertyPost][响应数据包长度: {} 字节]", response.length); - parseBinaryResponse(response); - } else { - log.warn("[testPropertyPost][未收到响应]"); - } - } - } - - // ===================== 直连设备事件上报测试 ===================== - - /** - * 事件上报测试(二进制格式) - * - * 注意:TCP 协议需要先认证,这里为了简化测试,在同一连接上先认证再上报 - */ - @Test - public void testEventPost() throws Exception { - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - - // 1. 先进行认证 - byte[] authResponse = authenticateBinary(socket); - log.info("[testEventPost][认证响应长度: {} 字节]", authResponse != null ? authResponse.length : 0); - if (authResponse != null) { - parseBinaryResponse(authResponse); - } - - // 2.1 构建事件上报请求 - String messageId = IdUtil.fastSimpleUUID(); - String method = IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); - Object params = IotDeviceEventPostReqDTO.of( - "eat", - MapUtil.builder().put("rice", 3).build(), - System.currentTimeMillis()); - byte[] payload = buildBinaryRequest(messageId, method, params); - // 2.2 输出请求 - log.info("[testEventPost][消息ID: {}, 方法: {}, 参数: {}]", messageId, method, JsonUtils.toJsonString(params)); - log.info("[testEventPost][二进制数据包长度: {} 字节]", payload.length); - - // 3.1 发送请求 - byte[] response = sendAndReceiveBinary(socket, payload); - // 3.2 输出结果 - if (response != null) { - log.info("[testEventPost][响应数据包长度: {} 字节]", response.length); - parseBinaryResponse(response); - } else { - log.warn("[testEventPost][未收到响应]"); - } - } - } - - // ===================== 辅助方法 ===================== - - /** - * 执行设备认证(二进制格式) - * - * @param socket TCP 连接 - * @return 认证响应 - */ - private byte[] authenticateBinary(Socket socket) throws Exception { - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() - .setClientId(authInfo.getClientId()) - .setUsername(authInfo.getUsername()) - .setPassword(authInfo.getPassword()); - String messageId = IdUtil.fastSimpleUUID(); - byte[] payload = buildBinaryRequest(messageId, "auth", authReqDTO); - return sendAndReceiveBinary(socket, payload); - } - - /** - * 构建二进制请求数据包 - * - *

协议格式: - *

-     * +--------+--------+--------+---------------------------+
-     * | 魔术字 | 版本号 | 消息类型|      消息长度(4字节)      |
-     * +--------+--------+--------+---------------------------+
-     * |    消息ID长度(2字节)    |    消息ID(变长字符串)      |
-     * +--------+--------+--------+--------+--------+--------+
-     * |    方法名长度(2字节)    |    方法名(变长字符串)      |
-     * +--------+--------+--------+--------+--------+--------+
-     * |                 消息体数据(变长)                    |
-     * +--------+--------+--------+--------+--------+--------+
-     * 
- * - * @param messageId 消息 ID - * @param method 方法名 - * @param params 请求参数 - * @return 二进制数据包 - */ - private byte[] buildBinaryRequest(String messageId, String method, Object params) { - Buffer buffer = Buffer.buffer(); - // 1. 写入协议头部 - buffer.appendByte(MAGIC_NUMBER); - buffer.appendByte(PROTOCOL_VERSION); - buffer.appendByte(REQUEST); - // 2. 预留消息长度位置 - int lengthPosition = buffer.length(); - buffer.appendInt(0); - // 3. 写入消息 ID - byte[] messageIdBytes = StrUtil.utf8Bytes(messageId); - buffer.appendShort((short) messageIdBytes.length); - buffer.appendBytes(messageIdBytes); - // 4. 写入方法名 - byte[] methodBytes = StrUtil.utf8Bytes(method); - buffer.appendShort((short) methodBytes.length); - buffer.appendBytes(methodBytes); - // 5. 写入消息体(params 序列化为 JSON) - if (params != null) { - buffer.appendBytes(JsonUtils.toJsonByte(params)); - } - // 6. 更新消息长度 - buffer.setInt(lengthPosition, buffer.length()); - return buffer.getBytes(); - } - - /** - * 解析二进制响应数据包 - * - * @param data 响应数据 - */ - private void parseBinaryResponse(byte[] data) { - if (data == null || data.length < 11) { - log.warn("[parseBinaryResponse][数据包过短]"); - return; - } - - Buffer buffer = Buffer.buffer(data); - int index = 0; - - // 1. 解析魔术字 - byte magic = buffer.getByte(index++); - log.info("[parseBinaryResponse][魔术字: 0x{} (预期: 0x7E)]", String.format("%02X", magic)); - - // 2. 解析版本号 - byte version = buffer.getByte(index++); - log.info("[parseBinaryResponse][版本号: 0x{}]", String.format("%02X", version)); - - // 3. 解析消息类型 - byte messageType = buffer.getByte(index++); - log.info("[parseBinaryResponse][消息类型: 0x{} (0x01=请求, 0x02=响应)]", String.format("%02X", messageType)); - - // 4. 解析消息长度 - int messageLength = buffer.getInt(index); - index += 4; - log.info("[parseBinaryResponse][消息长度: {}]", messageLength); - - // 5. 解析消息 ID - short messageIdLength = buffer.getShort(index); - index += 2; - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name()); - index += messageIdLength; - log.info("[parseBinaryResponse][消息ID: {}]", messageId); - - // 6. 解析方法名 - short methodLength = buffer.getShort(index); - index += 2; - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name()); - index += methodLength; - log.info("[parseBinaryResponse][方法名: {}]", method); - - // 7. 解析消息体 - if (messageType == 0x02) { // RESPONSE - // 响应消息:code(4) + msgLen(2) + msg + data(JSON) - if (index + 4 <= buffer.length()) { - int code = buffer.getInt(index); - index += 4; - log.info("[parseBinaryResponse][响应码: {}]", code); - - if (index + 2 <= buffer.length()) { - short msgLength = buffer.getShort(index); - index += 2; - if (msgLength > 0 && index + msgLength <= buffer.length()) { - String msg = buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name()); - index += msgLength; - log.info("[parseBinaryResponse][响应消息: {}]", msg); - } - - if (index < buffer.length()) { - String dataJson = buffer.getString(index, buffer.length(), StandardCharsets.UTF_8.name()); - log.info("[parseBinaryResponse][响应数据: {}]", dataJson); - } - } - } - } else { - // 请求消息:params(JSON) - if (index < buffer.length()) { - String paramsJson = buffer.getString(index, buffer.length(), StandardCharsets.UTF_8.name()); - log.info("[parseBinaryResponse][请求参数: {}]", paramsJson); - } - } - } - - /** - * 发送二进制请求并接收响应 - * - * @param socket TCP Socket - * @param payload 二进制数据包 - * @return 响应数据 - */ - private byte[] sendAndReceiveBinary(Socket socket, byte[] payload) throws Exception { - // 1. 发送请求 - OutputStream out = socket.getOutputStream(); - InputStream in = socket.getInputStream(); - out.write(payload); - out.flush(); - - // 2.1 等待一小段时间让服务器处理 - Thread.sleep(100); - // 2.2 接收响应 - byte[] buffer = new byte[4096]; - try { - int length = in.read(buffer); - if (length > 0) { - byte[] response = new byte[length]; - System.arraycopy(buffer, 0, response, 0, length); - return response; - } - return null; - } catch (java.net.SocketTimeoutException e) { - log.warn("[sendAndReceiveBinary][接收响应超时]"); - return null; - } - } - - // TODO @AI:hutool 简化下; - /** - * 字节数组转十六进制字符串 - * - * @param bytes 字节数组 - * @return 十六进制字符串 - */ - private String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02X ", b)); - } - return sb.toString().trim(); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md deleted file mode 100644 index d6b2b3f..0000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/binary/tcp-binary-packet-examples.md +++ /dev/null @@ -1,193 +0,0 @@ -# TCP 二进制协议数据包格式说明 - -## 1. 协议概述 - -TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景。 - -### 1.1 协议特点 - -- **高效传输**:完全二进制格式,减少数据传输量 -- **版本控制**:内置协议版本号,支持协议升级 -- **类型安全**:明确的消息类型标识 -- **简洁设计**:去除冗余字段,协议更加精简 -- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容 - -## 2. 协议格式 - -### 2.1 整体结构 - -``` -+--------+--------+--------+---------------------------+--------+--------+ -| 魔术字 | 版本号 | 消息类型| 消息长度(4字节) | -+--------+--------+--------+---------------------------+--------+--------+ -| 消息 ID 长度(2字节) | 消息 ID (变长字符串) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -| 方法名长度(2字节) | 方法名(变长字符串) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -| 消息体数据(变长) | -+--------+--------+--------+--------+--------+--------+--------+--------+ -``` - -### 2.2 字段详细说明 - -| 字段 | 长度 | 类型 | 说明 | -|------|------|------|------| -| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 | -| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 | -| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 | -| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) | -| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 | -| 消息 ID | 变长 | string | 消息唯一标识符(UTF-8编码) | -| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 | -| 方法名 | 变长 | string | 消息方法名(UTF-8编码) | -| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 | - -**⚠️ 重要说明**:deviceId 不包含在协议中,由服务器根据连接上下文自动设置 - -### 2.3 协议常量定义 - -```java -// 协议标识 -private static final byte MAGIC_NUMBER = (byte) 0x7E; -private static final byte PROTOCOL_VERSION = (byte) 0x01; - -// 消息类型 -private static final byte REQUEST = (byte) 0x01; // 请求消息 -private static final byte RESPONSE = (byte) 0x02; // 响应消息 - -// 协议长度 -private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度 -private static final int MIN_MESSAGE_LENGTH = 11; // 最小消息长度 -``` - -## 3. 消息类型和格式 - -### 3.1 请求消息 (REQUEST - 0x01) - -请求消息用于设备向服务器发送数据或请求。 - -#### 3.1.1 消息体格式 -``` -消息体 = params 数据(JSON格式) -``` - -#### 3.1.2 示例:设备认证请求 - -**消息内容:** -- 消息 ID: `auth_1704067200000_123` -- 方法名: `auth` -- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}` - -**二进制数据包结构:** -``` -7E // 魔术字 (0x7E) -01 // 版本号 (0x01) -01 // 消息类型 (REQUEST) -00 00 00 89 // 消息长度 (137字节) -00 19 // 消息 ID 长度 (25字节) -61 75 74 68 5F 31 37 30 34 30 // 消息 ID: "auth_1704067200000_123" -36 37 32 30 30 30 30 30 5F 31 -32 33 -00 04 // 方法名长度 (4字节) -61 75 74 68 // 方法名: "auth" -7B 22 63 6C 69 65 6E 74 49 64 // JSON参数数据 -22 3A 22 64 65 76 69 63 65 5F // {"clientId":"device_001", -30 30 31 22 2C 22 75 73 65 72 // "username":"productKey_deviceName", -6E 61 6D 65 22 3A 22 70 72 6F // "password":"device_password"} -64 75 63 74 4B 65 79 5F 64 65 -76 69 63 65 4E 61 6D 65 22 2C -22 70 61 73 73 77 6F 72 64 22 -3A 22 64 65 76 69 63 65 5F 70 -61 73 73 77 6F 72 64 22 7D -``` - -#### 3.1.3 示例:属性数据上报 - -**消息内容:** -- 消息 ID: `property_1704067200000_456` -- 方法名: `thing.property.post` -- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}` - -### 3.2 响应消息 (RESPONSE - 0x02) - -响应消息用于服务器向设备回复请求结果。 - -#### 3.2.1 消息体格式 -``` -消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON) -``` - -#### 3.2.2 字段说明 - -| 字段 | 长度 | 类型 | 说明 | -|------|------|------|------| -| 响应码 | 4字节 | int | HTTP状态码风格,0=成功,其他=错误 | -| 响应消息长度 | 2字节 | short | 响应消息字符串的字节长度 | -| 响应消息 | 变长 | string | 响应提示信息(UTF-8编码) | -| 响应数据 | 变长 | binary | JSON格式的响应数据(可选) | - -#### 3.2.3 示例:认证成功响应 - -**消息内容:** -- 消息 ID: `auth_response_1704067200000_123` -- 方法名: `auth` -- 响应码: `0` -- 响应消息: `认证成功` -- 响应数据: `{"success":true,"message":"认证成功"}` - -**二进制数据包结构:** -``` -7E // 魔术字 (0x7E) -01 // 版本号 (0x01) -02 // 消息类型 (RESPONSE) -00 00 00 A4 // 消息长度 (164字节) -00 22 // 消息 ID 长度 (34字节) -61 75 74 68 5F 72 65 73 70 6F // 消息 ID: "auth_response_1704067200000_123" -6E 73 65 5F 31 37 30 34 30 36 -37 32 30 30 30 30 30 5F 31 32 -33 -00 04 // 方法名长度 (4字节) -61 75 74 68 // 方法名: "auth" -00 00 00 00 // 响应码 (0 = 成功) -00 0C // 响应消息长度 (12字节) -E8 AE A4 E8 AF 81 E6 88 90 E5 // 响应消息: "认证成功" (UTF-8) -8A 9F -7B 22 73 75 63 63 65 73 73 22 // JSON响应数据 -3A 74 72 75 65 2C 22 6D 65 73 // {"success":true,"message":"认证成功"} -73 61 67 65 22 3A 22 E8 AE A4 -E8 AF 81 E6 88 90 E5 8A 9F 22 -7D -``` - -## 4. 编解码器标识 - -```java -public static final String TYPE = "TCP_BINARY"; -``` - -## 5. 协议优势 - -- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量 -- **解析高效**:直接二进制操作,减少字符串转换开销 -- **类型安全**:明确的消息类型和字段定义 -- **设计简洁**:去除冗余字段,协议更加精简高效 -- **版本控制**:内置版本号支持协议升级 - -## 6. 与 JSON 协议对比 - -| 特性 | 二进制协议 | JSON协议 | -|------|-------------|--------| -| 数据大小 | 小(节省30-50%) | 大 | -| 解析性能 | 高 | 中等 | -| 网络开销 | 低 | 高 | -| 可读性 | 差 | 优秀 | -| 调试难度 | 高 | 低 | -| 扩展性 | 良好 | 优秀 | - -**推荐场景**: -- ✅ **高频数据传输**:传感器数据实时上报 -- ✅ **带宽受限环境**:移动网络、卫星通信 -- ✅ **性能要求高**:需要低延迟、高吞吐的场景 -- ✅ **设备资源有限**:嵌入式设备、低功耗设备 -- ❌ **开发调试阶段**:调试困难,建议使用 JSON 协议 -- ❌ **快速原型开发**:开发效率低 diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java deleted file mode 100644 index 62940ca..0000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/IotDirectDeviceTcpJsonProtocolIntegrationTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.json; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; -import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; -import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.nio.charset.StandardCharsets; - -/** - * IoT 直连设备 TCP JSON 协议集成测试(手动测试) - * - *

测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 TCP JSON 协议直接连接平台 - * - *

使用步骤: - *

    - *
  1. 启动 yudao-module-iot-gateway 服务(TCP 端口 8091)
  2. - *
  3. 运行 {@link #testAuth()} 获取设备认证,认证成功后连接保持
  4. - *
  5. 运行以下测试方法: - *
      - *
    • {@link #testPropertyPost()} - 设备属性上报
    • - *
    • {@link #testEventPost()} - 设备事件上报
    • - *
    - *
  6. - *
- * - *

注意:TCP 协议是有状态的长连接,认证成功后同一连接上的后续请求无需再携带认证信息 - * - * @author 芋道源码 - */ -@Slf4j -public class IotDirectDeviceTcpJsonProtocolIntegrationTest { - - private static final String SERVER_HOST = "127.0.0.1"; - private static final int SERVER_PORT = 8091; - private static final int TIMEOUT_MS = 5000; - - // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== - private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; - private static final String DEVICE_NAME = "small"; - private static final String DEVICE_SECRET = "0baa4c2ecc104ae1a26b4070c218bdf3"; - - // ===================== 认证测试 ===================== - - /** - * 认证测试:设备认证 - */ - @Test - public void testAuth() throws Exception { - // 1.1 构建请求 - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() - .setClientId(authInfo.getClientId()) - .setUsername(authInfo.getUsername()) - .setPassword(authInfo.getPassword()); - String payload = JsonUtils.toJsonString(MapUtil.builder() - .put("id", IdUtil.fastSimpleUUID()) - .put("method", "auth") - .put("params", authReqDTO) - .put("timestamp", System.currentTimeMillis()) - .build()); - // 1.2 输出请求 - log.info("[testAuth][请求体: {}]", payload); - - // 2.1 发送请求 - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - String response = sendAndReceive(socket, payload); - // 2.2 输出结果 - log.info("[testAuth][响应体: {}]", response); - } - } - - // ===================== 直连设备属性上报测试 ===================== - - /** - * 属性上报测试 - */ - @Test - public void testPropertyPost() throws Exception { - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - - // 1. 先进行认证 - String authResponse = authenticate(socket); - log.info("[testPropertyPost][认证响应: {}]", authResponse); - - // 2.1 构建属性上报请求 - String payload = JsonUtils.toJsonString(MapUtil.builder() - .put("id", IdUtil.fastSimpleUUID()) - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod()) - .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.builder() - .put("width", 1) - .put("height", "2") - .build())) - .put("timestamp", System.currentTimeMillis()) - .build()); - // 2.2 输出请求 - log.info("[testPropertyPost][请求体: {}]", payload); - - // 3.1 发送请求 - String response = sendAndReceive(socket, payload); - // 3.2 输出结果 - log.info("[testPropertyPost][响应体: {}]", response); - } - } - - // ===================== 直连设备事件上报测试 ===================== - - /** - * 事件上报测试 - */ - @Test - public void testEventPost() throws Exception { - try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT)) { - socket.setSoTimeout(TIMEOUT_MS); - - // 1. 先进行认证 - String authResponse = authenticate(socket); - log.info("[testEventPost][认证响应: {}]", authResponse); - - // 2.1 构建事件上报请求 - String payload = JsonUtils.toJsonString(MapUtil.builder() - .put("id", IdUtil.fastSimpleUUID()) - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod()) - .put("params", IotDeviceEventPostReqDTO.of( - "eat", - MapUtil.builder().put("rice", 3).build(), - System.currentTimeMillis())) - .put("timestamp", System.currentTimeMillis()) - .build()); - // 2.2 输出请求 - log.info("[testEventPost][请求体: {}]", payload); - - // 3.1 发送请求 - String response = sendAndReceive(socket, payload); - // 3.2 输出结果 - log.info("[testEventPost][响应体: {}]", response); - } - } - - // ===================== 辅助方法 ===================== - - /** - * 执行设备认证 - * - * @param socket TCP 连接 - * @return 认证响应 - */ - private String authenticate(Socket socket) throws Exception { - IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); - IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() - .setClientId(authInfo.getClientId()) - .setUsername(authInfo.getUsername()) - .setPassword(authInfo.getPassword()); - String payload = JsonUtils.toJsonString(MapUtil.builder() - .put("id", IdUtil.fastSimpleUUID()) - .put("method", "auth") - .put("params", authReqDTO) - .put("timestamp", System.currentTimeMillis()) - .build()); - return sendAndReceive(socket, payload); - } - - /** - * 发送 TCP 请求并接收响应 - * - * @param socket TCP Socket - * @param payload 请求体 - * @return 响应内容 - */ - private String sendAndReceive(Socket socket, String payload) throws Exception { - // 1. 发送请求 - OutputStream out = socket.getOutputStream(); - InputStream in = socket.getInputStream(); - byte[] sendData = payload.getBytes(StandardCharsets.UTF_8); - out.write(sendData); - out.flush(); - - // 2.1 等待一小段时间让服务器处理 - Thread.sleep(100); - // 2.2 接收响应 - byte[] buffer = new byte[4096]; - try { - int length = in.read(buffer); - if (length > 0) { - return new String(buffer, 0, length, StandardCharsets.UTF_8); - } - return null; - } catch (java.net.SocketTimeoutException e) { - log.warn("[sendAndReceive][接收响应超时]"); - return null; - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md deleted file mode 100644 index 09ef50c..0000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/json/tcp-json-packet-examples.md +++ /dev/null @@ -1,191 +0,0 @@ -# TCP JSON 格式协议说明 - -## 1. 协议概述 - -TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点: - -- **标准化**:使用标准 JSON 格式,易于解析和处理 -- **可读性**:人类可读,便于调试和维护 -- **扩展性**:可以轻松添加新字段,向后兼容 -- **跨平台**:JSON 格式支持所有主流编程语言 -- **安全优化**:移除冗余的 deviceId 字段,提高安全性 - -## 2. 消息格式 - -### 2.1 基础消息结构 - -```json -{ - "id": "消息唯一标识", - "method": "消息方法", - "params": { - // 请求参数 - }, - "data": { - // 响应数据 - }, - "code": 响应码, - "msg": "响应消息", - "timestamp": 时间戳 -} -``` - -**⚠️ 重要说明**: -- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID -- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息 - -### 2.2 字段详细说明 - -| 字段名 | 类型 | 必填 | 用途 | 说明 | -|--------|------|------|------|------| -| id | String | 是 | 所有消息 | 消息唯一标识 | -| method | String | 是 | 所有消息 | 消息方法,如 `auth`、`thing.property.post` | -| params | Object | 否 | 请求消息 | 请求参数,具体内容根据method而定 | -| data | Object | 否 | 响应消息 | 响应数据,服务器返回的结果数据 | -| code | Integer | 否 | 响应消息 | 响应码,0=成功,其他=错误 | -| msg | String | 否 | 响应消息 | 响应提示信息 | -| timestamp | Long | 是 | 所有消息 | 时间戳(毫秒),编码时自动生成 | - -### 2.3 消息分类 - -#### 2.3.1 请求消息(上行) -- **特征**:包含 `params` 字段,不包含 `code`、`msg` 字段 -- **方向**:设备 → 服务器 -- **用途**:设备认证、数据上报、状态更新等 - -#### 2.3.2 响应消息(下行) -- **特征**:包含 `code`、`msg` 字段,可能包含 `data` 字段 -- **方向**:服务器 → 设备 -- **用途**:认证结果、指令响应、错误提示等 - -## 3. 消息示例 - -### 3.1 设备认证 (auth) - -#### 认证请求格式 -**消息方向**:设备 → 服务器 - -```json -{ - "id": "auth_1704067200000_123", - "method": "auth", - "params": { - "clientId": "device_001", - "username": "productKey_deviceName", - "password": "设备密码" - }, - "timestamp": 1704067200000 -} -``` - -**认证参数说明:** - -| 字段名 | 类型 | 必填 | 说明 | -|--------|------|------|------| -| clientId | String | 是 | 客户端唯一标识,用于连接管理 | -| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` | -| password | String | 是 | 设备密码,在设备管理平台配置 | - -#### 认证响应格式 -**消息方向**:服务器 → 设备 - -**认证成功响应:** -```json -{ - "id": "response_auth_1704067200000_123", - "method": "auth", - "data": { - "success": true, - "message": "认证成功" - }, - "code": 0, - "msg": "认证成功", - "timestamp": 1704067200001 -} -``` - -**认证失败响应:** -```json -{ - "id": "response_auth_1704067200000_123", - "method": "auth", - "data": { - "success": false, - "message": "认证失败:用户名或密码错误" - }, - "code": 401, - "msg": "认证失败", - "timestamp": 1704067200001 -} -``` - -### 3.2 属性数据上报 (thing.property.post) - -**消息方向**:设备 → 服务器 - -**示例:温度传感器数据上报** -```json -{ - "id": "property_1704067200000_456", - "method": "thing.property.post", - "params": { - "temperature": 25.5, - "humidity": 60.2, - "pressure": 1013.25, - "battery": 85, - "signal_strength": -65 - }, - "timestamp": 1704067200000 -} -``` - -### 3.3 设备状态更新 (thing.state.update) - -**消息方向**:设备 → 服务器 - -**示例:心跳请求** -```json -{ - "id": "heartbeat_1704067200000_321", - "method": "thing.state.update", - "params": { - "state": "online", - "uptime": 86400, - "memory_usage": 65.2, - "cpu_usage": 12.8 - }, - "timestamp": 1704067200000 -} -``` - -## 4. 编解码器标识 - -```java -public static final String TYPE = "TCP_JSON"; -``` - -## 5. 协议优势 - -- **开发效率高**:JSON 格式,开发和调试简单 -- **跨语言支持**:所有主流语言都支持 JSON -- **可读性优秀**:可以直接查看消息内容 -- **扩展性强**:可以轻松添加新字段 -- **安全性高**:移除 deviceId 字段,防止伪造攻击 - -## 6. 与二进制协议对比 - -| 特性 | JSON协议 | 二进制协议 | -|------|----------|------------| -| 开发难度 | 低 | 高 | -| 调试难度 | 低 | 高 | -| 可读性 | 优秀 | 差 | -| 数据大小 | 中等 | 小(节省30-50%) | -| 解析性能 | 中等 | 高 | -| 学习成本 | 低 | 高 | - -**推荐场景**: -- ✅ **开发调试阶段**:调试友好,开发效率高 -- ✅ **快速原型开发**:实现简单,快速迭代 -- ✅ **多语言集成**:广泛的语言支持 -- ❌ **高频数据传输**:建议使用二进制协议 -- ❌ **带宽受限环境**:建议使用二进制协议 \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md new file mode 100644 index 0000000..d6b2b3f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-binary-packet-examples.md @@ -0,0 +1,193 @@ +# TCP 二进制协议数据包格式说明 + +## 1. 协议概述 + +TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景。 + +### 1.1 协议特点 + +- **高效传输**:完全二进制格式,减少数据传输量 +- **版本控制**:内置协议版本号,支持协议升级 +- **类型安全**:明确的消息类型标识 +- **简洁设计**:去除冗余字段,协议更加精简 +- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容 + +## 2. 协议格式 + +### 2.1 整体结构 + +``` ++--------+--------+--------+---------------------------+--------+--------+ +| 魔术字 | 版本号 | 消息类型| 消息长度(4字节) | ++--------+--------+--------+---------------------------+--------+--------+ +| 消息 ID 长度(2字节) | 消息 ID (变长字符串) | ++--------+--------+--------+--------+--------+--------+--------+--------+ +| 方法名长度(2字节) | 方法名(变长字符串) | ++--------+--------+--------+--------+--------+--------+--------+--------+ +| 消息体数据(变长) | ++--------+--------+--------+--------+--------+--------+--------+--------+ +``` + +### 2.2 字段详细说明 + +| 字段 | 长度 | 类型 | 说明 | +|------|------|------|------| +| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 | +| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 | +| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 | +| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) | +| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 | +| 消息 ID | 变长 | string | 消息唯一标识符(UTF-8编码) | +| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 | +| 方法名 | 变长 | string | 消息方法名(UTF-8编码) | +| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 | + +**⚠️ 重要说明**:deviceId 不包含在协议中,由服务器根据连接上下文自动设置 + +### 2.3 协议常量定义 + +```java +// 协议标识 +private static final byte MAGIC_NUMBER = (byte) 0x7E; +private static final byte PROTOCOL_VERSION = (byte) 0x01; + +// 消息类型 +private static final byte REQUEST = (byte) 0x01; // 请求消息 +private static final byte RESPONSE = (byte) 0x02; // 响应消息 + +// 协议长度 +private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度 +private static final int MIN_MESSAGE_LENGTH = 11; // 最小消息长度 +``` + +## 3. 消息类型和格式 + +### 3.1 请求消息 (REQUEST - 0x01) + +请求消息用于设备向服务器发送数据或请求。 + +#### 3.1.1 消息体格式 +``` +消息体 = params 数据(JSON格式) +``` + +#### 3.1.2 示例:设备认证请求 + +**消息内容:** +- 消息 ID: `auth_1704067200000_123` +- 方法名: `auth` +- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}` + +**二进制数据包结构:** +``` +7E // 魔术字 (0x7E) +01 // 版本号 (0x01) +01 // 消息类型 (REQUEST) +00 00 00 89 // 消息长度 (137字节) +00 19 // 消息 ID 长度 (25字节) +61 75 74 68 5F 31 37 30 34 30 // 消息 ID: "auth_1704067200000_123" +36 37 32 30 30 30 30 30 5F 31 +32 33 +00 04 // 方法名长度 (4字节) +61 75 74 68 // 方法名: "auth" +7B 22 63 6C 69 65 6E 74 49 64 // JSON参数数据 +22 3A 22 64 65 76 69 63 65 5F // {"clientId":"device_001", +30 30 31 22 2C 22 75 73 65 72 // "username":"productKey_deviceName", +6E 61 6D 65 22 3A 22 70 72 6F // "password":"device_password"} +64 75 63 74 4B 65 79 5F 64 65 +76 69 63 65 4E 61 6D 65 22 2C +22 70 61 73 73 77 6F 72 64 22 +3A 22 64 65 76 69 63 65 5F 70 +61 73 73 77 6F 72 64 22 7D +``` + +#### 3.1.3 示例:属性数据上报 + +**消息内容:** +- 消息 ID: `property_1704067200000_456` +- 方法名: `thing.property.post` +- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}` + +### 3.2 响应消息 (RESPONSE - 0x02) + +响应消息用于服务器向设备回复请求结果。 + +#### 3.2.1 消息体格式 +``` +消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON) +``` + +#### 3.2.2 字段说明 + +| 字段 | 长度 | 类型 | 说明 | +|------|------|------|------| +| 响应码 | 4字节 | int | HTTP状态码风格,0=成功,其他=错误 | +| 响应消息长度 | 2字节 | short | 响应消息字符串的字节长度 | +| 响应消息 | 变长 | string | 响应提示信息(UTF-8编码) | +| 响应数据 | 变长 | binary | JSON格式的响应数据(可选) | + +#### 3.2.3 示例:认证成功响应 + +**消息内容:** +- 消息 ID: `auth_response_1704067200000_123` +- 方法名: `auth` +- 响应码: `0` +- 响应消息: `认证成功` +- 响应数据: `{"success":true,"message":"认证成功"}` + +**二进制数据包结构:** +``` +7E // 魔术字 (0x7E) +01 // 版本号 (0x01) +02 // 消息类型 (RESPONSE) +00 00 00 A4 // 消息长度 (164字节) +00 22 // 消息 ID 长度 (34字节) +61 75 74 68 5F 72 65 73 70 6F // 消息 ID: "auth_response_1704067200000_123" +6E 73 65 5F 31 37 30 34 30 36 +37 32 30 30 30 30 30 5F 31 32 +33 +00 04 // 方法名长度 (4字节) +61 75 74 68 // 方法名: "auth" +00 00 00 00 // 响应码 (0 = 成功) +00 0C // 响应消息长度 (12字节) +E8 AE A4 E8 AF 81 E6 88 90 E5 // 响应消息: "认证成功" (UTF-8) +8A 9F +7B 22 73 75 63 63 65 73 73 22 // JSON响应数据 +3A 74 72 75 65 2C 22 6D 65 73 // {"success":true,"message":"认证成功"} +73 61 67 65 22 3A 22 E8 AE A4 +E8 AF 81 E6 88 90 E5 8A 9F 22 +7D +``` + +## 4. 编解码器标识 + +```java +public static final String TYPE = "TCP_BINARY"; +``` + +## 5. 协议优势 + +- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量 +- **解析高效**:直接二进制操作,减少字符串转换开销 +- **类型安全**:明确的消息类型和字段定义 +- **设计简洁**:去除冗余字段,协议更加精简高效 +- **版本控制**:内置版本号支持协议升级 + +## 6. 与 JSON 协议对比 + +| 特性 | 二进制协议 | JSON协议 | +|------|-------------|--------| +| 数据大小 | 小(节省30-50%) | 大 | +| 解析性能 | 高 | 中等 | +| 网络开销 | 低 | 高 | +| 可读性 | 差 | 优秀 | +| 调试难度 | 高 | 低 | +| 扩展性 | 良好 | 优秀 | + +**推荐场景**: +- ✅ **高频数据传输**:传感器数据实时上报 +- ✅ **带宽受限环境**:移动网络、卫星通信 +- ✅ **性能要求高**:需要低延迟、高吞吐的场景 +- ✅ **设备资源有限**:嵌入式设备、低功耗设备 +- ❌ **开发调试阶段**:调试困难,建议使用 JSON 协议 +- ❌ **快速原型开发**:开发效率低 diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md new file mode 100644 index 0000000..09ef50c --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/tcp-json-packet-examples.md @@ -0,0 +1,191 @@ +# TCP JSON 格式协议说明 + +## 1. 协议概述 + +TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点: + +- **标准化**:使用标准 JSON 格式,易于解析和处理 +- **可读性**:人类可读,便于调试和维护 +- **扩展性**:可以轻松添加新字段,向后兼容 +- **跨平台**:JSON 格式支持所有主流编程语言 +- **安全优化**:移除冗余的 deviceId 字段,提高安全性 + +## 2. 消息格式 + +### 2.1 基础消息结构 + +```json +{ + "id": "消息唯一标识", + "method": "消息方法", + "params": { + // 请求参数 + }, + "data": { + // 响应数据 + }, + "code": 响应码, + "msg": "响应消息", + "timestamp": 时间戳 +} +``` + +**⚠️ 重要说明**: +- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID +- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息 + +### 2.2 字段详细说明 + +| 字段名 | 类型 | 必填 | 用途 | 说明 | +|--------|------|------|------|------| +| id | String | 是 | 所有消息 | 消息唯一标识 | +| method | String | 是 | 所有消息 | 消息方法,如 `auth`、`thing.property.post` | +| params | Object | 否 | 请求消息 | 请求参数,具体内容根据method而定 | +| data | Object | 否 | 响应消息 | 响应数据,服务器返回的结果数据 | +| code | Integer | 否 | 响应消息 | 响应码,0=成功,其他=错误 | +| msg | String | 否 | 响应消息 | 响应提示信息 | +| timestamp | Long | 是 | 所有消息 | 时间戳(毫秒),编码时自动生成 | + +### 2.3 消息分类 + +#### 2.3.1 请求消息(上行) +- **特征**:包含 `params` 字段,不包含 `code`、`msg` 字段 +- **方向**:设备 → 服务器 +- **用途**:设备认证、数据上报、状态更新等 + +#### 2.3.2 响应消息(下行) +- **特征**:包含 `code`、`msg` 字段,可能包含 `data` 字段 +- **方向**:服务器 → 设备 +- **用途**:认证结果、指令响应、错误提示等 + +## 3. 消息示例 + +### 3.1 设备认证 (auth) + +#### 认证请求格式 +**消息方向**:设备 → 服务器 + +```json +{ + "id": "auth_1704067200000_123", + "method": "auth", + "params": { + "clientId": "device_001", + "username": "productKey_deviceName", + "password": "设备密码" + }, + "timestamp": 1704067200000 +} +``` + +**认证参数说明:** + +| 字段名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| clientId | String | 是 | 客户端唯一标识,用于连接管理 | +| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` | +| password | String | 是 | 设备密码,在设备管理平台配置 | + +#### 认证响应格式 +**消息方向**:服务器 → 设备 + +**认证成功响应:** +```json +{ + "id": "response_auth_1704067200000_123", + "method": "auth", + "data": { + "success": true, + "message": "认证成功" + }, + "code": 0, + "msg": "认证成功", + "timestamp": 1704067200001 +} +``` + +**认证失败响应:** +```json +{ + "id": "response_auth_1704067200000_123", + "method": "auth", + "data": { + "success": false, + "message": "认证失败:用户名或密码错误" + }, + "code": 401, + "msg": "认证失败", + "timestamp": 1704067200001 +} +``` + +### 3.2 属性数据上报 (thing.property.post) + +**消息方向**:设备 → 服务器 + +**示例:温度传感器数据上报** +```json +{ + "id": "property_1704067200000_456", + "method": "thing.property.post", + "params": { + "temperature": 25.5, + "humidity": 60.2, + "pressure": 1013.25, + "battery": 85, + "signal_strength": -65 + }, + "timestamp": 1704067200000 +} +``` + +### 3.3 设备状态更新 (thing.state.update) + +**消息方向**:设备 → 服务器 + +**示例:心跳请求** +```json +{ + "id": "heartbeat_1704067200000_321", + "method": "thing.state.update", + "params": { + "state": "online", + "uptime": 86400, + "memory_usage": 65.2, + "cpu_usage": 12.8 + }, + "timestamp": 1704067200000 +} +``` + +## 4. 编解码器标识 + +```java +public static final String TYPE = "TCP_JSON"; +``` + +## 5. 协议优势 + +- **开发效率高**:JSON 格式,开发和调试简单 +- **跨语言支持**:所有主流语言都支持 JSON +- **可读性优秀**:可以直接查看消息内容 +- **扩展性强**:可以轻松添加新字段 +- **安全性高**:移除 deviceId 字段,防止伪造攻击 + +## 6. 与二进制协议对比 + +| 特性 | JSON协议 | 二进制协议 | +|------|----------|------------| +| 开发难度 | 低 | 高 | +| 调试难度 | 低 | 高 | +| 可读性 | 优秀 | 差 | +| 数据大小 | 中等 | 小(节省30-50%) | +| 解析性能 | 中等 | 高 | +| 学习成本 | 低 | 高 | + +**推荐场景**: +- ✅ **开发调试阶段**:调试友好,开发效率高 +- ✅ **快速原型开发**:实现简单,快速迭代 +- ✅ **多语言集成**:广泛的语言支持 +- ❌ **高频数据传输**:建议使用二进制协议 +- ❌ **带宽受限环境**:建议使用二进制协议 \ No newline at end of file -- libgit2 0.21.4