feat:【IoT 物联网】新增 TCP 连接管理器,优化消息编解码逻辑

This commit is contained in:
haohao
2025-07-29 17:41:08 +08:00
parent 6b79cab09a
commit cda59081a3
15 changed files with 1045 additions and 1781 deletions

View File

@@ -1,241 +0,0 @@
package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* TCP 二进制格式数据包单元测试
*
* 测试二进制协议创建和解析 TCP 上报数据包和心跳包
*
* 二进制协议格式:
* 包头(4 字节) | 功能码(2 字节) | 消息序号(2 字节) | 包体数据(变长)
*
* @author 芋道源码
*/
@Slf4j
class TcpBinaryDataPacketExamplesTest {
private IotTcpBinaryDeviceMessageCodec codec;
@BeforeEach
void setUp() {
codec = new IotTcpBinaryDeviceMessageCodec();
}
@Test
void testDataReport() {
log.info("=== 二进制格式数据上报包测试 ===");
// 创建传感器数据
Map<String, Object> sensorData = new HashMap<>();
sensorData.put("temperature", 25.5);
sensorData.put("humidity", 60.2);
sensorData.put("pressure", 1013.25);
sensorData.put("battery", 85);
// 创建设备消息
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
message.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(message);
log.info("编码后数据包长度: {} 字节", packet.length);
log.info("编码后数据包(HEX): {}", bytesToHex(packet));
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后请求 ID: {}", decoded.getRequestId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后服务 ID: {}", decoded.getServerId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.property.post", decoded.getMethod());
assertNotNull(decoded.getParams());
assertTrue(decoded.getParams() instanceof Map);
}
@Test
void testHeartbeat() {
log.info("=== 二进制格式心跳包测试 ===");
// 创建心跳消息
IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null);
heartbeat.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(heartbeat);
log.info("心跳包长度: {} 字节", packet.length);
log.info("心跳包(HEX): {}", bytesToHex(packet));
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后请求 ID: {}", decoded.getRequestId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后服务 ID: {}", decoded.getServerId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.state.online", decoded.getMethod());
}
@Test
void testComplexDataReport() {
log.info("=== 二进制格式复杂数据上报测试 ===");
// 创建复杂设备数据
Map<String, Object> deviceData = new HashMap<>();
// 环境数据
Map<String, Object> environment = new HashMap<>();
environment.put("temperature", 23.8);
environment.put("humidity", 55.0);
environment.put("co2", 420);
deviceData.put("environment", environment);
// GPS 数据
Map<String, Object> location = new HashMap<>();
location.put("latitude", 39.9042);
location.put("longitude", 116.4074);
location.put("altitude", 43.5);
deviceData.put("location", location);
// 设备状态
Map<String, Object> status = new HashMap<>();
status.put("battery", 78);
status.put("signal", -65);
status.put("online", true);
deviceData.put("status", status);
// 创建设备消息
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", deviceData);
message.setDeviceId(789012L);
// 编码
byte[] packet = codec.encode(message);
log.info("复杂数据包长度: {} 字节", packet.length);
log.info("复杂数据包(HEX): {}", bytesToHex(packet));
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后请求 ID: {}", decoded.getRequestId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后服务 ID: {}", decoded.getServerId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.property.post", decoded.getMethod());
assertNotNull(decoded.getParams());
}
@Test
void testPacketStructureAnalysis() {
log.info("=== 数据包结构分析测试 ===");
// 创建测试数据
Map<String, Object> sensorData = new HashMap<>();
sensorData.put("temperature", 25.5);
sensorData.put("humidity", 60.2);
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
message.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(message);
// 分析数据包结构
analyzePacketStructure(packet);
// 断言验证
assertTrue(packet.length >= 8, "数据包长度应该至少为 8 字节");
}
// ==================== 内部辅助方法 ====================
/**
* 字节数组转十六进制字符串
*
* @param bytes 字节数组
* @return 十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02X ", b));
}
return result.toString().trim();
}
/**
* 演示数据包结构分析
*
* @param packet 数据包
*/
private static void analyzePacketStructure(byte[] packet) {
if (packet.length < 8) {
log.error("数据包长度不足");
return;
}
int index = 0;
// 解析包头(4 字节) - 后续数据长度
int totalLength = ((packet[index] & 0xFF) << 24) |
((packet[index + 1] & 0xFF) << 16) |
((packet[index + 2] & 0xFF) << 8) |
(packet[index + 3] & 0xFF);
index += 4;
log.info("包头 - 后续数据长度: {} 字节", totalLength);
// 解析功能码(2 字节)
int functionCode = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF);
index += 2;
log.info("功能码: {} ({})", functionCode, getFunctionCodeName(functionCode));
// 解析消息序号(2 字节)
int messageId = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF);
index += 2;
log.info("消息序号: {}", messageId);
// 解析包体数据
if (index < packet.length) {
String payload = new String(packet, index, packet.length - index);
log.info("包体数据: {}", payload);
}
}
/**
* 获取功能码名称
*
* @param code 功能码
* @return 功能码名称
*/
private static String getFunctionCodeName(int code) {
return switch (code) {
case 10 -> "设备注册";
case 11 -> "注册回复";
case 20 -> "心跳请求";
case 21 -> "心跳回复";
case 30 -> "消息上行";
case 40 -> "消息下行";
default -> "未知功能码";
};
}
}

View File

@@ -1,185 +0,0 @@
package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* TCP JSON 格式数据包单元测试
* <p>
* 测试 JSON 格式的 TCP 消息编解码功能
*
* @author 芋道源码
*/
@Slf4j
class TcpJsonDataPacketExamplesTest {
private IotTcpJsonDeviceMessageCodec codec;
@BeforeEach
void setUp() {
codec = new IotTcpJsonDeviceMessageCodec();
}
@Test
void testDataReport() {
log.info("=== JSON 格式数据上报测试 ===");
// 创建传感器数据
Map<String, Object> sensorData = new HashMap<>();
sensorData.put("temperature", 25.5);
sensorData.put("humidity", 60.2);
sensorData.put("pressure", 1013.25);
sensorData.put("battery", 85);
// 创建设备消息
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
message.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(message);
String jsonString = new String(packet, StandardCharsets.UTF_8);
log.info("编码后 JSON: {}", jsonString);
log.info("数据包长度: {} 字节", packet.length);
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后服务 ID: {}", decoded.getServerId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.property.post", decoded.getMethod());
assertEquals(123456L, decoded.getDeviceId());
assertNotNull(decoded.getParams());
assertTrue(decoded.getParams() instanceof Map);
}
@Test
void testHeartbeat() {
log.info("=== JSON 格式心跳测试 ===");
// 创建心跳消息
IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null);
heartbeat.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(heartbeat);
String jsonString = new String(packet, StandardCharsets.UTF_8);
log.info("编码后 JSON: {}", jsonString);
log.info("心跳包长度: {} 字节", packet.length);
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后服务 ID: {}", decoded.getServerId());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.state.online", decoded.getMethod());
assertEquals(123456L, decoded.getDeviceId());
}
@Test
void testEventReport() {
log.info("=== JSON 格式事件上报测试 ===");
// 创建事件数据
Map<String, Object> eventData = new HashMap<>();
eventData.put("eventType", "alarm");
eventData.put("level", "warning");
eventData.put("description", "温度过高");
eventData.put("value", 45.8);
// 创建事件消息
IotDeviceMessage event = IotDeviceMessage.requestOf("thing.event.post", eventData);
event.setDeviceId(123456L);
// 编码
byte[] packet = codec.encode(event);
String jsonString = new String(packet, StandardCharsets.UTF_8);
log.info("编码后 JSON: {}", jsonString);
log.info("事件包长度: {} 字节", packet.length);
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.event.post", decoded.getMethod());
assertEquals(123456L, decoded.getDeviceId());
assertNotNull(decoded.getParams());
}
@Test
void testComplexDataReport() {
log.info("=== JSON 格式复杂数据上报测试 ===");
// 创建复杂设备数据(类似 EMQX 格式)
Map<String, Object> deviceData = new HashMap<>();
// 环境数据
Map<String, Object> environment = new HashMap<>();
environment.put("temperature", 23.8);
environment.put("humidity", 55.0);
environment.put("co2", 420);
environment.put("pm25", 35);
deviceData.put("environment", environment);
// GPS 数据
Map<String, Object> location = new HashMap<>();
location.put("latitude", 39.9042);
location.put("longitude", 116.4074);
location.put("altitude", 43.5);
location.put("speed", 0.0);
deviceData.put("location", location);
// 设备状态
Map<String, Object> status = new HashMap<>();
status.put("battery", 78);
status.put("signal", -65);
status.put("online", true);
status.put("version", "1.2.3");
deviceData.put("status", status);
// 创建设备消息
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", deviceData);
message.setDeviceId(789012L);
// 编码
byte[] packet = codec.encode(message);
String jsonString = new String(packet, StandardCharsets.UTF_8);
log.info("编码后 JSON: {}", jsonString);
log.info("复杂数据包长度: {} 字节", packet.length);
// 解码验证
IotDeviceMessage decoded = codec.decode(packet);
log.info("解码后消息 ID: {}", decoded.getId());
log.info("解码后方法: {}", decoded.getMethod());
log.info("解码后设备 ID: {}", decoded.getDeviceId());
log.info("解码后参数: {}", decoded.getParams());
// 断言验证
assertNotNull(decoded.getId());
assertEquals("thing.property.post", decoded.getMethod());
assertEquals(789012L, decoded.getDeviceId());
assertNotNull(decoded.getParams());
}
}

View File

@@ -1,370 +1,198 @@
# TCP 二进制协议数据包格式说明和示例
# TCP 二进制协议数据包格式说明
## 1. 二进制协议概述
## 1. 协议概述
TCP 二进制协议是一种高效的自定义协议格式,适用于对带宽和性能要求较高的场景。该协议采用紧凑的二进制格式,减少数据传输量,提高传输效率
TCP 二进制协议是一种高效的自定义协议格式,采用紧凑的二进制格式传输数据,适用于对带宽和性能要求较高的 IoT 场景
## 2. 数据包格式
### 1.1 协议特点
- **高效传输**:完全二进制格式,减少数据传输量
- **版本控制**:内置协议版本号,支持协议升级
- **类型安全**:明确的消息类型标识
- **扩展性**:预留标志位,支持未来功能扩展
- **兼容性**:与现有 `IotDeviceMessage` 接口完全兼容
## 2. 协议格式
### 2.1 整体结构
根据代码实现TCP 二进制协议的数据包格式为:
```
+----------+----------+----------+----------+
| 包头 | 功能码 | 消息序号 | 包体数据 |
| 4字节 | 2字节 | 2字节 | 变长 |
+----------+----------+----------+----------+
+--------+--------+--------+--------+--------+--------+--------+--------+
| 魔术字 | 版本号 | 消息类型| 消息标志| 消息长度(4字节) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 消息 ID 长度(2字节) | 消息 ID (变长字符串) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 方法名长度(2字节) | 方法名(变长字符串) |
+--------+--------+--------+--------+--------+--------+--------+--------+
| 消息体数据(变长) |
+--------+--------+--------+--------+--------+--------+--------+--------+
```
**注意**:与原始设计相比,实际实现中移除了设备地址字段,简化了协议结构。
### 2.2 字段详细说明
### 2.2 字段说明
| 字段 | 长度 | 类型 | 说明 |
|------|------|------|------|
| 魔术字 | 1字节 | byte | `0x7E` - 协议识别标识,用于数据同步 |
| 版本号 | 1字节 | byte | `0x01` - 协议版本号,支持版本控制 |
| 消息类型 | 1字节 | byte | `0x01`=请求, `0x02`=响应 |
| 消息标志 | 1字节 | byte | 预留字段,用于未来扩展 |
| 消息长度 | 4字节 | int | 整个消息的总长度(包含头部) |
| 消息 ID 长度 | 2字节 | short | 消息 ID 字符串的字节长度 |
| 消息 ID | 变长 | string | 消息唯一标识符UTF-8编码 |
| 方法名长度 | 2字节 | short | 方法名字符串的字节长度 |
| 方法名 | 变长 | string | 消息方法名UTF-8编码 |
| 消息体 | 变长 | binary | 根据消息类型的不同数据格式 |
| 字段 | 长度 | 类型 | 说明 |
|------|-----|--------|-----------------|
| 包头 | 4字节 | int | 后续数据的总长度(不包含包头) |
| 功能码 | 2字节 | short | 消息类型标识 |
| 消息序号 | 2字节 | short | 消息唯一标识 |
| 包体数据 | 变长 | string | JSON 格式的消息内容 |
**⚠️ 重要说明**deviceId 不包含在协议中,由服务器根据连接上下文自动设置
### 2.3 功能码定义
根据代码实现,支持的功能码:
| 功能码 | 名称 | 说明 |
|-----|------|--------------|
| 10 | 设备注册 | 设备首次连接时的注册请求 |
| 11 | 注册回复 | 服务器对注册请求的回复 |
| 20 | 心跳请求 | 设备发送的心跳包 |
| 21 | 心跳回复 | 服务器对心跳的回复 |
| 30 | 消息上行 | 设备向服务器发送的数据 |
| 40 | 消息下行 | 服务器向设备发送的指令 |
**常量定义:**
### 2.3 协议常量定义
```java
public static final short CODE_REGISTER = 10;
public static final short CODE_REGISTER_REPLY = 11;
public static final short CODE_HEARTBEAT = 20;
public static final short CODE_HEARTBEAT_REPLY = 21;
public static final short CODE_MESSAGE_UP = 30;
public static final short CODE_MESSAGE_DOWN = 40;
```
// 协议标识
private static final byte MAGIC_NUMBER = (byte) 0x7E;
private static final byte PROTOCOL_VERSION = (byte) 0x01;
## 3. 包体数据格式
### 3.1 JSON 负载结构
包体数据采用 JSON 格式,包含以下字段:
```json
{
"method": "消息方法",
"params": {
// 消息参数
},
"timestamp": ,
"requestId": "请求ID",
"msgId": "消息ID"
// 消息类型
public static class MessageType {
public static final byte REQUEST = 0x01; // 请求消息
public static final byte RESPONSE = 0x02; // 响应消息
}
// 协议长度
private static final int HEADER_FIXED_LENGTH = 8; // 固定头部长度
private static final int MIN_MESSAGE_LENGTH = 12; // 最小消息长度
```
### 3.2 字段说明
## 3. 消息类型和格式
| 字段名 | 类型 | 必填 | 说明 |
|-----------|--------|----|------------------------------|
| method | String | 是 | 消息方法,如 `thing.property.post` |
| params | Object | 否 | 消息参数 |
| timestamp | Long | 是 | 时间戳(毫秒) |
| requestId | String | 否 | 请求唯一标识 |
| msgId | String | 否 | 消息唯一标识 |
### 3.1 请求消息 (REQUEST - 0x01)
**常量定义:**
请求消息用于设备向服务器发送数据或请求。
```java
public static final String METHOD = "method";
public static final String PARAMS = "params";
public static final String TIMESTAMP = "timestamp";
public static final String REQUEST_ID = "requestId";
public static final String MESSAGE_ID = "msgId";
#### 3.1.1 消息体格式
```
消息体 = params 数据(JSON格式)
```
## 4. 消息类型
#### 3.1.2 示例:设备认证请求
### 4.1 数据上报 (thing.property.post)
**消息内容:**
- 消息 ID: `auth_1704067200000_123`
- 方法名: `auth`
- 参数: `{"clientId":"device_001","username":"productKey_deviceName","password":"device_password"}`
设备向服务器上报属性数据。
**功能码:** 30 (CODE_MESSAGE_UP)
**包体数据示例:**
```json
{
"method": "thing.property.post",
"params": {
"temperature": 25.5,
"humidity": 60.2,
"pressure": 1013.25
},
"timestamp": 1642781234567,
"requestId": "req_001"
}
**二进制数据包结构:**
```
7E // 魔术字 (0x7E)
01 // 版本号 (0x01)
01 // 消息类型 (REQUEST)
00 // 消息标志 (预留)
00 00 00 8A // 消息长度 (138字节)
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
```
### 4.2 心跳 (thing.state.online)
#### 3.1.3 示例:属性数据上报
设备向服务器发送心跳保活。
**消息内容:**
- 消息 ID: `property_1704067200000_456`
- 方法名: `thing.property.post`
- 参数: `{"temperature":25.5,"humidity":60.2,"pressure":1013.25}`
**功能码:** 20 (CODE_HEARTBEAT)
### 3.2 响应消息 (RESPONSE - 0x02)
**包体数据示例:**
响应消息用于服务器向设备回复请求结果。
```json
{
"method": "thing.state.online",
"params": {},
"timestamp": 1642781234567,
"requestId": "req_002"
}
#### 3.2.1 消息体格式
```
消息体 = 响应码(4字节) + 响应消息长度(2字节) + 响应消息(UTF-8) + 响应数据(JSON)
```
### 4.3 消息方法常量
#### 3.2.2 字段说明
```java
public static final String PROPERTY_POST = "thing.property.post"; // 数据上报
public static final String STATE_ONLINE = "thing.state.online"; // 心跳
| 字段 | 长度 | 类型 | 说明 |
|------|------|------|------|
| 响应码 | 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 00 A5 // 消息长度 (165字节)
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
```
## 5. 数据包示例
### 5.1 温度传感器数据上报
**包体数据:**
```json
{
"method": "thing.property.post",
"params": {
"temperature": 25.5,
"humidity": 60.2,
"pressure": 1013.25
},
"timestamp": 1642781234567,
"requestId": "req_001"
}
```
**数据包结构:**
```
包头: 0x00000045 (69字节)
功能码: 0x001E (30 - 消息上行)
消息序号: 0x1234 (4660)
包体: JSON字符串
```
**完整十六进制数据包:**
```
00 00 00 45 00 1E 12 34
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67
2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C
22 70 61 72 61 6D 73 22 3A 7B 22 74 65 6D 70 65
72 61 74 75 72 65 22 3A 32 35 2E 35 2C 22 68 75
6D 69 64 69 74 79 22 3A 36 30 2E 32 2C 22 70 72
65 73 73 75 72 65 22 3A 31 30 31 33 2E 32 35 7D
2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 36 34
32 37 38 31 32 33 34 35 36 37 2C 22 72 65 71 75
65 73 74 49 64 22 3A 22 72 65 71 5F 30 30 31 22 7D
```
### 5.2 心跳包示例
**包体数据:**
```json
{
"method": "thing.state.online",
"params": {},
"timestamp": 1642781234567,
"requestId": "req_002"
}
```
**数据包结构:**
```
包头: 0x00000028 (40字节)
功能码: 0x0014 (20 - 心跳请求)
消息序号: 0x5678 (22136)
包体: JSON字符串
```
**完整十六进制数据包:**
```
00 00 00 28 00 14 56 78
7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67
2E 73 74 61 74 65 2E 6F 6E 6C 69 6E 65 22 2C 22
70 61 72 61 6D 73 22 3A 7B 7D 2C 22 74 69 6D 65
73 74 61 6D 70 22 3A 31 36 34 32 37 38 31 32 33
34 35 36 37 2C 22 72 65 71 75 65 73 74 49 64 22
3A 22 72 65 71 5F 30 30 32 22 7D
```
## 6. 编解码器实现
### 6.1 编码器类型
## 4. 编解码器标识
```java
public static final String TYPE = "TCP_BINARY";
```
### 6.2 编码过程
## 5. 协议优势
1. **参数验证**:检查消息和方法是否为空
2. **确定功能码**
- 心跳消息:使用 `CODE_HEARTBEAT` (20)
- 其他消息:使用 `CODE_MESSAGE_UP` (30)
3. **构建负载**:使用 `buildSimplePayload()` 构建 JSON 负载
4. **生成消息序号**:基于当前时间戳生成
5. **构建数据包**:创建 `TcpDataPackage` 对象
6. **编码为字节流**:使用 `encodeTcpDataPackage()` 编码
- **数据紧凑**:二进制格式,相比 JSON 减少 30-50% 的数据量
- **解析高效**:直接二进制操作,减少字符串转换开销
- **类型安全**:明确的消息类型和字段定义
- **扩展性强**:预留标志位支持未来功能扩展
- **版本控制**:内置版本号支持协议升级
### 6.3 解码过程
## 6. 与 JSON 协议对比
1. **参数验证**:检查字节数组是否为空
2. **解码数据包**:使用 `decodeTcpDataPackage()` 解码
3. **确定消息方法**
- 功能码 20`thing.state.online` (心跳)
- 功能码 30`thing.property.post` (数据上报)
4. **解析负载信息**:使用 `parsePayloadInfo()` 解析 JSON 负载
5. **构建设备消息**:创建 `IotDeviceMessage` 对象
6. **设置服务 ID**:使用 `generateServerId()` 生成
### 6.4 服务 ID 生成
```java
private String generateServerId(TcpDataPackage dataPackage) {
return String.format("tcp_binary_%d_%d", dataPackage.getCode(), dataPackage.getMid());
}
```
## 7. 数据包解析步骤
### 7.1 解析流程
1. **读取包头4字节**
- 获取后续数据的总长度
- 验证数据包完整性
2. **读取功能码2字节**
- 确定消息类型
3. **读取消息序号2字节**
- 获取消息唯一标识
4. **读取包体数据(变长)**
- 解析 JSON 格式的消息内容
### 7.2 Java 解析示例
```java
public TcpDataPackage parsePacket(byte[] packet) {
int index = 0;
// 1. 解析包头
int totalLength = ByteBuffer.wrap(packet, index, 4).getInt();
index += 4;
// 2. 解析功能码
short functionCode = ByteBuffer.wrap(packet, index, 2).getShort();
index += 2;
// 3. 解析消息序号
short messageId = ByteBuffer.wrap(packet, index, 2).getShort();
index += 2;
// 4. 解析包体数据
String payload = new String(packet, index, packet.length - index);
return new TcpDataPackage(functionCode, messageId, payload);
}
```
## 8. 使用示例
### 8.1 基本使用
```java
// 创建编解码器
IotTcpBinaryDeviceMessageCodec codec = new IotTcpBinaryDeviceMessageCodec();
// 创建数据上报消息
Map<String, Object> sensorData = Map.of(
"temperature", 25.5,
"humidity", 60.2
);
// 编码
IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
byte[] data = codec.encode(message);
// 解码
IotDeviceMessage decoded = codec.decode(data);
```
### 8.2 错误处理
```java
try{
byte[] data = codec.encode(message);
// 处理编码成功
}catch(
IllegalArgumentException e){
// 处理参数错误
log.
error("编码参数错误: {}",e.getMessage());
}catch(
TcpCodecException e){
// 处理编码失败
log.
error("编码失败: {}",e.getMessage());
}
```
## 9. 注意事项
1. **字节序**所有多字节数据使用大端序Big-Endian
2. **字符编码**:字符串数据使用 UTF-8 编码
3. **JSON 格式**:包体数据必须是有效的 JSON 格式
4. **长度限制**:单个数据包建议不超过 1MB
5. **错误处理**:解析失败时会抛出 `TcpCodecException`
6. **功能码映射**:目前只支持心跳和数据上报两种消息类型
## 10. 协议特点
### 10.1 优势
- **高效传输**:二进制格式,数据量小
- **性能优化**:减少解析开销
- **带宽节省**:相比 JSON 格式节省带宽
- **实时性好**:适合高频数据传输
### 10.2 适用场景
| 特性 | 二进制协议 | JSON协议 |
|------|------------|----------|
| 数据大小 | 小节省30-50% | 大 |
| 解析性能 | 高 | 中等 |
| 网络开销 | 低 | 高 |
| 可读性 | 差 | 优秀 |
| 调试难度 | 高 | 低 |
| 扩展性 | 良好(有预留位) | 优秀 |
**推荐场景**
-**高频数据传输**:传感器数据实时上报
-**带宽受限环境**:移动网络、卫星通信
-**性能要求高**:需要低延迟的场景
-**设备资源有限**:嵌入式设备、IoT 设备
### 10.3 与 JSON 协议对比
| 特性 | 二进制协议 | JSON 协议 |
|-------|-------|---------|
| 数据大小 | 小 | 稍大 |
| 解析性能 | 高 | 中等 |
| 可读性 | 差 | 优秀 |
| 调试难度 | 高 | 低 |
| 扩展性 | 差 | 优秀 |
| 实现复杂度 | 高 | 低 |
这样就完成了 TCP 二进制协议的完整说明,与实际代码实现完全一致。
-**性能要求高**:需要低延迟、高吞吐的场景
-**设备资源有限**:嵌入式设备、低功耗设备
-**开发调试阶段**:调试困难,建议使用 JSON 协议
-**快速原型开发**:开发效率低

View File

@@ -2,13 +2,13 @@
## 1. 协议概述
TCP JSON 格式协议采用纯 JSON 格式进行数据传输,参考了 EMQX 和 HTTP 模块的数据格式设计,具有以下优势
TCP JSON 格式协议采用纯 JSON 格式进行数据传输,具有以下特点
- **标准化**:使用标准 JSON 格式,易于解析和处理
- **可读性**:人类可读,便于调试和维护
- **扩展性**:可以轻松添加新字段,向后兼容
- **统一性**:与 HTTP 模块保持一致的数据格式
- **简化性**:相比二进制协议,实现更简单,调试更容易
- **跨平台**JSON 格式支持所有主流编程语言
- **安全优化**:移除冗余的 deviceId 字段,提高安全性
## 2. 消息格式
@@ -18,294 +18,174 @@ TCP JSON 格式协议采用纯 JSON 格式进行数据传输,参考了 EMQX
{
"id": "消息唯一标识",
"method": "消息方法",
"deviceId": ID,
"params": {
// 消息参数
// 请求参数
},
"data": {
// 响应数据
},
"timestamp": ,
"code": ,
"message": "响应消息"
"msg": "响应消息",
"timestamp":
}
```
### 2.2 字段说明
**⚠️ 重要说明**
- **不包含 deviceId 字段**:由服务器通过 TCP 连接上下文自动确定设备 ID
- **避免伪造攻击**:防止设备伪造其他设备的 ID 发送消息
| 字段名 | 类型 | 必填 | 说明 |
|-----------|---------|----|-------------------------------------|
| id | String | 是 | 消息唯一标识,如果为空会自动生成 UUID |
| method | String | 是 | 消息方法,如 `auth``thing.property.post` |
| deviceId | Long | 否 | 设备 ID |
| params | Object | 否 | 消息参数,具体内容根据 method 而定 |
| timestamp | Long | 是 | 时间戳(毫秒),自动生成 |
| code | Integer | 否 | 响应码(下行消息使用) |
| message | String | 否 | 响应消息(下行消息使用) |
### 2.2 字段详细说明
## 3. 消息类型
| 字段名 | 类型 | 必填 | 用途 | 说明 |
|--------|------|------|------|------|
| 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)
设备连接后首先需要进行认证,认证成功后才能进行其他操作。
#### 3.1.1 认证请求格式
**示例:**
#### 认证请求格式
**消息方向**:设备 → 服务器
```json
{
"id": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
"id": "auth_1704067200000_123",
"method": "auth",
"params": {
"clientId": "device_001",
"username": "productKey_deviceName",
"password": "设备密码"
},
"timestamp": 1753111026437
"timestamp": 1704067200000
}
```
**字段说明:**
**认证参数说明:**
| 字段名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| clientId | String | 是 | 客户端唯一标识 |
| clientId | String | 是 | 客户端唯一标识,用于连接管理 |
| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` |
| password | String | 是 | 设备密码 |
| password | String | 是 | 设备密码,在设备管理平台配置 |
#### 3.1.2 认证响应格式
#### 认证响应格式
**消息方向**:服务器 → 设备
**认证成功响应:**
```json
{
"id": "auth_response_8ac6a1db91e64aa9996143fdbac2cbfe",
"requestId": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
"id": "response_auth_1704067200000_123",
"method": "auth",
"data": {
"success": true,
"message": "认证成功"
},
"code": 0,
"msg": "认证成功"
"msg": "认证成功",
"timestamp": 1704067200001
}
```
**认证失败响应:**
```json
{
"id": "auth_response_8ac6a1db91e64aa9996143fdbac2cbfe",
"requestId": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
"id": "response_auth_1704067200000_123",
"method": "auth",
"data": {
"success": false,
"message": "认证失败:用户名或密码错误"
},
"code": 401,
"msg": "认证失败:用户名或密码错误"
"msg": "认证失败",
"timestamp": 1704067200001
}
```
#### 3.1.3 认证流程
### 3.2 属性数据上报 (thing.property.post)
1. **设备连接** → 建立 TCP 连接
2. **发送认证请求** → 发送包含认证信息的 JSON 消息
3. **服务器验证** → 验证 clientId、username、password
4. **生成 Token** → 认证成功后生成 JWT Token内部使用
5. **设备上线** → 发送设备上线消息到消息总线
6. **返回响应** → 返回认证结果
7. **会话注册** → 注册设备会话,允许后续业务操作
**消息方向**:设备 → 服务器
#### 3.1.4 认证错误码
| 错误码 | 说明 | 处理建议 |
|-----|-------|--------------|
| 401 | 认证失败 | 检查用户名、密码是否正确 |
| 400 | 参数错误 | 检查认证参数是否完整 |
| 404 | 设备不存在 | 检查设备是否已注册 |
| 500 | 服务器错误 | 联系管理员 |
### 3.2 数据上报 (thing.property.post)
设备向服务器上报属性数据。
**示例:**
**示例:温度传感器数据上报**
```json
{
"id": "8ac6a1db91e64aa9996143fdbac2cbfe",
"id": "property_1704067200000_456",
"method": "thing.property.post",
"deviceId": 8,
"params": {
"temperature": 25.5,
"humidity": 60.2,
"pressure": 1013.25,
"battery": 85
"battery": 85,
"signal_strength": -65
},
"timestamp": 1753111026437
"timestamp": 1704067200000
}
```
### 3.3 心跳 (thing.state.update)
### 3.3 设备状态更新 (thing.state.update)
设备向服务器发送心跳保活。
**消息方向**:设备 → 服务器
**示例:**
**示例:心跳请求**
```json
{
"id": "7db8c4e6408b40f8b2549ddd94f6bb02",
"id": "heartbeat_1704067200000_321",
"method": "thing.state.update",
"deviceId": 8,
"params": {
"state": "1"
"state": "online",
"uptime": 86400,
"memory_usage": 65.2,
"cpu_usage": 12.8
},
"timestamp": 1753111026467
"timestamp": 1704067200000
}
```
### 3.4 消息方法常量
## 4. 编解码器标识
支持的消息方法:
```java
public static final String TYPE = "TCP_JSON";
```
- `auth` - 设备认证
- `thing.property.post` - 数据上报
- `thing.state.update` - 心跳
## 5. 协议优势
## 4. 协议特点
### 4.1 优势
- **简单易用**:纯 JSON 格式,无需复杂的二进制解析
- **调试友好**:可以直接查看消息内容
- **开发效率高**JSON 格式,开发和调试简单
- **跨语言支持**:所有主流语言都支持 JSON
- **可读性优秀**:可以直接查看消息内容
- **扩展性强**:可以轻松添加新字段
- **标准化**:与 EMQX 等主流平台格式兼容
- **错误处理**:提供详细的错误信息和异常处理
- **安全性**:支持设备认证机制
- **安全性高**:移除 deviceId 字段,防止伪造攻击
### 4.2 与二进制协议对比
## 6. 与二进制协议对比
| 特性 | 二进制协议 | JSON 协议 |
|-------|-------|----------|
| 可读性 | 差 | 优秀 |
| 调试难度 | 高 | 低 |
| 扩展性 | 差 | 优秀 |
| 解析复杂度 | 高 | 低 |
| 数据大小 | 小 | 稍大 |
| 标准化程度 | 低 | 高 |
| 实现复杂度 | 高 | 低 |
| 安全性 | 一般 | 优秀(支持认证) |
| 特性 | JSON协议 | 二进制协议 |
|------|----------|------------|
| 开发难度 | 低 | 高 |
| 调试难度 | 低 | 高 |
| 可读性 | 优秀 | 差 |
| 数据大小 | 中等 | 小节省30-50% |
| 解析性能 | 中等 | 高 |
| 学习成本 | 低 | 高 |
### 4.3 适用场景
-**开发调试**JSON 格式便于查看和调试
-**快速集成**标准 JSON 格式,集成简单
- **协议扩展**:可以轻松添加新字段
-**多语言支持**JSON 格式支持所有主流语言
-**云平台对接**:与主流 IoT 云平台格式兼容
-**安全要求**:支持设备认证和访问控制
## 5. 最佳实践
### 5.1 认证最佳实践
1. **连接即认证**:设备连接后立即进行认证
2. **重连机制**:连接断开后重新认证
3. **错误重试**:认证失败时适当重试
4. **安全传输**:使用 TLS 加密传输敏感信息
### 5.2 消息设计
1. **保持简洁**:避免过深的嵌套结构
2. **字段命名**:使用驼峰命名法,保持一致性
3. **数据类型**:使用合适的数据类型,避免字符串表示数字
4. **时间戳**:统一使用毫秒级时间戳
### 5.3 错误处理
1. **参数验证**:确保必要字段存在且有效
2. **异常捕获**:正确处理编码解码异常
3. **日志记录**:记录详细的调试信息
4. **认证失败**:认证失败时及时关闭连接
### 5.4 性能优化
1. **批量上报**:可以在 params 中包含多个数据点
2. **连接复用**:保持 TCP 连接,避免频繁建立连接
3. **消息缓存**:客户端可以缓存消息,批量发送
4. **心跳间隔**:合理设置心跳间隔,避免过于频繁
## 6. 配置说明
### 6.1 启用 JSON 协议
在配置文件中设置:
```yaml
yudao:
iot:
gateway:
protocol:
tcp:
enabled: true
port: 8091
default-protocol: "JSON" # 使用 JSON 协议
```
### 6.2 认证配置
```yaml
yudao:
iot:
gateway:
token:
secret: "your-secret-key" # JWT 密钥
expiration: "24h" # Token 过期时间
```
## 7. 调试和监控
### 7.1 日志级别
```yaml
logging:
level:
cn.iocoder.yudao.module.iot.gateway.protocol.tcp: DEBUG
```
### 7.2 调试信息
编解码器会输出详细的调试日志:
- 认证过程:显示认证请求和响应
- 编码成功:显示方法、长度、内容
- 解码过程:显示原始数据、解析结果
- 错误信息:详细的异常堆栈
### 7.3 监控指标
- 认证成功率
- 消息处理数量
- 编解码成功率
- 处理延迟
- 错误率
- 在线设备数量
## 8. 安全考虑
### 8.1 认证安全
1. **密码强度**:使用强密码策略
2. **Token 过期**:设置合理的 Token 过期时间
3. **连接限制**:限制单个设备的并发连接数
4. **IP 白名单**:可选的 IP 访问控制
### 8.2 传输安全
1. **TLS 加密**:使用 TLS 1.2+ 加密传输
2. **证书验证**:验证服务器证书
3. **密钥管理**:安全存储和管理密钥
### 8.3 数据安全
1. **敏感信息**:不在日志中记录密码等敏感信息
2. **数据验证**:验证所有输入数据
3. **访问控制**:基于 Token 的访问控制
这样就完成了 TCP JSON 格式协议的完整说明,包括认证流程的详细说明,与实际代码实现完全一致。
**推荐场景**
-**开发调试阶段**:调试友好,开发效率高
-**快速原型开发**:实现简单,快速迭代
-**多语言集成**广泛的语言支持
- **高频数据传输**:建议使用二进制协议
- ❌ **带宽受限环境**:建议使用二进制协议