Commit 8449ccbb7d7d13b51a1665c8b49d3d46ca551a9b
1 parent
00bd4293
feat:【IoT 物联网】优化 TCP 二进制协议的消息编码和解码逻辑
Showing
4 changed files
with
267 additions
and
451 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java
| @@ -4,10 +4,9 @@ import cn.hutool.core.lang.Assert; | @@ -4,10 +4,9 @@ import cn.hutool.core.lang.Assert; | ||
| 4 | import cn.hutool.core.util.StrUtil; | 4 | import cn.hutool.core.util.StrUtil; |
| 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | 5 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | ||
| 7 | import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | 8 | import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; |
| 8 | import io.vertx.core.buffer.Buffer; | 9 | import io.vertx.core.buffer.Buffer; |
| 9 | -import lombok.AllArgsConstructor; | ||
| 10 | -import lombok.Data; | ||
| 11 | import lombok.extern.slf4j.Slf4j; | 10 | import lombok.extern.slf4j.Slf4j; |
| 12 | import org.springframework.stereotype.Component; | 11 | import org.springframework.stereotype.Component; |
| 13 | 12 | ||
| @@ -19,9 +18,9 @@ import java.nio.charset.StandardCharsets; | @@ -19,9 +18,9 @@ import java.nio.charset.StandardCharsets; | ||
| 19 | * 二进制协议格式(所有数值使用大端序): | 18 | * 二进制协议格式(所有数值使用大端序): |
| 20 | * | 19 | * |
| 21 | * <pre> | 20 | * <pre> |
| 22 | - * +--------+--------+--------+--------+--------+--------+--------+--------+ | ||
| 23 | - * | 魔术字 | 版本号 | 消息类型| 消息标志| 消息长度(4 字节) | | ||
| 24 | - * +--------+--------+--------+--------+--------+--------+--------+--------+ | 21 | + * +--------+--------+--------+---------------------------+--------+--------+ |
| 22 | + * | 魔术字 | 版本号 | 消息类型| 消息长度(4 字节) | | ||
| 23 | + * +--------+--------+--------+---------------------------+--------+--------+ | ||
| 25 | * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) | | 24 | * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) | |
| 26 | * +--------+--------+--------+--------+--------+--------+--------+--------+ | 25 | * +--------+--------+--------+--------+--------+--------+--------+--------+ |
| 27 | * | 方法名长度(2 字节) | 方法名(变长字符串) | | 26 | * | 方法名长度(2 字节) | 方法名(变长字符串) | |
| @@ -44,8 +43,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -44,8 +43,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 44 | 43 | ||
| 45 | public static final String TYPE = "TCP_BINARY"; | 44 | public static final String TYPE = "TCP_BINARY"; |
| 46 | 45 | ||
| 47 | - // ==================== 协议常量 ==================== | ||
| 48 | - | ||
| 49 | /** | 46 | /** |
| 50 | * 协议魔术字,用于协议识别 | 47 | * 协议魔术字,用于协议识别 |
| 51 | */ | 48 | */ |
| @@ -56,27 +53,20 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -56,27 +53,20 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 56 | */ | 53 | */ |
| 57 | private static final byte PROTOCOL_VERSION = (byte) 0x01; | 54 | private static final byte PROTOCOL_VERSION = (byte) 0x01; |
| 58 | 55 | ||
| 59 | - // TODO @haohao:这个要不直接静态枚举,不用 MessageType | ||
| 60 | /** | 56 | /** |
| 61 | - * 消息类型常量 | 57 | + * 请求消息类型 |
| 62 | */ | 58 | */ |
| 63 | - public static class MessageType { | 59 | + private static final byte REQUEST = (byte) 0x01; |
| 64 | 60 | ||
| 65 | - /** | ||
| 66 | - * 请求消息 | ||
| 67 | - */ | ||
| 68 | - public static final byte REQUEST = 0x01; | ||
| 69 | - /** | ||
| 70 | - * 响应消息 | ||
| 71 | - */ | ||
| 72 | - public static final byte RESPONSE = 0x02; | ||
| 73 | - | ||
| 74 | - } | 61 | + /** |
| 62 | + * 响应消息类型 | ||
| 63 | + */ | ||
| 64 | + private static final byte RESPONSE = (byte) 0x02; | ||
| 75 | 65 | ||
| 76 | /** | 66 | /** |
| 77 | - * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息标志 + 消息长度) | 67 | + * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度) |
| 78 | */ | 68 | */ |
| 79 | - private static final int HEADER_FIXED_LENGTH = 8; | 69 | + private static final int HEADER_FIXED_LENGTH = 7; |
| 80 | 70 | ||
| 81 | /** | 71 | /** |
| 82 | * 最小消息长度(头部 + 消息ID长度 + 方法名长度) | 72 | * 最小消息长度(头部 + 消息ID长度 + 方法名长度) |
| @@ -97,7 +87,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -97,7 +87,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 97 | byte messageType = determineMessageType(message); | 87 | byte messageType = determineMessageType(message); |
| 98 | // 2. 构建消息体 | 88 | // 2. 构建消息体 |
| 99 | byte[] bodyData = buildMessageBody(message, messageType); | 89 | byte[] bodyData = buildMessageBody(message, messageType); |
| 100 | - // 3. 构建完整消息(不包含deviceId,由连接上下文管理) | 90 | + // 3. 构建完整消息 |
| 101 | return buildCompleteMessage(message, messageType, bodyData); | 91 | return buildCompleteMessage(message, messageType, bodyData); |
| 102 | } catch (Exception e) { | 92 | } catch (Exception e) { |
| 103 | log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e); | 93 | log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e); |
| @@ -111,30 +101,59 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -111,30 +101,59 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 111 | Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足"); | 101 | Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足"); |
| 112 | try { | 102 | try { |
| 113 | Buffer buffer = Buffer.buffer(bytes); | 103 | Buffer buffer = Buffer.buffer(bytes); |
| 114 | - // 1. 解析协议头部 | ||
| 115 | - ProtocolHeader header = parseProtocolHeader(buffer); | ||
| 116 | - // 2. 解析消息内容(不包含deviceId,由上层连接管理器设置) | ||
| 117 | - return parseMessageContent(buffer, header); | 104 | + // 解析协议头部和消息内容 |
| 105 | + int index = 0; | ||
| 106 | + // 1. 验证魔术字 | ||
| 107 | + byte magic = buffer.getByte(index++); | ||
| 108 | + Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic); | ||
| 109 | + | ||
| 110 | + // 2. 验证版本号 | ||
| 111 | + byte version = buffer.getByte(index++); | ||
| 112 | + Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version); | ||
| 113 | + | ||
| 114 | + // 3. 读取消息类型 | ||
| 115 | + byte messageType = buffer.getByte(index++); | ||
| 116 | + // 直接验证消息类型,无需抽取方法 | ||
| 117 | + Assert.isTrue(messageType == REQUEST || messageType == RESPONSE, | ||
| 118 | + "无效的消息类型: " + messageType); | ||
| 119 | + | ||
| 120 | + // 4. 读取消息长度 | ||
| 121 | + int messageLength = buffer.getInt(index); | ||
| 122 | + index += 4; | ||
| 123 | + Assert.isTrue(messageLength == buffer.length(), | ||
| 124 | + "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length()); | ||
| 125 | + | ||
| 126 | + // 5. 读取消息 ID | ||
| 127 | + short messageIdLength = buffer.getShort(index); | ||
| 128 | + index += 2; | ||
| 129 | + String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name()); | ||
| 130 | + index += messageIdLength; | ||
| 131 | + | ||
| 132 | + // 6. 读取方法名 | ||
| 133 | + short methodLength = buffer.getShort(index); | ||
| 134 | + index += 2; | ||
| 135 | + String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name()); | ||
| 136 | + index += methodLength; | ||
| 137 | + | ||
| 138 | + // 7. 解析消息体 | ||
| 139 | + return parseMessageBody(buffer, index, messageType, messageId, method); | ||
| 118 | } catch (Exception e) { | 140 | } catch (Exception e) { |
| 119 | log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e); | 141 | log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e); |
| 120 | throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e); | 142 | throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e); |
| 121 | } | 143 | } |
| 122 | } | 144 | } |
| 123 | 145 | ||
| 124 | - // ==================== 编码相关方法 ==================== | ||
| 125 | - | ||
| 126 | /** | 146 | /** |
| 127 | * 确定消息类型 | 147 | * 确定消息类型 |
| 128 | * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息 | 148 | * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息 |
| 129 | */ | 149 | */ |
| 130 | private byte determineMessageType(IotDeviceMessage message) { | 150 | private byte determineMessageType(IotDeviceMessage message) { |
| 131 | // 判断是否为响应消息:有响应码或响应消息时为响应 | 151 | // 判断是否为响应消息:有响应码或响应消息时为响应 |
| 132 | - // TODO @haohao:感觉只判断 code 更稳妥点?msg 有可能空。。。 | ||
| 133 | - if (message.getCode() != null || StrUtil.isNotBlank(message.getMsg())) { | ||
| 134 | - return MessageType.RESPONSE; | 152 | + if (message.getCode() != null) { |
| 153 | + return RESPONSE; | ||
| 135 | } | 154 | } |
| 136 | // 默认为请求消息 | 155 | // 默认为请求消息 |
| 137 | - return MessageType.REQUEST; | 156 | + return REQUEST; |
| 138 | } | 157 | } |
| 139 | 158 | ||
| 140 | /** | 159 | /** |
| @@ -142,12 +161,12 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -142,12 +161,12 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 142 | */ | 161 | */ |
| 143 | private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) { | 162 | private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) { |
| 144 | Buffer bodyBuffer = Buffer.buffer(); | 163 | Buffer bodyBuffer = Buffer.buffer(); |
| 145 | - if (messageType == MessageType.RESPONSE) { | 164 | + if (messageType == RESPONSE) { |
| 146 | // code | 165 | // code |
| 147 | bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0); | 166 | bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0); |
| 148 | // msg | 167 | // msg |
| 149 | String msg = message.getMsg() != null ? message.getMsg() : ""; | 168 | String msg = message.getMsg() != null ? message.getMsg() : ""; |
| 150 | - byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8); | 169 | + byte[] msgBytes = StrUtil.utf8Bytes(msg); |
| 151 | bodyBuffer.appendShort((short) msgBytes.length); | 170 | bodyBuffer.appendShort((short) msgBytes.length); |
| 152 | bodyBuffer.appendBytes(msgBytes); | 171 | bodyBuffer.appendBytes(msgBytes); |
| 153 | // data | 172 | // data |
| @@ -155,11 +174,9 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -155,11 +174,9 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 155 | bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData())); | 174 | bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData())); |
| 156 | } | 175 | } |
| 157 | } else { | 176 | } else { |
| 158 | - // params | ||
| 159 | - // TODO @haohao:请求是不是只处理 message.getParams() 哈? | ||
| 160 | - Object payload = message.getParams() != null ? message.getParams() : message.getData(); | ||
| 161 | - if (payload != null) { | ||
| 162 | - bodyBuffer.appendBytes(JsonUtils.toJsonByte(payload)); | 177 | + // 请求消息只处理 params 参数 |
| 178 | + if (message.getParams() != null) { | ||
| 179 | + bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams())); | ||
| 163 | } | 180 | } |
| 164 | } | 181 | } |
| 165 | return bodyBuffer.getBytes(); | 182 | return bodyBuffer.getBytes(); |
| @@ -174,20 +191,17 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -174,20 +191,17 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 174 | buffer.appendByte(MAGIC_NUMBER); | 191 | buffer.appendByte(MAGIC_NUMBER); |
| 175 | buffer.appendByte(PROTOCOL_VERSION); | 192 | buffer.appendByte(PROTOCOL_VERSION); |
| 176 | buffer.appendByte(messageType); | 193 | buffer.appendByte(messageType); |
| 177 | - buffer.appendByte((byte) 0x00); // 消息标志,预留字段 TODO @haohao:这个标识的作用是啥呀? | ||
| 178 | - // 2. 预留消息长度位置(在 6. 更新消息长度) | 194 | + // 2. 预留消息长度位置(在 5. 更新消息长度) |
| 179 | int lengthPosition = buffer.length(); | 195 | int lengthPosition = buffer.length(); |
| 180 | buffer.appendInt(0); | 196 | buffer.appendInt(0); |
| 181 | // 3. 写入消息 ID | 197 | // 3. 写入消息 ID |
| 182 | String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId() | 198 | String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId() |
| 183 | - // TODO @haohao:复用 IotDeviceMessageUtils 的 generateMessageId 哇? | ||
| 184 | - : generateMessageId(message.getMethod()); | ||
| 185 | - // TODO @haohao:StrUtil.utf8Bytes() | ||
| 186 | - byte[] messageIdBytes = messageId.getBytes(StandardCharsets.UTF_8); | 199 | + : IotDeviceMessageUtils.generateMessageId(); |
| 200 | + byte[] messageIdBytes = StrUtil.utf8Bytes(messageId); | ||
| 187 | buffer.appendShort((short) messageIdBytes.length); | 201 | buffer.appendShort((short) messageIdBytes.length); |
| 188 | buffer.appendBytes(messageIdBytes); | 202 | buffer.appendBytes(messageIdBytes); |
| 189 | // 4. 写入方法名 | 203 | // 4. 写入方法名 |
| 190 | - byte[] methodBytes = message.getMethod().getBytes(StandardCharsets.UTF_8); | 204 | + byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod()); |
| 191 | buffer.appendShort((short) methodBytes.length); | 205 | buffer.appendShort((short) methodBytes.length); |
| 192 | buffer.appendBytes(methodBytes); | 206 | buffer.appendBytes(methodBytes); |
| 193 | // 5. 写入消息体 | 207 | // 5. 写入消息体 |
| @@ -198,66 +212,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -198,66 +212,6 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 198 | } | 212 | } |
| 199 | 213 | ||
| 200 | /** | 214 | /** |
| 201 | - * 生成消息 ID | ||
| 202 | - */ | ||
| 203 | - private String generateMessageId(String method) { | ||
| 204 | - return method + "_" + System.currentTimeMillis() + "_" + (int) (Math.random() * 1000); | ||
| 205 | - } | ||
| 206 | - | ||
| 207 | - // ==================== 解码相关方法 ==================== | ||
| 208 | - | ||
| 209 | - // TODO @haohao:是不是把 parseProtocolHeader、parseMessageContent 合并? | ||
| 210 | - /** | ||
| 211 | - * 解析协议头部 | ||
| 212 | - */ | ||
| 213 | - private ProtocolHeader parseProtocolHeader(Buffer buffer) { | ||
| 214 | - int index = 0; | ||
| 215 | - // 1. 验证魔术字 | ||
| 216 | - byte magic = buffer.getByte(index++); | ||
| 217 | - Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic); | ||
| 218 | - // 2. 验证版本号 | ||
| 219 | - byte version = buffer.getByte(index++); | ||
| 220 | - Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version); | ||
| 221 | - | ||
| 222 | - // 3. 读取消息类型 | ||
| 223 | - byte messageType = buffer.getByte(index++); | ||
| 224 | - Assert.isTrue(isValidMessageType(messageType), "无效的消息类型: " + messageType); | ||
| 225 | - // 4. 读取消息标志(暂时跳过) | ||
| 226 | - byte messageFlags = buffer.getByte(index++); | ||
| 227 | - | ||
| 228 | - // 5. 读取消息长度 | ||
| 229 | - int messageLength = buffer.getInt(index); | ||
| 230 | - index += 4; | ||
| 231 | - | ||
| 232 | - Assert.isTrue(messageLength == buffer.length(), | ||
| 233 | - "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length()); | ||
| 234 | - | ||
| 235 | - return new ProtocolHeader(magic, version, messageType, messageFlags, messageLength, index); | ||
| 236 | - } | ||
| 237 | - | ||
| 238 | - /** | ||
| 239 | - * 解析消息内容 | ||
| 240 | - */ | ||
| 241 | - private IotDeviceMessage parseMessageContent(Buffer buffer, ProtocolHeader header) { | ||
| 242 | - int index = header.getNextIndex(); | ||
| 243 | - | ||
| 244 | - // 1. 读取消息 ID | ||
| 245 | - short messageIdLength = buffer.getShort(index); | ||
| 246 | - index += 2; | ||
| 247 | - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name()); | ||
| 248 | - index += messageIdLength; | ||
| 249 | - | ||
| 250 | - // 2. 读取方法名 | ||
| 251 | - short methodLength = buffer.getShort(index); | ||
| 252 | - index += 2; | ||
| 253 | - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name()); | ||
| 254 | - index += methodLength; | ||
| 255 | - | ||
| 256 | - // 3. 解析消息体 | ||
| 257 | - return parseMessageBody(buffer, index, header.getMessageType(), messageId, method); | ||
| 258 | - } | ||
| 259 | - | ||
| 260 | - /** | ||
| 261 | * 解析消息体 | 215 | * 解析消息体 |
| 262 | */ | 216 | */ |
| 263 | private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType, | 217 | private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType, |
| @@ -267,11 +221,11 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -267,11 +221,11 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 267 | return IotDeviceMessage.of(messageId, method, null, null, null, null); | 221 | return IotDeviceMessage.of(messageId, method, null, null, null, null); |
| 268 | } | 222 | } |
| 269 | 223 | ||
| 270 | - if (messageType == MessageType.RESPONSE) { | 224 | + if (messageType == RESPONSE) { |
| 271 | // 响应消息:解析 code + msg + data | 225 | // 响应消息:解析 code + msg + data |
| 272 | return parseResponseMessage(buffer, startIndex, messageId, method); | 226 | return parseResponseMessage(buffer, startIndex, messageId, method); |
| 273 | } else { | 227 | } else { |
| 274 | - // 请求消息:解析 payload(可能是 params 或 data) | 228 | + // 请求消息:解析 payload |
| 275 | Object payload = parseJsonData(buffer, startIndex, buffer.length()); | 229 | Object payload = parseJsonData(buffer, startIndex, buffer.length()); |
| 276 | return IotDeviceMessage.of(messageId, method, payload, null, null, null); | 230 | return IotDeviceMessage.of(messageId, method, payload, null, null, null); |
| 277 | } | 231 | } |
| @@ -303,7 +257,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -303,7 +257,7 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 303 | } | 257 | } |
| 304 | 258 | ||
| 305 | /** | 259 | /** |
| 306 | - * 解析JSON数据 | 260 | + * 解析 JSON 数据 |
| 307 | */ | 261 | */ |
| 308 | private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) { | 262 | private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) { |
| 309 | if (startIndex >= endIndex) { | 263 | if (startIndex >= endIndex) { |
| @@ -318,34 +272,14 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | @@ -318,34 +272,14 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | ||
| 318 | } | 272 | } |
| 319 | } | 273 | } |
| 320 | 274 | ||
| 321 | - // ==================== 辅助方法 ==================== | ||
| 322 | - | ||
| 323 | - // TODO @haohao:这个貌似只用一次,可以考虑不抽小方法哈; | ||
| 324 | /** | 275 | /** |
| 325 | - * 验证消息类型是否有效 | 276 | + * 快速检测是否为二进制格式 |
| 277 | + * | ||
| 278 | + * @param data 数据 | ||
| 279 | + * @return 是否为二进制格式 | ||
| 326 | */ | 280 | */ |
| 327 | - private boolean isValidMessageType(byte messageType) { | ||
| 328 | - return messageType == MessageType.REQUEST || messageType == MessageType.RESPONSE; | 281 | + public static boolean isBinaryFormatQuick(byte[] data) { |
| 282 | + return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER; | ||
| 329 | } | 283 | } |
| 330 | 284 | ||
| 331 | - // ==================== 内部类 ==================== | ||
| 332 | - | ||
| 333 | - /** | ||
| 334 | - * 协议头部信息 | ||
| 335 | - */ | ||
| 336 | - @Data | ||
| 337 | - @AllArgsConstructor | ||
| 338 | - private static class ProtocolHeader { | ||
| 339 | - | ||
| 340 | - private byte magic; | ||
| 341 | - private byte version; | ||
| 342 | - private byte messageType; | ||
| 343 | - private byte messageFlags; | ||
| 344 | - private int messageLength; | ||
| 345 | - /** | ||
| 346 | - * 指向消息内容开始位置 | ||
| 347 | - */ | ||
| 348 | - private int nextIndex; | ||
| 349 | - | ||
| 350 | - } | ||
| 351 | } | 285 | } |
| 352 | \ No newline at end of file | 286 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
| @@ -28,46 +28,33 @@ public class IotTcpConnectionManager { | @@ -28,46 +28,33 @@ public class IotTcpConnectionManager { | ||
| 28 | private final Map<NetSocket, ConnectionInfo> connectionMap = new ConcurrentHashMap<>(); | 28 | private final Map<NetSocket, ConnectionInfo> connectionMap = new ConcurrentHashMap<>(); |
| 29 | 29 | ||
| 30 | /** | 30 | /** |
| 31 | - * 设备 ID -> NetSocket 的映射(用于快速查找) | 31 | + * 设备 ID -> NetSocket 的映射 |
| 32 | */ | 32 | */ |
| 33 | private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>(); | 33 | private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>(); |
| 34 | 34 | ||
| 35 | /** | 35 | /** |
| 36 | - * NetSocket -> 设备 ID 的映射(用于连接断开时清理) | ||
| 37 | - */ | ||
| 38 | - private final Map<NetSocket, Long> socketDeviceMap = new ConcurrentHashMap<>(); | ||
| 39 | - | ||
| 40 | - /** | ||
| 41 | * 注册设备连接(包含认证信息) | 36 | * 注册设备连接(包含认证信息) |
| 42 | * | 37 | * |
| 43 | - * @param socket TCP 连接 | ||
| 44 | - * @param deviceId 设备 ID | ||
| 45 | - * @param authInfo 认证信息 | 38 | + * @param socket TCP 连接 |
| 39 | + * @param deviceId 设备 ID | ||
| 40 | + * @param connectionInfo 连接信息 | ||
| 46 | */ | 41 | */ |
| 47 | - public void registerConnection(NetSocket socket, Long deviceId, AuthInfo authInfo) { | 42 | + public void registerConnection(NetSocket socket, Long deviceId, ConnectionInfo connectionInfo) { |
| 48 | // 如果设备已有其他连接,先清理旧连接 | 43 | // 如果设备已有其他连接,先清理旧连接 |
| 49 | NetSocket oldSocket = deviceSocketMap.get(deviceId); | 44 | NetSocket oldSocket = deviceSocketMap.get(deviceId); |
| 50 | if (oldSocket != null && oldSocket != socket) { | 45 | if (oldSocket != null && oldSocket != socket) { |
| 51 | log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", | 46 | log.info("[registerConnection][设备已有其他连接,断开旧连接,设备 ID: {},旧连接: {}]", |
| 52 | deviceId, oldSocket.remoteAddress()); | 47 | deviceId, oldSocket.remoteAddress()); |
| 53 | oldSocket.close(); | 48 | oldSocket.close(); |
| 54 | - // 清理所有相关映射 | 49 | + // 清理旧连接的映射 |
| 55 | connectionMap.remove(oldSocket); | 50 | connectionMap.remove(oldSocket); |
| 56 | - socketDeviceMap.remove(oldSocket); | ||
| 57 | } | 51 | } |
| 58 | 52 | ||
| 59 | - // 注册新连接 - 更新所有映射关系 | ||
| 60 | - ConnectionInfo connectionInfo = new ConnectionInfo() | ||
| 61 | - .setDeviceId(deviceId) | ||
| 62 | - .setAuthInfo(authInfo) | ||
| 63 | - .setAuthenticated(true); | ||
| 64 | connectionMap.put(socket, connectionInfo); | 53 | connectionMap.put(socket, connectionInfo); |
| 65 | deviceSocketMap.put(deviceId, socket); | 54 | deviceSocketMap.put(deviceId, socket); |
| 66 | - // TODO @haohao:socketDeviceMap 和 connectionMap 会重复哇?connectionMap.get(socket).getDeviceId | ||
| 67 | - socketDeviceMap.put(socket, deviceId); | ||
| 68 | 55 | ||
| 69 | log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", | 56 | log.info("[registerConnection][注册设备连接,设备 ID: {},连接: {},product key: {},device name: {}]", |
| 70 | - deviceId, socket.remoteAddress(), authInfo.getProductKey(), authInfo.getDeviceName()); | 57 | + deviceId, socket.remoteAddress(), connectionInfo.getProductKey(), connectionInfo.getDeviceName()); |
| 71 | } | 58 | } |
| 72 | 59 | ||
| 73 | /** | 60 | /** |
| @@ -77,29 +64,14 @@ public class IotTcpConnectionManager { | @@ -77,29 +64,14 @@ public class IotTcpConnectionManager { | ||
| 77 | */ | 64 | */ |
| 78 | public void unregisterConnection(NetSocket socket) { | 65 | public void unregisterConnection(NetSocket socket) { |
| 79 | ConnectionInfo connectionInfo = connectionMap.remove(socket); | 66 | ConnectionInfo connectionInfo = connectionMap.remove(socket); |
| 80 | - Long deviceId = socketDeviceMap.remove(socket); | ||
| 81 | - if (connectionInfo != null && deviceId != null) { | 67 | + if (connectionInfo != null) { |
| 68 | + Long deviceId = connectionInfo.getDeviceId(); | ||
| 82 | deviceSocketMap.remove(deviceId); | 69 | deviceSocketMap.remove(deviceId); |
| 83 | log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", | 70 | log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", |
| 84 | deviceId, socket.remoteAddress()); | 71 | deviceId, socket.remoteAddress()); |
| 85 | } | 72 | } |
| 86 | } | 73 | } |
| 87 | 74 | ||
| 88 | - // TODO @haohao:用不到,要不暂时清理哈。 | ||
| 89 | - /** | ||
| 90 | - * 注销设备连接(通过设备 ID) | ||
| 91 | - * | ||
| 92 | - * @param deviceId 设备 ID | ||
| 93 | - */ | ||
| 94 | - public void unregisterConnection(Long deviceId) { | ||
| 95 | - NetSocket socket = deviceSocketMap.remove(deviceId); | ||
| 96 | - if (socket != null) { | ||
| 97 | - connectionMap.remove(socket); | ||
| 98 | - socketDeviceMap.remove(socket); | ||
| 99 | - log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]", deviceId, socket.remoteAddress()); | ||
| 100 | - } | ||
| 101 | - } | ||
| 102 | - | ||
| 103 | /** | 75 | /** |
| 104 | * 检查连接是否已认证 | 76 | * 检查连接是否已认证 |
| 105 | */ | 77 | */ |
| @@ -116,11 +88,10 @@ public class IotTcpConnectionManager { | @@ -116,11 +88,10 @@ public class IotTcpConnectionManager { | ||
| 116 | } | 88 | } |
| 117 | 89 | ||
| 118 | /** | 90 | /** |
| 119 | - * 获取连接的认证信息 | 91 | + * 获取连接信息 |
| 120 | */ | 92 | */ |
| 121 | - public AuthInfo getAuthInfo(NetSocket socket) { | ||
| 122 | - ConnectionInfo info = connectionMap.get(socket); | ||
| 123 | - return info != null ? info.getAuthInfo() : null; | 93 | + public ConnectionInfo getConnectionInfo(NetSocket socket) { |
| 94 | + return connectionMap.get(socket); | ||
| 124 | } | 95 | } |
| 125 | 96 | ||
| 126 | /** | 97 | /** |
| @@ -159,30 +130,34 @@ public class IotTcpConnectionManager { | @@ -159,30 +130,34 @@ public class IotTcpConnectionManager { | ||
| 159 | } | 130 | } |
| 160 | } | 131 | } |
| 161 | 132 | ||
| 162 | - // TODO @haohao:ConnectionInfo 和 AuthInfo 是不是可以融合哈? | ||
| 163 | - | ||
| 164 | /** | 133 | /** |
| 165 | - * 连接信息 | 134 | + * 连接信息(包含认证信息) |
| 166 | */ | 135 | */ |
| 167 | @Data | 136 | @Data |
| 168 | public static class ConnectionInfo { | 137 | public static class ConnectionInfo { |
| 169 | - | ||
| 170 | - private Long deviceId; | ||
| 171 | - private AuthInfo authInfo; | ||
| 172 | - private boolean authenticated; | ||
| 173 | - | ||
| 174 | - } | ||
| 175 | - | ||
| 176 | - /** | ||
| 177 | - * 认证信息 | ||
| 178 | - */ | ||
| 179 | - @Data | ||
| 180 | - public static class AuthInfo { | ||
| 181 | - | 138 | + /** |
| 139 | + * 设备 ID | ||
| 140 | + */ | ||
| 182 | private Long deviceId; | 141 | private Long deviceId; |
| 142 | + /** | ||
| 143 | + * 产品 Key | ||
| 144 | + */ | ||
| 183 | private String productKey; | 145 | private String productKey; |
| 146 | + /** | ||
| 147 | + * 设备名称 | ||
| 148 | + */ | ||
| 184 | private String deviceName; | 149 | private String deviceName; |
| 150 | + /** | ||
| 151 | + * 客户端 ID | ||
| 152 | + */ | ||
| 185 | private String clientId; | 153 | private String clientId; |
| 186 | - | 154 | + /** |
| 155 | + * 消息编解码类型(认证后确定) | ||
| 156 | + */ | ||
| 157 | + private String codecType; | ||
| 158 | + /** | ||
| 159 | + * 是否已认证 | ||
| 160 | + */ | ||
| 161 | + private boolean authenticated; | ||
| 187 | } | 162 | } |
| 188 | } | 163 | } |
| 189 | \ No newline at end of file | 164 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java
| 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.map.MapUtil; | 3 | import cn.hutool.core.map.MapUtil; |
| 4 | +import cn.hutool.core.util.BooleanUtil; | ||
| 4 | import cn.hutool.core.util.IdUtil; | 5 | import cn.hutool.core.util.IdUtil; |
| 5 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 6 | import cn.hutool.extra.spring.SpringUtil; | 7 | import cn.hutool.extra.spring.SpringUtil; |
| 7 | -import cn.hutool.json.JSONObject; | ||
| 8 | -import cn.hutool.json.JSONUtil; | ||
| 9 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 9 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 10 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | 10 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | 11 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 12 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | 12 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| @@ -21,12 +21,8 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa | @@ -21,12 +21,8 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa | ||
| 21 | import io.vertx.core.Handler; | 21 | import io.vertx.core.Handler; |
| 22 | import io.vertx.core.buffer.Buffer; | 22 | import io.vertx.core.buffer.Buffer; |
| 23 | import io.vertx.core.net.NetSocket; | 23 | import io.vertx.core.net.NetSocket; |
| 24 | -import lombok.AllArgsConstructor; | ||
| 25 | -import lombok.Data; | ||
| 26 | import lombok.extern.slf4j.Slf4j; | 24 | import lombok.extern.slf4j.Slf4j; |
| 27 | 25 | ||
| 28 | -import java.nio.charset.StandardCharsets; | ||
| 29 | - | ||
| 30 | /** | 26 | /** |
| 31 | * TCP 上行消息处理器 | 27 | * TCP 上行消息处理器 |
| 32 | * | 28 | * |
| @@ -77,31 +73,55 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -77,31 +73,55 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 77 | }); | 73 | }); |
| 78 | 74 | ||
| 79 | // 设置消息处理器 | 75 | // 设置消息处理器 |
| 80 | - socket.handler(buffer -> processMessage(clientId, buffer, socket)); | 76 | + socket.handler(buffer -> { |
| 77 | + try { | ||
| 78 | + processMessage(clientId, buffer, socket); | ||
| 79 | + } catch (Exception e) { | ||
| 80 | + log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]", | ||
| 81 | + clientId, socket.remoteAddress(), e.getMessage()); | ||
| 82 | + cleanupConnection(socket); | ||
| 83 | + socket.close(); | ||
| 84 | + } | ||
| 85 | + }); | ||
| 81 | } | 86 | } |
| 82 | 87 | ||
| 83 | /** | 88 | /** |
| 84 | * 处理消息 | 89 | * 处理消息 |
| 90 | + * | ||
| 91 | + * @param clientId 客户端 ID | ||
| 92 | + * @param buffer 消息 | ||
| 93 | + * @param socket 网络连接 | ||
| 94 | + * @throws Exception 消息解码失败时抛出异常 | ||
| 85 | */ | 95 | */ |
| 86 | - private void processMessage(String clientId, Buffer buffer, NetSocket socket) { | 96 | + private void processMessage(String clientId, Buffer buffer, NetSocket socket) throws Exception { |
| 97 | + // 1. 基础检查 | ||
| 98 | + if (buffer == null || buffer.length() == 0) { | ||
| 99 | + return; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + // 2. 获取消息格式类型 | ||
| 103 | + String codecType = getMessageCodecType(buffer, socket); | ||
| 104 | + | ||
| 105 | + // 3. 解码消息 | ||
| 106 | + IotDeviceMessage message; | ||
| 87 | try { | 107 | try { |
| 88 | - // 1.1 数据包基础检查 | ||
| 89 | - if (buffer.length() == 0) { | ||
| 90 | - return; | ||
| 91 | - } | ||
| 92 | - // 1.2 解码消息 | ||
| 93 | - MessageInfo messageInfo = decodeMessage(buffer); | ||
| 94 | - if (messageInfo == null) { | ||
| 95 | - return; | 108 | + message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType); |
| 109 | + if (message == null) { | ||
| 110 | + throw new Exception("解码后消息为空"); | ||
| 96 | } | 111 | } |
| 112 | + } catch (Exception e) { | ||
| 113 | + // 消息格式错误时抛出异常,由上层处理连接断开 | ||
| 114 | + throw new Exception("消息解码失败: " + e.getMessage(), e); | ||
| 115 | + } | ||
| 97 | 116 | ||
| 98 | - // 2. 根据消息类型路由处理 | ||
| 99 | - if (isAuthRequest(messageInfo.message)) { | 117 | + // 4. 根据消息类型路由处理 |
| 118 | + try { | ||
| 119 | + if (AUTH_METHOD.equals(message.getMethod())) { | ||
| 100 | // 认证请求 | 120 | // 认证请求 |
| 101 | - handleAuthenticationRequest(clientId, messageInfo, socket); | 121 | + handleAuthenticationRequest(clientId, message, codecType, socket); |
| 102 | } else { | 122 | } else { |
| 103 | // 业务消息 | 123 | // 业务消息 |
| 104 | - handleBusinessRequest(clientId, messageInfo, socket); | 124 | + handleBusinessRequest(clientId, message, codecType, socket); |
| 105 | } | 125 | } |
| 106 | } catch (Exception e) { | 126 | } catch (Exception e) { |
| 107 | log.error("[processMessage][处理消息失败,客户端 ID: {}]", clientId, e); | 127 | log.error("[processMessage][处理消息失败,客户端 ID: {}]", clientId, e); |
| @@ -110,226 +130,158 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -110,226 +130,158 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 110 | 130 | ||
| 111 | /** | 131 | /** |
| 112 | * 处理认证请求 | 132 | * 处理认证请求 |
| 133 | + * | ||
| 134 | + * @param clientId 客户端 ID | ||
| 135 | + * @param message 消息信息 | ||
| 136 | + * @param codecType 消息编解码类型 | ||
| 137 | + * @param socket 网络连接 | ||
| 113 | */ | 138 | */ |
| 114 | - private void handleAuthenticationRequest(String clientId, MessageInfo messageInfo, NetSocket socket) { | 139 | + private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, String codecType, |
| 140 | + NetSocket socket) { | ||
| 115 | try { | 141 | try { |
| 116 | // 1.1 解析认证参数 | 142 | // 1.1 解析认证参数 |
| 117 | - IotDeviceMessage message = messageInfo.message; | ||
| 118 | - AuthParams authParams = parseAuthParams(message.getParams()); | 143 | + IotDeviceAuthReqDTO authParams = JsonUtils.parseObject(message.getParams().toString(), |
| 144 | + IotDeviceAuthReqDTO.class); | ||
| 119 | if (authParams == null) { | 145 | if (authParams == null) { |
| 120 | - sendError(socket, message.getRequestId(), "认证参数不完整", messageInfo.codecType); | 146 | + sendErrorResponse(socket, message.getRequestId(), "认证参数不完整", codecType); |
| 121 | return; | 147 | return; |
| 122 | } | 148 | } |
| 123 | // 1.2 执行认证 | 149 | // 1.2 执行认证 |
| 124 | - if (!authenticateDevice(authParams)) { | 150 | + if (!validateDeviceAuth(authParams)) { |
| 125 | log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", | 151 | log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", |
| 126 | - clientId, authParams.username); | ||
| 127 | - sendError(socket, message.getRequestId(), "认证失败", messageInfo.codecType); | 152 | + clientId, authParams.getUsername()); |
| 153 | + sendErrorResponse(socket, message.getRequestId(), "认证失败", codecType); | ||
| 128 | return; | 154 | return; |
| 129 | } | 155 | } |
| 130 | 156 | ||
| 131 | // 2.1 解析设备信息 | 157 | // 2.1 解析设备信息 |
| 132 | - IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.username); | 158 | + IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); |
| 133 | if (deviceInfo == null) { | 159 | if (deviceInfo == null) { |
| 134 | - sendError(socket, message.getRequestId(), "解析设备信息失败", messageInfo.codecType); | 160 | + sendErrorResponse(socket, message.getRequestId(), "解析设备信息失败", codecType); |
| 135 | return; | 161 | return; |
| 136 | } | 162 | } |
| 137 | // 2.2 获取设备信息 | 163 | // 2.2 获取设备信息 |
| 138 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), | 164 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), |
| 139 | deviceInfo.getDeviceName()); | 165 | deviceInfo.getDeviceName()); |
| 140 | if (device == null) { | 166 | if (device == null) { |
| 141 | - sendError(socket, message.getRequestId(), "设备不存在", messageInfo.codecType); | 167 | + sendErrorResponse(socket, message.getRequestId(), "设备不存在", codecType); |
| 142 | return; | 168 | return; |
| 143 | } | 169 | } |
| 144 | 170 | ||
| 145 | - // 3. 注册连接并发送成功响应 | ||
| 146 | - registerConnection(socket, device, deviceInfo, authParams.clientId); | ||
| 147 | - sendOnlineMessage(deviceInfo); | ||
| 148 | - sendSuccess(socket, message.getRequestId(), "认证成功", messageInfo.codecType); | 171 | + // 3.1 注册连接 |
| 172 | + registerConnection(socket, device, clientId, codecType); | ||
| 173 | + // 3.2 发送上线消息 | ||
| 174 | + sendOnlineMessage(device); | ||
| 175 | + // 3.3 发送成功响应 | ||
| 176 | + sendSuccessResponse(socket, message.getRequestId(), "认证成功", codecType); | ||
| 149 | log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", | 177 | log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", |
| 150 | - device.getId(), deviceInfo.getDeviceName()); | 178 | + device.getId(), device.getDeviceName()); |
| 151 | } catch (Exception e) { | 179 | } catch (Exception e) { |
| 152 | log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e); | 180 | log.error("[handleAuthenticationRequest][认证处理异常,客户端 ID: {}]", clientId, e); |
| 153 | - sendError(socket, messageInfo.message.getRequestId(), "认证处理异常", messageInfo.codecType); | 181 | + sendErrorResponse(socket, message.getRequestId(), "认证处理异常", codecType); |
| 154 | } | 182 | } |
| 155 | } | 183 | } |
| 156 | 184 | ||
| 157 | /** | 185 | /** |
| 158 | * 处理业务请求 | 186 | * 处理业务请求 |
| 187 | + * | ||
| 188 | + * @param clientId 客户端 ID | ||
| 189 | + * @param message 消息信息 | ||
| 190 | + * @param codecType 消息编解码类型 | ||
| 191 | + * @param socket 网络连接 | ||
| 159 | */ | 192 | */ |
| 160 | - private void handleBusinessRequest(String clientId, MessageInfo messageInfo, NetSocket socket) { | 193 | + private void handleBusinessRequest(String clientId, IotDeviceMessage message, String codecType, NetSocket socket) { |
| 161 | try { | 194 | try { |
| 162 | // 1. 检查认证状态 | 195 | // 1. 检查认证状态 |
| 163 | if (connectionManager.isNotAuthenticated(socket)) { | 196 | if (connectionManager.isNotAuthenticated(socket)) { |
| 164 | log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId); | 197 | log.warn("[handleBusinessRequest][设备未认证,客户端 ID: {}]", clientId); |
| 165 | - sendError(socket, messageInfo.message.getRequestId(), "请先进行认证", messageInfo.codecType); | 198 | + sendErrorResponse(socket, message.getRequestId(), "请先进行认证", codecType); |
| 166 | return; | 199 | return; |
| 167 | } | 200 | } |
| 168 | 201 | ||
| 169 | // 2. 获取认证信息并处理业务消息 | 202 | // 2. 获取认证信息并处理业务消息 |
| 170 | - IotTcpConnectionManager.AuthInfo authInfo = connectionManager.getAuthInfo(socket); | ||
| 171 | - processBusinessMessage(clientId, messageInfo.message, authInfo); | ||
| 172 | - } catch (Exception e) { | ||
| 173 | - log.error("[handleBusinessRequest][业务请求处理异常,客户端 ID: {}]", clientId, e); | ||
| 174 | - } | ||
| 175 | - } | 203 | + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); |
| 176 | 204 | ||
| 177 | - // TODO @haohao:processBusinessMessage 这个小方法,直接融合到 handleBusinessRequest 里?读起来更聚集点 | ||
| 178 | - /** | ||
| 179 | - * 处理业务消息 | ||
| 180 | - */ | ||
| 181 | - private void processBusinessMessage(String clientId, IotDeviceMessage message, | ||
| 182 | - IotTcpConnectionManager.AuthInfo authInfo) { | ||
| 183 | - try { | ||
| 184 | - message.setDeviceId(authInfo.getDeviceId()); | ||
| 185 | - message.setServerId(serverId); | ||
| 186 | - // 发送到消息总线 | ||
| 187 | - deviceMessageService.sendDeviceMessage(message, authInfo.getProductKey(), | ||
| 188 | - authInfo.getDeviceName(), serverId); | 205 | + // 3. 发送消息到消息总线 |
| 206 | + deviceMessageService.sendDeviceMessage(message, connectionInfo.getProductKey(), | ||
| 207 | + connectionInfo.getDeviceName(), serverId); | ||
| 189 | } catch (Exception e) { | 208 | } catch (Exception e) { |
| 190 | - log.error("[processBusinessMessage][业务消息处理失败,客户端 ID: {},消息 ID: {}]", | ||
| 191 | - clientId, message.getId(), e); | 209 | + log.error("[handleBusinessRequest][业务请求处理异常,客户端 ID: {}]", clientId, e); |
| 192 | } | 210 | } |
| 193 | } | 211 | } |
| 194 | 212 | ||
| 195 | /** | 213 | /** |
| 196 | - * 解码消息 | 214 | + * 获取消息编解码类型 |
| 197 | * | 215 | * |
| 198 | * @param buffer 消息 | 216 | * @param buffer 消息 |
| 217 | + * @param socket 网络连接 | ||
| 218 | + * @return 消息编解码类型 | ||
| 199 | */ | 219 | */ |
| 200 | - private MessageInfo decodeMessage(Buffer buffer) { | ||
| 201 | - if (buffer == null || buffer.length() == 0) { | ||
| 202 | - return null; | ||
| 203 | - } | ||
| 204 | - // 1. 快速检测消息格式类型 | ||
| 205 | - // TODO @haohao:是不是进一步优化?socket 建立认证后,那条消息已经定义了所有消息的格式哈? | ||
| 206 | - String codecType = detectMessageFormat(buffer); | ||
| 207 | - try { | ||
| 208 | - // 2. 使用检测到的格式进行解码 | ||
| 209 | - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType); | ||
| 210 | - if (message == null) { | ||
| 211 | - return null; | ||
| 212 | - } | ||
| 213 | - return new MessageInfo(message, codecType); | ||
| 214 | - } catch (Exception e) { | ||
| 215 | - log.warn("[decodeMessage][消息解码失败,格式: {},数据长度: {},错误: {}]", | ||
| 216 | - codecType, buffer.length(), e.getMessage()); | ||
| 217 | - // TODO @haohao:一般消息格式不对,应该抛出异常,断开连接居多? | ||
| 218 | - return null; | ||
| 219 | - } | ||
| 220 | - } | ||
| 221 | - | ||
| 222 | - /** | ||
| 223 | - * 检测消息格式类型 | ||
| 224 | - * 优化性能:避免不必要的字符串转换 | ||
| 225 | - */ | ||
| 226 | - private String detectMessageFormat(Buffer buffer) { | ||
| 227 | - // TODO @haohao:是不是 IotTcpBinaryDeviceMessageCodec 提供一个 isBinaryFormat 方法哈? | ||
| 228 | - // 默认使用 JSON | ||
| 229 | - if (buffer.length() == 0) { | ||
| 230 | - return CODEC_TYPE_JSON; | ||
| 231 | - } | ||
| 232 | - | ||
| 233 | - // 1. 优先检测二进制格式(检查魔术字节 0x7E) | ||
| 234 | - if (isBinaryFormat(buffer)) { | ||
| 235 | - return CODEC_TYPE_BINARY; | ||
| 236 | - } | ||
| 237 | - | ||
| 238 | - // 2. 检测 JSON 格式(检查前几个有效字符) | ||
| 239 | - // TODO @haohao:这个检测去掉?直接 return CODEC_TYPE_JSON 更简洁一点。 | ||
| 240 | - if (isJsonFormat(buffer)) { | ||
| 241 | - return CODEC_TYPE_JSON; | 220 | + private String getMessageCodecType(Buffer buffer, NetSocket socket) { |
| 221 | + // 1. 如果已认证,优先使用缓存的编解码类型 | ||
| 222 | + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); | ||
| 223 | + if (connectionInfo != null && connectionInfo.isAuthenticated() && | ||
| 224 | + StrUtil.isNotBlank(connectionInfo.getCodecType())) { | ||
| 225 | + return connectionInfo.getCodecType(); | ||
| 242 | } | 226 | } |
| 243 | 227 | ||
| 244 | - // 3. 默认尝试 JSON 格式 | ||
| 245 | - return CODEC_TYPE_JSON; | ||
| 246 | - } | ||
| 247 | - | ||
| 248 | - /** | ||
| 249 | - * 检测二进制格式 | ||
| 250 | - * 通过检查魔术字节快速识别,避免完整字符串转换 | ||
| 251 | - */ | ||
| 252 | - private boolean isBinaryFormat(Buffer buffer) { | ||
| 253 | - // 二进制协议最小长度检查 | ||
| 254 | - if (buffer.length() < 8) { | ||
| 255 | - return false; | ||
| 256 | - } | ||
| 257 | - | ||
| 258 | - try { | ||
| 259 | - // 检查魔术字节 0x7E(二进制协议的第一个字节) | ||
| 260 | - byte firstByte = buffer.getByte(0); | ||
| 261 | - return firstByte == (byte) 0x7E; | ||
| 262 | - } catch (Exception e) { | ||
| 263 | - return false; | ||
| 264 | - } | ||
| 265 | - } | ||
| 266 | - | ||
| 267 | - /** | ||
| 268 | - * 检测 JSON 格式 | ||
| 269 | - * 只检查前几个有效字符,避免完整字符串转换 | ||
| 270 | - */ | ||
| 271 | - private boolean isJsonFormat(Buffer buffer) { | ||
| 272 | - try { | ||
| 273 | - // 检查前 64 个字节或整个缓冲区(取较小值) | ||
| 274 | - int checkLength = Math.min(buffer.length(), 64); | ||
| 275 | - String prefix = buffer.getString(0, checkLength, StandardCharsets.UTF_8.name()); | ||
| 276 | - | ||
| 277 | - if (StrUtil.isBlank(prefix)) { | ||
| 278 | - return false; | ||
| 279 | - } | ||
| 280 | - | ||
| 281 | - String trimmed = prefix.trim(); | ||
| 282 | - // JSON 格式必须以 { 或 [ 开头 | ||
| 283 | - return trimmed.startsWith("{") || trimmed.startsWith("["); | ||
| 284 | - | ||
| 285 | - } catch (Exception e) { | ||
| 286 | - return false; | ||
| 287 | - } | 228 | + // 2. 未认证时检测消息格式类型 |
| 229 | + return IotTcpBinaryDeviceMessageCodec.isBinaryFormatQuick(buffer.getBytes()) ? CODEC_TYPE_BINARY | ||
| 230 | + : CODEC_TYPE_JSON; | ||
| 288 | } | 231 | } |
| 289 | 232 | ||
| 290 | /** | 233 | /** |
| 291 | * 注册连接信息 | 234 | * 注册连接信息 |
| 235 | + * | ||
| 236 | + * @param socket 网络连接 | ||
| 237 | + * @param device 设备 | ||
| 238 | + * @param clientId 客户端 ID | ||
| 239 | + * @param codecType 消息编解码类型 | ||
| 292 | */ | 240 | */ |
| 293 | private void registerConnection(NetSocket socket, IotDeviceRespDTO device, | 241 | private void registerConnection(NetSocket socket, IotDeviceRespDTO device, |
| 294 | - IotDeviceAuthUtils.DeviceInfo deviceInfo, String clientId) { | ||
| 295 | - // TODO @haohao:AuthInfo 的创建,放在 connectionManager 里构建貌似会更收敛一点? | ||
| 296 | - // 创建认证信息 | ||
| 297 | - IotTcpConnectionManager.AuthInfo authInfo = new IotTcpConnectionManager.AuthInfo() | 242 | + String clientId, String codecType) { |
| 243 | + IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo() | ||
| 298 | .setDeviceId(device.getId()) | 244 | .setDeviceId(device.getId()) |
| 299 | - .setProductKey(deviceInfo.getProductKey()) | ||
| 300 | - .setDeviceName(deviceInfo.getDeviceName()) | ||
| 301 | - .setClientId(clientId); | 245 | + .setProductKey(device.getProductKey()) |
| 246 | + .setDeviceName(device.getDeviceName()) | ||
| 247 | + .setClientId(clientId) | ||
| 248 | + .setCodecType(codecType) | ||
| 249 | + .setAuthenticated(true); | ||
| 302 | // 注册连接 | 250 | // 注册连接 |
| 303 | - connectionManager.registerConnection(socket, device.getId(), authInfo); | 251 | + connectionManager.registerConnection(socket, device.getId(), connectionInfo); |
| 304 | } | 252 | } |
| 305 | 253 | ||
| 306 | /** | 254 | /** |
| 307 | * 发送设备上线消息 | 255 | * 发送设备上线消息 |
| 256 | + * | ||
| 257 | + * @param device 设备信息 | ||
| 308 | */ | 258 | */ |
| 309 | - private void sendOnlineMessage(IotDeviceAuthUtils.DeviceInfo deviceInfo) { | 259 | + private void sendOnlineMessage(IotDeviceRespDTO device) { |
| 310 | try { | 260 | try { |
| 311 | IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); | 261 | IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); |
| 312 | - deviceMessageService.sendDeviceMessage(onlineMessage, deviceInfo.getProductKey(), | ||
| 313 | - deviceInfo.getDeviceName(), serverId); | 262 | + deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(), |
| 263 | + device.getDeviceName(), serverId); | ||
| 314 | } catch (Exception e) { | 264 | } catch (Exception e) { |
| 315 | - log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", deviceInfo.getDeviceName(), e); | 265 | + log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", device.getDeviceName(), e); |
| 316 | } | 266 | } |
| 317 | } | 267 | } |
| 318 | 268 | ||
| 319 | /** | 269 | /** |
| 320 | * 清理连接 | 270 | * 清理连接 |
| 271 | + * | ||
| 272 | + * @param socket 网络连接 | ||
| 321 | */ | 273 | */ |
| 322 | private void cleanupConnection(NetSocket socket) { | 274 | private void cleanupConnection(NetSocket socket) { |
| 323 | try { | 275 | try { |
| 324 | - // 发送离线消息(如果已认证) | ||
| 325 | - IotTcpConnectionManager.AuthInfo authInfo = connectionManager.getAuthInfo(socket); | ||
| 326 | - if (authInfo != null) { | 276 | + // 1. 发送离线消息(如果已认证) |
| 277 | + IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket); | ||
| 278 | + if (connectionInfo != null) { | ||
| 327 | IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); | 279 | IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline(); |
| 328 | - deviceMessageService.sendDeviceMessage(offlineMessage, authInfo.getProductKey(), | ||
| 329 | - authInfo.getDeviceName(), serverId); | 280 | + deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(), |
| 281 | + connectionInfo.getDeviceName(), serverId); | ||
| 330 | } | 282 | } |
| 331 | 283 | ||
| 332 | - // 注销连接 | 284 | + // 2. 注销连接 |
| 333 | connectionManager.unregisterConnection(socket); | 285 | connectionManager.unregisterConnection(socket); |
| 334 | } catch (Exception e) { | 286 | } catch (Exception e) { |
| 335 | log.error("[cleanupConnection][清理连接失败]", e); | 287 | log.error("[cleanupConnection][清理连接失败]", e); |
| @@ -338,6 +290,12 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -338,6 +290,12 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 338 | 290 | ||
| 339 | /** | 291 | /** |
| 340 | * 发送响应消息 | 292 | * 发送响应消息 |
| 293 | + * | ||
| 294 | + * @param socket 网络连接 | ||
| 295 | + * @param success 是否成功 | ||
| 296 | + * @param message 消息 | ||
| 297 | + * @param requestId 请求 ID | ||
| 298 | + * @param codecType 消息编解码类型 | ||
| 341 | */ | 299 | */ |
| 342 | private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) { | 300 | private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) { |
| 343 | try { | 301 | try { |
| @@ -346,8 +304,9 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -346,8 +304,9 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 346 | .put("message", message) | 304 | .put("message", message) |
| 347 | .build(); | 305 | .build(); |
| 348 | 306 | ||
| 307 | + int code = success ? 0 : 401; | ||
| 349 | IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, | 308 | IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, |
| 350 | - success ? 0 : 401, message); | 309 | + code, message); |
| 351 | 310 | ||
| 352 | byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType); | 311 | byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType); |
| 353 | socket.write(Buffer.buffer(encodedData)); | 312 | socket.write(Buffer.buffer(encodedData)); |
| @@ -357,94 +316,47 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | @@ -357,94 +316,47 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 357 | } | 316 | } |
| 358 | } | 317 | } |
| 359 | 318 | ||
| 360 | - // ==================== 辅助方法 ==================== | ||
| 361 | - | ||
| 362 | - /** | ||
| 363 | - * 判断是否为认证请求 | ||
| 364 | - */ | ||
| 365 | - private boolean isAuthRequest(IotDeviceMessage message) { | ||
| 366 | - return AUTH_METHOD.equals(message.getMethod()); | ||
| 367 | - } | ||
| 368 | - | ||
| 369 | - /** | ||
| 370 | - * 解析认证参数 | ||
| 371 | - */ | ||
| 372 | - private AuthParams parseAuthParams(Object params) { | ||
| 373 | - if (params == null) { | ||
| 374 | - return null; | ||
| 375 | - } | ||
| 376 | - try { | ||
| 377 | - JSONObject paramsJson = params instanceof JSONObject ? (JSONObject) params | ||
| 378 | - : JSONUtil.parseObj(params.toString()); | ||
| 379 | - String clientId = paramsJson.getStr("clientId"); | ||
| 380 | - String username = paramsJson.getStr("username"); | ||
| 381 | - String password = paramsJson.getStr("password"); | ||
| 382 | - return StrUtil.hasBlank(clientId, username, password) ? null | ||
| 383 | - : new AuthParams(clientId, username, password); | ||
| 384 | - } catch (Exception e) { | ||
| 385 | - log.warn("[parseAuthParams][解析认证参数失败]", e); | ||
| 386 | - return null; | ||
| 387 | - } | ||
| 388 | - } | ||
| 389 | - | ||
| 390 | /** | 319 | /** |
| 391 | - * 认证设备 | 320 | + * 验证设备认证信息 |
| 321 | + * | ||
| 322 | + * @param authParams 认证参数 | ||
| 323 | + * @return 是否认证成功 | ||
| 392 | */ | 324 | */ |
| 393 | - private boolean authenticateDevice(AuthParams authParams) { | 325 | + private boolean validateDeviceAuth(IotDeviceAuthReqDTO authParams) { |
| 394 | try { | 326 | try { |
| 395 | CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() | 327 | CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO() |
| 396 | - .setClientId(authParams.clientId) | ||
| 397 | - .setUsername(authParams.username) | ||
| 398 | - .setPassword(authParams.password)); | ||
| 399 | - return result.isSuccess() && Boolean.TRUE.equals(result.getData()); | 328 | + .setClientId(authParams.getClientId()).setUsername(authParams.getUsername()) |
| 329 | + .setPassword(authParams.getPassword())); | ||
| 330 | + result.checkError(); | ||
| 331 | + return BooleanUtil.isTrue(result.getData()); | ||
| 400 | } catch (Exception e) { | 332 | } catch (Exception e) { |
| 401 | - log.error("[authenticateDevice][设备认证异常,username: {}]", authParams.username, e); | 333 | + log.error("[validateDeviceAuth][设备认证异常,username: {}]", authParams.getUsername(), e); |
| 402 | return false; | 334 | return false; |
| 403 | } | 335 | } |
| 404 | } | 336 | } |
| 405 | 337 | ||
| 406 | - // TODO @haohao:改成 sendErrorResponse sendSuccessResponse 更清晰点? | ||
| 407 | - | ||
| 408 | /** | 338 | /** |
| 409 | * 发送错误响应 | 339 | * 发送错误响应 |
| 340 | + * | ||
| 341 | + * @param socket 网络连接 | ||
| 342 | + * @param requestId 请求 ID | ||
| 343 | + * @param errorMessage 错误消息 | ||
| 344 | + * @param codecType 消息编解码类型 | ||
| 410 | */ | 345 | */ |
| 411 | - private void sendError(NetSocket socket, String requestId, String errorMessage, String codecType) { | 346 | + private void sendErrorResponse(NetSocket socket, String requestId, String errorMessage, String codecType) { |
| 412 | sendResponse(socket, false, errorMessage, requestId, codecType); | 347 | sendResponse(socket, false, errorMessage, requestId, codecType); |
| 413 | } | 348 | } |
| 414 | 349 | ||
| 415 | /** | 350 | /** |
| 416 | * 发送成功响应 | 351 | * 发送成功响应 |
| 352 | + * | ||
| 353 | + * @param socket 网络连接 | ||
| 354 | + * @param requestId 请求 ID | ||
| 355 | + * @param message 消息 | ||
| 356 | + * @param codecType 消息编解码类型 | ||
| 417 | */ | 357 | */ |
| 418 | - private void sendSuccess(NetSocket socket, String requestId, String message, String codecType) { | 358 | + private void sendSuccessResponse(NetSocket socket, String requestId, String message, String codecType) { |
| 419 | sendResponse(socket, true, message, requestId, codecType); | 359 | sendResponse(socket, true, message, requestId, codecType); |
| 420 | } | 360 | } |
| 421 | 361 | ||
| 422 | - // ==================== 内部类 ==================== | ||
| 423 | - | ||
| 424 | - // TODO @haohao:IotDeviceAuthReqDTO 复用这个? | ||
| 425 | - /** | ||
| 426 | - * 认证参数 | ||
| 427 | - */ | ||
| 428 | - @Data | ||
| 429 | - @AllArgsConstructor | ||
| 430 | - private static class AuthParams { | ||
| 431 | - | ||
| 432 | - private final String clientId; | ||
| 433 | - private final String username; | ||
| 434 | - private final String password; | ||
| 435 | - | ||
| 436 | - } | ||
| 437 | - | ||
| 438 | - /** | ||
| 439 | - * 消息信息 | ||
| 440 | - */ | ||
| 441 | - @Data | ||
| 442 | - @AllArgsConstructor | ||
| 443 | - private static class MessageInfo { | ||
| 444 | - | ||
| 445 | - private final IotDeviceMessage message; | ||
| 446 | - | ||
| 447 | - private final String codecType; | ||
| 448 | - | ||
| 449 | - } | ||
| 450 | } | 362 | } |
| 451 | \ No newline at end of file | 363 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md
| @@ -9,7 +9,7 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | @@ -9,7 +9,7 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | ||
| 9 | - **é«˜æ•ˆä¼ è¾“**ï¼šå®Œå…¨äºŒè¿›åˆ¶æ ¼å¼ï¼Œå‡å°‘æ•°æ®ä¼ è¾“é‡ | 9 | - **é«˜æ•ˆä¼ è¾“**ï¼šå®Œå…¨äºŒè¿›åˆ¶æ ¼å¼ï¼Œå‡å°‘æ•°æ®ä¼ è¾“é‡ |
| 10 | - **版本控制**:内置å议版本å·ï¼Œæ”¯æŒåè®®å‡çº§ | 10 | - **版本控制**:内置å议版本å·ï¼Œæ”¯æŒåè®®å‡çº§ |
| 11 | - **类型安全**:明确的消æ¯ç±»åž‹æ ‡è¯† | 11 | - **类型安全**:明确的消æ¯ç±»åž‹æ ‡è¯† |
| 12 | -- **扩展性**ï¼šé¢„ç•™æ ‡å¿—ä½ï¼Œæ”¯æŒæœªæ¥åŠŸèƒ½æ‰©å±• | 12 | +- **简æ´è®¾è®¡**ï¼šåŽ»é™¤å†—ä½™å—æ®µï¼Œåè®®æ›´åŠ ç²¾ç®€ |
| 13 | - **兼容性**:与现有 `IotDeviceMessage` 接å£å®Œå…¨å…¼å®¹ | 13 | - **兼容性**:与现有 `IotDeviceMessage` 接å£å®Œå…¨å…¼å®¹ |
| 14 | 14 | ||
| 15 | ## 2. åè®®æ ¼å¼ | 15 | ## 2. åè®®æ ¼å¼ |
| @@ -17,9 +17,9 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | @@ -17,9 +17,9 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | ||
| 17 | ### 2.1 整体结构 | 17 | ### 2.1 整体结构 |
| 18 | 18 | ||
| 19 | ``` | 19 | ``` |
| 20 | -+--------+--------+--------+--------+--------+--------+--------+--------+ | ||
| 21 | -| 锿œ¯å— | ç‰ˆæœ¬å· | 消æ¯ç±»åž‹| æ¶ˆæ¯æ ‡å¿—| 消æ¯é•¿åº¦(4å—节) | | ||
| 22 | -+--------+--------+--------+--------+--------+--------+--------+--------+ | 20 | ++--------+--------+--------+---------------------------+--------+--------+ |
| 21 | +| 锿œ¯å— | ç‰ˆæœ¬å· | 消æ¯ç±»åž‹| 消æ¯é•¿åº¦(4å—节) | | ||
| 22 | ++--------+--------+--------+---------------------------+--------+--------+ | ||
| 23 | | æ¶ˆæ¯ ID 长度(2å—节) | æ¶ˆæ¯ ID (å˜é•¿å—符串) | | 23 | | æ¶ˆæ¯ ID 长度(2å—节) | æ¶ˆæ¯ ID (å˜é•¿å—符串) | |
| 24 | +--------+--------+--------+--------+--------+--------+--------+--------+ | 24 | +--------+--------+--------+--------+--------+--------+--------+--------+ |
| 25 | | 方法å长度(2å—节) | 方法å(å˜é•¿å—符串) | | 25 | | 方法å长度(2å—节) | 方法å(å˜é•¿å—符串) | |
| @@ -35,7 +35,6 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | @@ -35,7 +35,6 @@ TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义åè®®æ ¼å¼ï¼Œé‡‡ç”¨ç´§å‡‘的二è | ||
| 35 | | 锿œ¯å— | 1å—节 | byte | `0x7E` - åè®®è¯†åˆ«æ ‡è¯†ï¼Œç”¨äºŽæ•°æ®åŒæ¥ | | 35 | | 锿œ¯å— | 1å—节 | byte | `0x7E` - åè®®è¯†åˆ«æ ‡è¯†ï¼Œç”¨äºŽæ•°æ®åŒæ¥ | |
| 36 | | ç‰ˆæœ¬å· | 1å—节 | byte | `0x01` - å议版本å·ï¼Œæ”¯æŒç‰ˆæœ¬æŽ§åˆ¶ | | 36 | | ç‰ˆæœ¬å· | 1å—节 | byte | `0x01` - å议版本å·ï¼Œæ”¯æŒç‰ˆæœ¬æŽ§åˆ¶ | |
| 37 | | 消æ¯ç±»åž‹ | 1å—节 | byte | `0x01`=请求, `0x02`=å“应 | | 37 | | 消æ¯ç±»åž‹ | 1å—节 | byte | `0x01`=请求, `0x02`=å“应 | |
| 38 | -| æ¶ˆæ¯æ ‡å¿— | 1å—节 | byte | é¢„ç•™å—æ®µï¼Œç”¨äºŽæœªæ¥æ‰©å±• | | ||
| 39 | | 消æ¯é•¿åº¦ | 4å—节 | int | 整个消æ¯çš„æ€»é•¿åº¦ï¼ˆåŒ…å«å¤´éƒ¨ï¼‰ | | 38 | | 消æ¯é•¿åº¦ | 4å—节 | int | 整个消æ¯çš„æ€»é•¿åº¦ï¼ˆåŒ…å«å¤´éƒ¨ï¼‰ | |
| 40 | | æ¶ˆæ¯ ID 长度 | 2å—节 | short | æ¶ˆæ¯ ID å—符串的å—节长度 | | 39 | | æ¶ˆæ¯ ID 长度 | 2å—节 | short | æ¶ˆæ¯ ID å—符串的å—节长度 | |
| 41 | | æ¶ˆæ¯ ID | å˜é•¿ | string | 消æ¯å”¯ä¸€æ ‡è¯†ç¬¦ï¼ˆUTF-8ç¼–ç ) | | 40 | | æ¶ˆæ¯ ID | å˜é•¿ | string | 消æ¯å”¯ä¸€æ ‡è¯†ç¬¦ï¼ˆUTF-8ç¼–ç ) | |
| @@ -53,14 +52,12 @@ private static final byte MAGIC_NUMBER = (byte) 0x7E; | @@ -53,14 +52,12 @@ private static final byte MAGIC_NUMBER = (byte) 0x7E; | ||
| 53 | private static final byte PROTOCOL_VERSION = (byte) 0x01; | 52 | private static final byte PROTOCOL_VERSION = (byte) 0x01; |
| 54 | 53 | ||
| 55 | // 消æ¯ç±»åž‹ | 54 | // 消æ¯ç±»åž‹ |
| 56 | -public static class MessageType { | ||
| 57 | - public static final byte REQUEST = 0x01; // è¯·æ±‚æ¶ˆæ¯ | ||
| 58 | - public static final byte RESPONSE = 0x02; // å“åº”æ¶ˆæ¯ | ||
| 59 | -} | 55 | +private static final byte REQUEST = (byte) 0x01; // è¯·æ±‚æ¶ˆæ¯ |
| 56 | +private static final byte RESPONSE = (byte) 0x02; // å“åº”æ¶ˆæ¯ | ||
| 60 | 57 | ||
| 61 | // å议长度 | 58 | // å议长度 |
| 62 | -private static final int HEADER_FIXED_LENGTH = 8; // 固定头部长度 | ||
| 63 | -private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ | 59 | +private static final int HEADER_FIXED_LENGTH = 7; // 固定头部长度 |
| 60 | +private static final int MIN_MESSAGE_LENGTH = 11; // æœ€å°æ¶ˆæ¯é•¿åº¦ | ||
| 64 | ``` | 61 | ``` |
| 65 | 62 | ||
| 66 | ## 3. 消æ¯ç±»åž‹å’Œæ ¼å¼ | 63 | ## 3. 消æ¯ç±»åž‹å’Œæ ¼å¼ |
| @@ -86,8 +83,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ | @@ -86,8 +83,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ | ||
| 86 | 7E // 锿œ¯å— (0x7E) | 83 | 7E // 锿œ¯å— (0x7E) |
| 87 | 01 // ç‰ˆæœ¬å· (0x01) | 84 | 01 // ç‰ˆæœ¬å· (0x01) |
| 88 | 01 // 消æ¯ç±»åž‹ (REQUEST) | 85 | 01 // 消æ¯ç±»åž‹ (REQUEST) |
| 89 | -00 // æ¶ˆæ¯æ ‡å¿— (预留) | ||
| 90 | -00 00 00 8A // 消æ¯é•¿åº¦ (138å—节) | 86 | +00 00 00 89 // 消æ¯é•¿åº¦ (137å—节) |
| 91 | 00 19 // æ¶ˆæ¯ ID 长度 (25å—节) | 87 | 00 19 // æ¶ˆæ¯ ID 长度 (25å—节) |
| 92 | 61 75 74 68 5F 31 37 30 34 30 // æ¶ˆæ¯ ID: "auth_1704067200000_123" | 88 | 61 75 74 68 5F 31 37 30 34 30 // æ¶ˆæ¯ ID: "auth_1704067200000_123" |
| 93 | 36 37 32 30 30 30 30 30 5F 31 | 89 | 36 37 32 30 30 30 30 30 5F 31 |
| @@ -144,8 +140,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ | @@ -144,8 +140,7 @@ private static final int MIN_MESSAGE_LENGTH = 12; // æœ€å°æ¶ˆæ¯é•¿åº¦ | ||
| 144 | 7E // 锿œ¯å— (0x7E) | 140 | 7E // 锿œ¯å— (0x7E) |
| 145 | 01 // ç‰ˆæœ¬å· (0x01) | 141 | 01 // ç‰ˆæœ¬å· (0x01) |
| 146 | 02 // 消æ¯ç±»åž‹ (RESPONSE) | 142 | 02 // 消æ¯ç±»åž‹ (RESPONSE) |
| 147 | -00 // æ¶ˆæ¯æ ‡å¿— (预留) | ||
| 148 | -00 00 00 A5 // 消æ¯é•¿åº¦ (165å—节) | 143 | +00 00 00 A4 // 消æ¯é•¿åº¦ (164å—节) |
| 149 | 00 22 // æ¶ˆæ¯ ID 长度 (34å—节) | 144 | 00 22 // æ¶ˆæ¯ ID 长度 (34å—节) |
| 150 | 61 75 74 68 5F 72 65 73 70 6F // æ¶ˆæ¯ ID: "auth_response_1704067200000_123" | 145 | 61 75 74 68 5F 72 65 73 70 6F // æ¶ˆæ¯ ID: "auth_response_1704067200000_123" |
| 151 | 6E 73 65 5F 31 37 30 34 30 36 | 146 | 6E 73 65 5F 31 37 30 34 30 36 |
| @@ -175,19 +170,19 @@ public static final String TYPE = "TCP_BINARY"; | @@ -175,19 +170,19 @@ public static final String TYPE = "TCP_BINARY"; | ||
| 175 | - **æ•°æ®ç´§å‡‘**ï¼šäºŒè¿›åˆ¶æ ¼å¼ï¼Œç›¸æ¯” JSON å‡å°‘ 30-50% 的数æ®é‡ | 170 | - **æ•°æ®ç´§å‡‘**ï¼šäºŒè¿›åˆ¶æ ¼å¼ï¼Œç›¸æ¯” JSON å‡å°‘ 30-50% 的数æ®é‡ |
| 176 | - **è§£æžé«˜æ•ˆ**:直接二进制æ“作,å‡å°‘å—符串转æ¢å¼€é”€ | 171 | - **è§£æžé«˜æ•ˆ**:直接二进制æ“作,å‡å°‘å—符串转æ¢å¼€é”€ |
| 177 | - **类型安全**:明确的消æ¯ç±»åž‹å’Œå—段定义 | 172 | - **类型安全**:明确的消æ¯ç±»åž‹å’Œå—段定义 |
| 178 | -- **扩展性强**ï¼šé¢„ç•™æ ‡å¿—ä½æ”¯æŒæœªæ¥åŠŸèƒ½æ‰©å±• | 173 | +- **设计简æ´**ï¼šåŽ»é™¤å†—ä½™å—æ®µï¼Œåè®®æ›´åŠ ç²¾ç®€é«˜æ•ˆ |
| 179 | - **版本控制**ï¼šå†…ç½®ç‰ˆæœ¬å·æ”¯æŒåè®®å‡çº§ | 174 | - **版本控制**ï¼šå†…ç½®ç‰ˆæœ¬å·æ”¯æŒåè®®å‡çº§ |
| 180 | 175 | ||
| 181 | ## 6. 与 JSON å议对比 | 176 | ## 6. 与 JSON å议对比 |
| 182 | 177 | ||
| 183 | -| 特性 | 二进制åè®® | JSONåè®® | | ||
| 184 | -|------|------------|----------| | ||
| 185 | -| æ•°æ®å¤§å° | å°ï¼ˆèŠ‚çœ30-50%) | 大 | | ||
| 186 | -| è§£æžæ€§èƒ½ | 高 | ä¸ç‰ | | ||
| 187 | -| 网络开销 | 低 | 高 | | ||
| 188 | -| å¯è¯»æ€§ | å·® | 优秀 | | ||
| 189 | -| 调试难度 | 高 | 低 | | ||
| 190 | -| 扩展性 | 良好(有预留ä½ï¼‰ | 优秀 | | 178 | +| 特性 | 二进制åè®® | JSONåè®® | |
| 179 | +|------|-------------|--------| | ||
| 180 | +| æ•°æ®å¤§å° | å°ï¼ˆèŠ‚çœ30-50%) | 大 | | ||
| 181 | +| è§£æžæ€§èƒ½ | 高 | ä¸ç‰ | | ||
| 182 | +| 网络开销 | 低 | 高 | | ||
| 183 | +| å¯è¯»æ€§ | å·® | 优秀 | | ||
| 184 | +| 调试难度 | 高 | 低 | | ||
| 185 | +| 扩展性 | 良好 | 优秀 | | ||
| 191 | 186 | ||
| 192 | **推è场景**: | 187 | **推è场景**: |
| 193 | - ✅ **高频数æ®ä¼ 输**ï¼šä¼ æ„Ÿå™¨æ•°æ®å®žæ—¶ä¸ŠæŠ¥ | 188 | - ✅ **高频数æ®ä¼ 输**ï¼šä¼ æ„Ÿå™¨æ•°æ®å®žæ—¶ä¸ŠæŠ¥ |