Commit 6a117c9d550ac8bc9448d0eac23b8512dbccf515
1 parent
bb1210a1
review:【IoT 物联网】TCP 网络接入的实现
Showing
11 changed files
with
114 additions
and
121 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpDeviceMessageCodec.java
| ... | ... | @@ -54,6 +54,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 54 | 54 | |
| 55 | 55 | static { |
| 56 | 56 | // 初始化方法映射 |
| 57 | + // TODO @haohao:有没可能去掉这个 code 到 method 的映射哈? | |
| 57 | 58 | initializeMethodMappings(); |
| 58 | 59 | } |
| 59 | 60 | |
| ... | ... | @@ -75,6 +76,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 75 | 76 | * 负载字段名 |
| 76 | 77 | */ |
| 77 | 78 | public static class PayloadField { |
| 79 | + | |
| 78 | 80 | public static final String TIMESTAMP = "timestamp"; |
| 79 | 81 | public static final String MESSAGE_ID = "msgId"; |
| 80 | 82 | public static final String DEVICE_ID = "deviceId"; |
| ... | ... | @@ -82,12 +84,14 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 82 | 84 | public static final String DATA = "data"; |
| 83 | 85 | public static final String CODE = "code"; |
| 84 | 86 | public static final String MESSAGE = "message"; |
| 87 | + | |
| 85 | 88 | } |
| 86 | 89 | |
| 87 | 90 | /** |
| 88 | 91 | * 消息方法映射 |
| 89 | 92 | */ |
| 90 | 93 | public static class MessageMethod { |
| 94 | + | |
| 91 | 95 | public static final String PROPERTY_POST = "thing.property.post"; |
| 92 | 96 | public static final String PROPERTY_SET = "thing.property.set"; |
| 93 | 97 | public static final String PROPERTY_GET = "thing.property.get"; |
| ... | ... | @@ -97,6 +101,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 97 | 101 | public static final String OTA_UPGRADE = "thing.ota.upgrade"; |
| 98 | 102 | public static final String STATE_ONLINE = "thing.state.online"; |
| 99 | 103 | public static final String STATE_OFFLINE = "thing.state.offline"; |
| 104 | + | |
| 100 | 105 | } |
| 101 | 106 | |
| 102 | 107 | // ==================== 初始化方法 ==================== |
| ... | ... | @@ -139,9 +144,9 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 139 | 144 | |
| 140 | 145 | // 3. 构建 TCP 数据包 |
| 141 | 146 | TcpDataPackage dataPackage = TcpDataPackage.builder() |
| 142 | - .addr("") // 地址在发送时由调用方设置 | |
| 147 | + .addr("") | |
| 143 | 148 | .code(code) |
| 144 | - .mid((short) 0) // 消息序号在发送时由调用方设置 | |
| 149 | + .mid((short) 0) | |
| 145 | 150 | .payload(payload) |
| 146 | 151 | .build(); |
| 147 | 152 | |
| ... | ... | @@ -154,9 +159,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 154 | 159 | log.debug("[encode][TCP 消息编码成功] 方法: {}, 数据长度: {}", |
| 155 | 160 | message.getMethod(), result.length); |
| 156 | 161 | } |
| 157 | - | |
| 158 | 162 | return result; |
| 159 | - | |
| 160 | 163 | } catch (Exception e) { |
| 161 | 164 | log.error("[encode][TCP 消息编码失败] 消息: {}", message, e); |
| 162 | 165 | throw new TcpCodecException("TCP 消息编码失败", e); |
| ... | ... | @@ -175,13 +178,10 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 175 | 178 | // 1. 解码 TCP 数据包 |
| 176 | 179 | Buffer buffer = Buffer.buffer(bytes); |
| 177 | 180 | TcpDataPackage dataPackage = TcpDataDecoder.decode(buffer); |
| 178 | - | |
| 179 | 181 | // 2. 获取消息方法 |
| 180 | 182 | String method = getMethodByCodeSafely(dataPackage.getCode()); |
| 181 | - | |
| 182 | 183 | // 3. 解析负载数据 |
| 183 | 184 | Object params = parsePayloadOptimized(dataPackage.getPayload()); |
| 184 | - | |
| 185 | 185 | // 4. 构建 IoT 设备消息 |
| 186 | 186 | IotDeviceMessage message = IotDeviceMessage.requestOf(method, params); |
| 187 | 187 | |
| ... | ... | @@ -190,9 +190,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 190 | 190 | log.debug("[decode][TCP 消息解码成功] 方法: {}, 功能码: {}", |
| 191 | 191 | method, dataPackage.getCode()); |
| 192 | 192 | } |
| 193 | - | |
| 194 | 193 | return message; |
| 195 | - | |
| 196 | 194 | } catch (Exception e) { |
| 197 | 195 | log.error("[decode][TCP 消息解码失败] 数据长度: {}, 数据内容: {}", |
| 198 | 196 | bytes.length, truncateData(bytes, 100), e); |
| ... | ... | @@ -226,8 +224,8 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 226 | 224 | if (Objects.isNull(bytes) || bytes.length == 0) { |
| 227 | 225 | throw new IllegalArgumentException("待解码数据不能为空"); |
| 228 | 226 | } |
| 229 | - if (bytes.length > 1024 * 1024) { // 1MB 限制 | |
| 230 | - throw new IllegalArgumentException("数据包过大,超过1MB限制"); | |
| 227 | + if (bytes.length > 1024 * 1024) { | |
| 228 | + throw new IllegalArgumentException("数据包过大,超过 1MB 限制"); | |
| 231 | 229 | } |
| 232 | 230 | } |
| 233 | 231 | |
| ... | ... | @@ -236,9 +234,10 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 236 | 234 | */ |
| 237 | 235 | private short getCodeByMethodSafely(String method) { |
| 238 | 236 | Short code = METHOD_TO_CODE_MAP.get(method); |
| 237 | + // 默认为数据上报 | |
| 239 | 238 | if (code == null) { |
| 240 | 239 | log.warn("[getCodeByMethodSafely][未知的消息方法: {},使用默认功能码]", method); |
| 241 | - return TcpDataPackage.CODE_DATA_UP; // 默认为数据上报 | |
| 240 | + return TcpDataPackage.CODE_DATA_UP; | |
| 242 | 241 | } |
| 243 | 242 | return code; |
| 244 | 243 | } |
| ... | ... | @@ -260,6 +259,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 260 | 259 | */ |
| 261 | 260 | private String buildPayloadOptimized(IotDeviceMessage message) { |
| 262 | 261 | // 使用缓存键 |
| 262 | + // TODO @haohao:是不是不用缓存哈? | |
| 263 | 263 | String cacheKey = message.getMethod() + "_" + message.getRequestId(); |
| 264 | 264 | JSONObject cachedPayload = jsonCache.get(cacheKey); |
| 265 | 265 | |
| ... | ... | @@ -271,7 +271,6 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 271 | 271 | |
| 272 | 272 | // 创建新的负载 |
| 273 | 273 | JSONObject payload = new JSONObject(); |
| 274 | - | |
| 275 | 274 | // 添加基础字段 |
| 276 | 275 | addToPayloadIfNotNull(payload, PayloadField.MESSAGE_ID, message.getRequestId()); |
| 277 | 276 | addToPayloadIfNotNull(payload, PayloadField.DEVICE_ID, message.getDeviceId()); |
| ... | ... | @@ -279,7 +278,6 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 279 | 278 | addToPayloadIfNotNull(payload, PayloadField.DATA, message.getData()); |
| 280 | 279 | addToPayloadIfNotNull(payload, PayloadField.CODE, message.getCode()); |
| 281 | 280 | addToPayloadIfNotEmpty(payload, PayloadField.MESSAGE, message.getMsg()); |
| 282 | - | |
| 283 | 281 | // 添加时间戳 |
| 284 | 282 | payload.set(PayloadField.TIMESTAMP, System.currentTimeMillis()); |
| 285 | 283 | |
| ... | ... | @@ -317,7 +315,6 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 317 | 315 | } |
| 318 | 316 | |
| 319 | 317 | return jsonObject.containsKey(PayloadField.PARAMS) ? jsonObject.get(PayloadField.PARAMS) : jsonObject; |
| 320 | - | |
| 321 | 318 | } catch (JSONException e) { |
| 322 | 319 | log.warn("[parsePayloadOptimized][负载解析为JSON失败,返回原始字符串] 负载: {}", payload); |
| 323 | 320 | return payload; |
| ... | ... | @@ -379,6 +376,7 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 379 | 376 | * TCP 编解码异常 |
| 380 | 377 | */ |
| 381 | 378 | public static class TcpCodecException extends RuntimeException { |
| 379 | + | |
| 382 | 380 | public TcpCodecException(String message) { |
| 383 | 381 | super(message); |
| 384 | 382 | } |
| ... | ... | @@ -386,5 +384,6 @@ public class IotTcpDeviceMessageCodec implements IotDeviceMessageCodec { |
| 386 | 384 | public TcpCodecException(String message, Throwable cause) { |
| 387 | 385 | super(message, cause); |
| 388 | 386 | } |
| 387 | + | |
| 389 | 388 | } |
| 390 | 389 | } |
| 391 | 390 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java
| ... | ... | @@ -108,16 +108,14 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic |
| 108 | 108 | processedMessages.incrementAndGet(); |
| 109 | 109 | |
| 110 | 110 | if (log.isDebugEnabled()) { |
| 111 | - log.debug("[onMessage][收到下行消息] 设备ID: {}, 方法: {}, 消息ID: {}", | |
| 111 | + log.debug("[onMessage][收到下行消息] 设备 ID: {}, 方法: {}, 消息ID: {}", | |
| 112 | 112 | message.getDeviceId(), message.getMethod(), message.getId()); |
| 113 | 113 | } |
| 114 | - | |
| 115 | 114 | // 参数校验 |
| 116 | 115 | if (message.getDeviceId() == null) { |
| 117 | - log.warn("[onMessage][下行消息设备ID为空,跳过处理] 消息: {}", message); | |
| 116 | + log.warn("[onMessage][下行消息设备 ID 为空,跳过处理] 消息: {}", message); | |
| 118 | 117 | return; |
| 119 | 118 | } |
| 120 | - | |
| 121 | 119 | // 检查连接状态 |
| 122 | 120 | if (connectionManager.getClientByDeviceId(message.getDeviceId()) == null) { |
| 123 | 121 | log.warn("[onMessage][设备({})离线,跳过下行消息] 方法: {}", |
| ... | ... | @@ -130,11 +128,11 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic |
| 130 | 128 | |
| 131 | 129 | // 性能监控 |
| 132 | 130 | long processTime = System.currentTimeMillis() - startTime; |
| 133 | - if (processTime > 1000) { // 超过1秒的慢消息 | |
| 131 | + // TODO @haohao:1000 搞成静态变量; | |
| 132 | + if (processTime > 1000) { // 超过 1 秒的慢消息 | |
| 134 | 133 | log.warn("[onMessage][慢消息处理] 设备ID: {}, 方法: {}, 耗时: {}ms", |
| 135 | 134 | message.getDeviceId(), message.getMethod(), processTime); |
| 136 | 135 | } |
| 137 | - | |
| 138 | 136 | } catch (Exception e) { |
| 139 | 137 | failedMessages.incrementAndGet(); |
| 140 | 138 | log.error("[onMessage][处理下行消息失败] 设备ID: {}, 方法: {}, 消息: {}", |
| ... | ... | @@ -142,6 +140,8 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic |
| 142 | 140 | } |
| 143 | 141 | } |
| 144 | 142 | |
| 143 | + // TODO @haohao:多余的要不先清理掉; | |
| 144 | + | |
| 145 | 145 | /** |
| 146 | 146 | * 获取订阅者统计信息 |
| 147 | 147 | */ |
| ... | ... | @@ -160,4 +160,5 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDevic |
| 160 | 160 | public boolean isHealthy() { |
| 161 | 161 | return initialized.get() && downstreamHandler != null; |
| 162 | 162 | } |
| 163 | + | |
| 163 | 164 | } |
| 164 | 165 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java
| ... | ... | @@ -92,6 +92,7 @@ public class IotTcpUpstreamProtocol { |
| 92 | 92 | * 启动 TCP 服务器 |
| 93 | 93 | */ |
| 94 | 94 | private void startTcpServer() { |
| 95 | + // TODO @haohao:同类的,最好使用相同序号前缀,一个方法看起来有段落感。包括同类可以去掉之间的空格。例如说这里的,1. 2. 3. 4. 是初始化;5. 6. 是管理启动 | |
| 95 | 96 | // 1. 创建服务器选项 |
| 96 | 97 | NetServerOptions options = new NetServerOptions() |
| 97 | 98 | .setPort(tcpProperties.getPort()) | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/client/TcpDeviceClient.java
| ... | ... | @@ -10,9 +10,8 @@ import lombok.extern.slf4j.Slf4j; |
| 10 | 10 | import java.util.concurrent.atomic.AtomicBoolean; |
| 11 | 11 | |
| 12 | 12 | /** |
| 13 | - * TCP 设备客户端 | |
| 13 | + * TCP 设备客户端:封装设备连接的基本信息和操作 | |
| 14 | 14 | * <p> |
| 15 | - * 封装设备连接的基本信息和操作。 | |
| 16 | 15 | * 该类中的状态变更(如 authenticated, closed)使用 AtomicBoolean 保证原子性。 |
| 17 | 16 | * 对 socket 的操作应在 Vert.x Event Loop 线程中执行,以避免并发问题。 |
| 18 | 17 | * |
| ... | ... | @@ -48,7 +47,7 @@ public class TcpDeviceClient { |
| 48 | 47 | private RecordParser parser; |
| 49 | 48 | |
| 50 | 49 | @Getter |
| 51 | - private final long keepAliveTimeoutMs; // 改为 final,通过构造函数注入 | |
| 50 | + private final long keepAliveTimeoutMs; | |
| 52 | 51 | |
| 53 | 52 | private volatile long lastKeepAliveTime; |
| 54 | 53 | |
| ... | ... | @@ -58,7 +57,7 @@ public class TcpDeviceClient { |
| 58 | 57 | /** |
| 59 | 58 | * 构造函数 |
| 60 | 59 | * |
| 61 | - * @param clientId 客户端ID,全局唯一 | |
| 60 | + * @param clientId 客户端 ID,全局唯一 | |
| 62 | 61 | * @param keepAliveTimeoutMs 心跳超时时间(毫秒),从配置中读取 |
| 63 | 62 | */ |
| 64 | 63 | public TcpDeviceClient(String clientId, long keepAliveTimeoutMs) { |
| ... | ... | @@ -69,19 +68,19 @@ public class TcpDeviceClient { |
| 69 | 68 | |
| 70 | 69 | /** |
| 71 | 70 | * 绑定网络套接字,并设置相关处理器。 |
| 72 | - * 此方法应在 Vert.x Event Loop 线程中调用。 | |
| 71 | + * 此方法应在 Vert.x Event Loop 线程中调用 | |
| 73 | 72 | * |
| 74 | 73 | * @param socket 网络套接字 |
| 75 | 74 | */ |
| 76 | 75 | public void setSocket(NetSocket socket) { |
| 77 | 76 | // 无需 synchronized,Vert.x 保证了同一个 socket 的事件在同一个 Event Loop 中处理 |
| 78 | 77 | if (this.socket != null && this.socket != socket) { |
| 79 | - log.warn("[setSocket][客户端({})] 正在用新的 socket 替换旧的,旧 socket 将被关闭。", clientId); | |
| 78 | + log.warn("[setSocket][客户端({}) 正在用新的 socket 替换旧的,旧 socket 将被关闭]", clientId); | |
| 80 | 79 | this.socket.close(); |
| 81 | 80 | } |
| 82 | - | |
| 83 | 81 | this.socket = socket; |
| 84 | 82 | |
| 83 | + // 注册处理器 | |
| 85 | 84 | if (socket != null) { |
| 86 | 85 | // 1. 设置关闭处理器 |
| 87 | 86 | socket.closeHandler(v -> { |
| ... | ... | @@ -103,22 +102,22 @@ public class TcpDeviceClient { |
| 103 | 102 | if (parser != null) { |
| 104 | 103 | parser.handle(buffer); |
| 105 | 104 | } else { |
| 106 | - log.warn("[setSocket][设备客户端({})] 未设置解析器(parser),原始数据被忽略: {}", clientId, buffer.toString()); | |
| 105 | + log.warn("[setSocket][设备客户端({}) 未设置解析器(parser),原始数据被忽略: {}]", clientId, buffer.toString()); | |
| 107 | 106 | } |
| 108 | 107 | }); |
| 109 | 108 | } |
| 110 | 109 | } |
| 111 | 110 | |
| 112 | 111 | /** |
| 113 | - * 更新心跳时间,表示设备仍然活跃。 | |
| 112 | + * 更新心跳时间,表示设备仍然活跃 | |
| 114 | 113 | */ |
| 115 | 114 | public void keepAlive() { |
| 116 | 115 | this.lastKeepAliveTime = System.currentTimeMillis(); |
| 117 | 116 | } |
| 118 | 117 | |
| 119 | 118 | /** |
| 120 | - * 检查连接是否在线。 | |
| 121 | - * 判断标准:未被主动关闭、socket 存在、且在心跳超时时间内。 | |
| 119 | + * 检查连接是否在线 | |
| 120 | + * 判断标准:未被主动关闭、socket 存在、且在心跳超时时间内 | |
| 122 | 121 | * |
| 123 | 122 | * @return 是否在线 |
| 124 | 123 | */ |
| ... | ... | @@ -130,6 +129,8 @@ public class TcpDeviceClient { |
| 130 | 129 | return idleTime < keepAliveTimeoutMs; |
| 131 | 130 | } |
| 132 | 131 | |
| 132 | + // TODO @haohao:1)是不是简化下:productKey 和 deviceName 非空,就认为是已认证;2)如果是的话,productKey 和 deviceName 搞成一个设置方法?setAuthenticated(productKey、deviceName) | |
| 133 | + | |
| 133 | 134 | public boolean isAuthenticated() { |
| 134 | 135 | return authenticated.get(); |
| 135 | 136 | } |
| ... | ... | @@ -139,7 +140,7 @@ public class TcpDeviceClient { |
| 139 | 140 | } |
| 140 | 141 | |
| 141 | 142 | /** |
| 142 | - * 向设备发送消息。 | |
| 143 | + * 向设备发送消息 | |
| 143 | 144 | * |
| 144 | 145 | * @param buffer 消息内容 |
| 145 | 146 | */ |
| ... | ... | @@ -151,18 +152,22 @@ public class TcpDeviceClient { |
| 151 | 152 | |
| 152 | 153 | // Vert.x 的 write 是异步的,不会阻塞 |
| 153 | 154 | socket.write(buffer, result -> { |
| 154 | - if (result.succeeded()) { | |
| 155 | - log.debug("[sendMessage][设备客户端({})发送消息成功]", clientId); | |
| 156 | - // 发送成功也更新心跳,表示连接活跃 | |
| 157 | - keepAlive(); | |
| 158 | - } else { | |
| 155 | + // 发送失败可能意味着连接已断开,主动关闭 | |
| 156 | + if (!result.succeeded()) { | |
| 159 | 157 | log.error("[sendMessage][设备客户端({})发送消息失败]", clientId, result.cause()); |
| 160 | - // 发送失败可能意味着连接已断开,主动关闭 | |
| 161 | 158 | shutdown(); |
| 159 | + return; | |
| 160 | + } | |
| 161 | + | |
| 162 | + // 发送成功也更新心跳,表示连接活跃 | |
| 163 | + if (log.isDebugEnabled()) { | |
| 164 | + log.debug("[sendMessage][设备客户端({})发送消息成功]", clientId); | |
| 162 | 165 | } |
| 166 | + keepAlive(); | |
| 163 | 167 | }); |
| 164 | 168 | } |
| 165 | 169 | |
| 170 | + // TODO @haohao:是不是叫 close 好点?或者问问大模型 | |
| 166 | 171 | /** |
| 167 | 172 | * 关闭客户端连接并清理资源。 |
| 168 | 173 | * 这是一个幂等操作,可以被多次安全调用。 |
| ... | ... | @@ -200,10 +205,6 @@ public class TcpDeviceClient { |
| 200 | 205 | return "disconnected"; |
| 201 | 206 | } |
| 202 | 207 | |
| 203 | - public long getLastKeepAliveTime() { | |
| 204 | - return lastKeepAliveTime; | |
| 205 | - } | |
| 206 | - | |
| 207 | 208 | @Override |
| 208 | 209 | public String toString() { |
| 209 | 210 | return "TcpDeviceClient{" + |
| ... | ... | @@ -215,4 +216,5 @@ public class TcpDeviceClient { |
| 215 | 216 | ", connection=" + getConnectionInfo() + |
| 216 | 217 | '}'; |
| 217 | 218 | } |
| 219 | + | |
| 218 | 220 | } |
| 219 | 221 | \ 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/TcpDeviceConnectionManager.java
| ... | ... | @@ -16,8 +16,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; |
| 16 | 16 | * 参考 EMQX 设计理念: |
| 17 | 17 | * 1. 高性能连接管理 |
| 18 | 18 | * 2. 连接池和资源管理 |
| 19 | - * 3. 流量控制 | |
| 20 | - * 4. 监控统计 | |
| 19 | + * 3. 流量控制 TODO @haohao:这个要不先去掉 | |
| 20 | + * 4. 监控统计 TODO @haohao:这个要不先去掉 | |
| 21 | 21 | * 5. 自动清理和容错 |
| 22 | 22 | * |
| 23 | 23 | * @author 芋道源码 |
| ... | ... | @@ -106,6 +106,7 @@ public class TcpDeviceConnectionManager { |
| 106 | 106 | * 添加设备客户端 |
| 107 | 107 | */ |
| 108 | 108 | public boolean addClient(String deviceAddr, TcpDeviceClient client) { |
| 109 | + // TODO @haohao:这个要不去掉;目前看着没做 result 的处理; | |
| 109 | 110 | if (clientMap.size() >= MAX_CONNECTIONS) { |
| 110 | 111 | log.warn("[addClient][连接数已达上限({}),拒绝新连接: {}]", MAX_CONNECTIONS, deviceAddr); |
| 111 | 112 | return false; |
| ... | ... | @@ -130,14 +131,13 @@ public class TcpDeviceConnectionManager { |
| 130 | 131 | socketToAddrMap.put(client.getSocket(), deviceAddr); |
| 131 | 132 | } |
| 132 | 133 | |
| 133 | - // 如果客户端已设置设备ID,更新映射 | |
| 134 | + // 如果客户端已设置设备 ID,更新映射 | |
| 134 | 135 | if (client.getDeviceId() != null) { |
| 135 | 136 | deviceIdToAddrMap.put(client.getDeviceId(), deviceAddr); |
| 136 | 137 | } |
| 137 | 138 | |
| 138 | 139 | totalConnections.incrementAndGet(); |
| 139 | 140 | return true; |
| 140 | - | |
| 141 | 141 | } finally { |
| 142 | 142 | writeLock.unlock(); |
| 143 | 143 | } |
| ... | ... | @@ -196,7 +196,7 @@ public class TcpDeviceConnectionManager { |
| 196 | 196 | } |
| 197 | 197 | |
| 198 | 198 | /** |
| 199 | - * 通过设备ID获取客户端 | |
| 199 | + * 通过设备 ID 获取客户端 | |
| 200 | 200 | */ |
| 201 | 201 | public TcpDeviceClient getClientByDeviceId(Long deviceId) { |
| 202 | 202 | readLock.lock(); |
| ... | ... | @@ -208,6 +208,8 @@ public class TcpDeviceConnectionManager { |
| 208 | 208 | } |
| 209 | 209 | } |
| 210 | 210 | |
| 211 | + // TODO @haohao:getClientBySocket、isDeviceOnline、sendMessage、sendMessageByDeviceId、broadcastMessage 用不到的方法,要不先暂时不提供?保持简洁、更容易理解哈。 | |
| 212 | + | |
| 211 | 213 | /** |
| 212 | 214 | * 通过网络连接获取客户端 |
| 213 | 215 | */ |
| ... | ... | @@ -230,7 +232,7 @@ public class TcpDeviceConnectionManager { |
| 230 | 232 | } |
| 231 | 233 | |
| 232 | 234 | /** |
| 233 | - * 设置设备ID映射 | |
| 235 | + * 设置设备 ID 映射 | |
| 234 | 236 | */ |
| 235 | 237 | public void setDeviceIdMapping(String deviceAddr, Long deviceId) { |
| 236 | 238 | writeLock.lock(); |
| ... | ... | @@ -349,12 +351,12 @@ public class TcpDeviceConnectionManager { |
| 349 | 351 | } |
| 350 | 352 | } |
| 351 | 353 | |
| 354 | + // TODO @haohao:心跳超时,需要 close 么? | |
| 352 | 355 | /** |
| 353 | 356 | * 心跳检查任务 |
| 354 | 357 | */ |
| 355 | 358 | private void checkHeartbeat() { |
| 356 | 359 | try { |
| 357 | - long currentTime = System.currentTimeMillis(); | |
| 358 | 360 | int offlineCount = 0; |
| 359 | 361 | |
| 360 | 362 | readLock.lock(); |
| ... | ... | @@ -369,7 +371,7 @@ public class TcpDeviceConnectionManager { |
| 369 | 371 | } |
| 370 | 372 | |
| 371 | 373 | if (offlineCount > 0) { |
| 372 | - log.info("[checkHeartbeat][发现{}个离线设备,将在清理任务中处理]", offlineCount); | |
| 374 | + log.info("[checkHeartbeat][发现 {} 个离线设备,将在清理任务中处理]", offlineCount); | |
| 373 | 375 | } |
| 374 | 376 | } catch (Exception e) { |
| 375 | 377 | log.error("[checkHeartbeat][心跳检查任务异常]", e); |
| ... | ... | @@ -424,14 +426,14 @@ public class TcpDeviceConnectionManager { |
| 424 | 426 | private void logStatistics() { |
| 425 | 427 | try { |
| 426 | 428 | long totalConn = totalConnections.get(); |
| 427 | - long totalDisconn = totalDisconnections.get(); | |
| 429 | + long totalDisconnections = this.totalDisconnections.get(); | |
| 428 | 430 | long totalMsg = totalMessages.get(); |
| 429 | 431 | long totalFailedMsg = totalFailedMessages.get(); |
| 430 | 432 | long totalBytesValue = totalBytes.get(); |
| 431 | 433 | |
| 432 | 434 | log.info("[logStatistics][连接统计] 总连接: {}, 总断开: {}, 当前在线: {}, 认证设备: {}, " + |
| 433 | 435 | "总消息: {}, 失败消息: {}, 总字节: {}", |
| 434 | - totalConn, totalDisconn, getOnlineCount(), getAuthenticatedCount(), | |
| 436 | + totalConn, totalDisconnections, getOnlineCount(), getAuthenticatedCount(), | |
| 435 | 437 | totalMsg, totalFailedMsg, totalBytesValue); |
| 436 | 438 | } catch (Exception e) { |
| 437 | 439 | log.error("[logStatistics][统计日志任务异常]", e); |
| ... | ... | @@ -500,4 +502,5 @@ public class TcpDeviceConnectionManager { |
| 500 | 502 | ? (double) (totalMessages.get() - totalFailedMessages.get()) / totalMessages.get() * 100 |
| 501 | 503 | : 0.0); |
| 502 | 504 | } |
| 505 | + | |
| 503 | 506 | } |
| 504 | 507 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/protocol/TcpDataDecoder.java
| ... | ... | @@ -3,13 +3,14 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.protocol; |
| 3 | 3 | import io.vertx.core.buffer.Buffer; |
| 4 | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | 5 | |
| 6 | +// TODO @haohao:“设备地址长度”是不是不需要。 | |
| 6 | 7 | /** |
| 7 | 8 | * TCP 数据解码器 |
| 8 | 9 | * <p> |
| 9 | 10 | * 负责将字节流解码为 TcpDataPackage 对象 |
| 10 | 11 | * <p> |
| 11 | 12 | * 数据包格式: |
| 12 | - * 包头(4字节长度) | 设备地址长度(2字节) | 设备地址(不定长) | 功能码(2字节) | 消息序号(2字节) | 包体(不定长) | |
| 13 | + * 包头(4 字节长度) | 设备地址长度(2 字节) | 设备地址(不定长) | 功能码(2 字节) | 消息序号(2 字节) | 包体(不定长) | |
| 13 | 14 | * |
| 14 | 15 | * @author 芋道源码 |
| 15 | 16 | */ |
| ... | ... | @@ -31,35 +32,35 @@ public class TcpDataDecoder { |
| 31 | 32 | try { |
| 32 | 33 | int index = 0; |
| 33 | 34 | |
| 34 | - // 1. 获取设备地址长度(2字节) | |
| 35 | + // 1.1 获取设备地址长度(2字节) | |
| 35 | 36 | short addrLength = buffer.getShort(index); |
| 36 | 37 | index += 2; |
| 37 | 38 | |
| 38 | - // 2. 校验数据包长度 | |
| 39 | + // 1.2 校验数据包长度 | |
| 39 | 40 | int expectedLength = 2 + addrLength + 2 + 2; // 地址长度 + 地址 + 功能码 + 消息序号 |
| 40 | 41 | if (buffer.length() < expectedLength) { |
| 41 | 42 | throw new IllegalArgumentException("数据包长度不足,期望至少 " + expectedLength + " 字节"); |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | - // 3. 获取设备地址 | |
| 45 | + // 1.3 获取设备地址 | |
| 45 | 46 | String addr = buffer.getBuffer(index, index + addrLength).toString(); |
| 46 | 47 | index += addrLength; |
| 47 | 48 | |
| 48 | - // 4. 获取功能码(2字节) | |
| 49 | + // 1.4 获取功能码(2字节) | |
| 49 | 50 | short code = buffer.getShort(index); |
| 50 | 51 | index += 2; |
| 51 | 52 | |
| 52 | - // 5. 获取消息序号(2字节) | |
| 53 | + // 1.5 获取消息序号(2字节) | |
| 53 | 54 | short mid = buffer.getShort(index); |
| 54 | 55 | index += 2; |
| 55 | 56 | |
| 56 | - // 6. 获取包体数据 | |
| 57 | + // 1.6 获取包体数据 | |
| 57 | 58 | String payload = ""; |
| 58 | 59 | if (index < buffer.length()) { |
| 59 | 60 | payload = buffer.getString(index, buffer.length()); |
| 60 | 61 | } |
| 61 | 62 | |
| 62 | - // 7. 构建数据包对象 | |
| 63 | + // 2. 构建数据包对象 | |
| 63 | 64 | TcpDataPackage dataPackage = TcpDataPackage.builder() |
| 64 | 65 | .addrLength((int) addrLength) |
| 65 | 66 | .addr(addr) |
| ... | ... | @@ -70,15 +71,14 @@ public class TcpDataDecoder { |
| 70 | 71 | |
| 71 | 72 | log.debug("[decode][解码成功] 设备地址: {}, 功能码: {}, 消息序号: {}, 包体长度: {}", |
| 72 | 73 | addr, dataPackage.getCodeDescription(), mid, payload.length()); |
| 73 | - | |
| 74 | 74 | return dataPackage; |
| 75 | - | |
| 76 | 75 | } catch (Exception e) { |
| 77 | 76 | log.error("[decode][解码失败] 数据: {}", buffer.toString(), e); |
| 78 | 77 | throw new IllegalArgumentException("数据包解码失败: " + e.getMessage(), e); |
| 79 | 78 | } |
| 80 | 79 | } |
| 81 | 80 | |
| 81 | + // TODO @haohao:这个要不去掉,暂时没用到; | |
| 82 | 82 | /** |
| 83 | 83 | * 校验数据包格式 |
| 84 | 84 | * |
| ... | ... | @@ -94,4 +94,5 @@ public class TcpDataDecoder { |
| 94 | 94 | return false; |
| 95 | 95 | } |
| 96 | 96 | } |
| 97 | + | |
| 97 | 98 | } |
| 98 | 99 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/protocol/TcpDataEncoder.java
| ... | ... | @@ -27,11 +27,9 @@ public class TcpDataEncoder { |
| 27 | 27 | if (dataPackage == null) { |
| 28 | 28 | throw new IllegalArgumentException("数据包对象不能为空"); |
| 29 | 29 | } |
| 30 | - | |
| 31 | 30 | if (dataPackage.getAddr() == null || dataPackage.getAddr().isEmpty()) { |
| 32 | 31 | throw new IllegalArgumentException("设备地址不能为空"); |
| 33 | 32 | } |
| 34 | - | |
| 35 | 33 | if (dataPackage.getPayload() == null) { |
| 36 | 34 | dataPackage.setPayload(""); |
| 37 | 35 | } |
| ... | ... | @@ -39,34 +37,27 @@ public class TcpDataEncoder { |
| 39 | 37 | try { |
| 40 | 38 | Buffer buffer = Buffer.buffer(); |
| 41 | 39 | |
| 42 | - // 1. 计算包体长度(除了包头4字节) | |
| 40 | + // 1. 计算包体长度(除了包头 4 字节) | |
| 43 | 41 | int payloadLength = dataPackage.getPayload().getBytes().length; |
| 44 | 42 | int totalLength = 2 + dataPackage.getAddr().length() + 2 + 2 + payloadLength; |
| 45 | 43 | |
| 46 | - // 2. 写入包头:总长度(4字节) | |
| 44 | + // 2.1 写入包头:总长度(4 字节) | |
| 47 | 45 | buffer.appendInt(totalLength); |
| 48 | - | |
| 49 | - // 3. 写入设备地址长度(2字节) | |
| 46 | + // 2.2 写入设备地址长度(2 字节) | |
| 50 | 47 | buffer.appendShort((short) dataPackage.getAddr().length()); |
| 51 | - | |
| 52 | - // 4. 写入设备地址(不定长) | |
| 48 | + // 2.3 写入设备地址(不定长) | |
| 53 | 49 | buffer.appendBytes(dataPackage.getAddr().getBytes()); |
| 54 | - | |
| 55 | - // 5. 写入功能码(2字节) | |
| 50 | + // 2.4 写入功能码(2 字节) | |
| 56 | 51 | buffer.appendShort(dataPackage.getCode()); |
| 57 | - | |
| 58 | - // 6. 写入消息序号(2字节) | |
| 52 | + // 2.5 写入消息序号(2 字节) | |
| 59 | 53 | buffer.appendShort(dataPackage.getMid()); |
| 60 | - | |
| 61 | - // 7. 写入包体数据(不定长) | |
| 54 | + // 2.6 写入包体数据(不定长) | |
| 62 | 55 | buffer.appendBytes(dataPackage.getPayload().getBytes()); |
| 63 | 56 | |
| 64 | 57 | log.debug("[encode][编码成功] 设备地址: {}, 功能码: {}, 消息序号: {}, 总长度: {}", |
| 65 | 58 | dataPackage.getAddr(), dataPackage.getCodeDescription(), |
| 66 | 59 | dataPackage.getMid(), buffer.length()); |
| 67 | - | |
| 68 | 60 | return buffer; |
| 69 | - | |
| 70 | 61 | } catch (Exception e) { |
| 71 | 62 | log.error("[encode][编码失败] 数据包: {}", dataPackage, e); |
| 72 | 63 | throw new IllegalArgumentException("数据包编码失败: " + e.getMessage(), e); |
| ... | ... | @@ -82,15 +73,14 @@ public class TcpDataEncoder { |
| 82 | 73 | * @return 编码后的数据包 |
| 83 | 74 | */ |
| 84 | 75 | public static Buffer createRegisterReply(String addr, short mid, boolean success) { |
| 85 | - String payload = success ? "0" : "1"; // 0表示成功,1表示失败 | |
| 86 | - | |
| 76 | + // TODO @haohao:payload 默认成功、失败,最好讴有个枚举 | |
| 77 | + String payload = success ? "0" : "1"; // 0 表示成功,1 表示失败 | |
| 87 | 78 | TcpDataPackage dataPackage = TcpDataPackage.builder() |
| 88 | 79 | .addr(addr) |
| 89 | 80 | .code(TcpDataPackage.CODE_REGISTER_REPLY) |
| 90 | 81 | .mid(mid) |
| 91 | 82 | .payload(payload) |
| 92 | 83 | .build(); |
| 93 | - | |
| 94 | 84 | return encode(dataPackage); |
| 95 | 85 | } |
| 96 | 86 | |
| ... | ... | @@ -109,7 +99,6 @@ public class TcpDataEncoder { |
| 109 | 99 | .mid(mid) |
| 110 | 100 | .payload(data) |
| 111 | 101 | .build(); |
| 112 | - | |
| 113 | 102 | return encode(dataPackage); |
| 114 | 103 | } |
| 115 | 104 | |
| ... | ... | @@ -128,7 +117,6 @@ public class TcpDataEncoder { |
| 128 | 117 | .mid(mid) |
| 129 | 118 | .payload(serviceData) |
| 130 | 119 | .build(); |
| 131 | - | |
| 132 | 120 | return encode(dataPackage); |
| 133 | 121 | } |
| 134 | 122 | |
| ... | ... | @@ -147,7 +135,6 @@ public class TcpDataEncoder { |
| 147 | 135 | .mid(mid) |
| 148 | 136 | .payload(propertyData) |
| 149 | 137 | .build(); |
| 150 | - | |
| 151 | 138 | return encode(dataPackage); |
| 152 | 139 | } |
| 153 | 140 | |
| ... | ... | @@ -166,7 +153,7 @@ public class TcpDataEncoder { |
| 166 | 153 | .mid(mid) |
| 167 | 154 | .payload(propertyNames) |
| 168 | 155 | .build(); |
| 169 | - | |
| 170 | 156 | return encode(dataPackage); |
| 171 | 157 | } |
| 158 | + | |
| 172 | 159 | } |
| 173 | 160 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/protocol/TcpDataPackage.java
| ... | ... | @@ -9,7 +9,7 @@ import lombok.NoArgsConstructor; |
| 9 | 9 | * TCP 数据包协议定义 |
| 10 | 10 | * <p> |
| 11 | 11 | * 数据包格式: |
| 12 | - * 包头(4字节长度) | 设备地址长度(2字节) | 设备地址(不定长) | 功能码(2字节) | 消息序号(2字节) | 包体(不定长) | |
| 12 | + * 包头(4 字节长度) | 设备地址长度(2 字节) | 设备地址(不定长) | 功能码(2 字节) | 消息序号(2 字节) | 包体(不定长) | |
| 13 | 13 | * |
| 14 | 14 | * @author 芋道源码 |
| 15 | 15 | */ |
| ... | ... | @@ -29,10 +29,12 @@ public class TcpDataPackage { |
| 29 | 29 | * 注册回复 |
| 30 | 30 | */ |
| 31 | 31 | public static final short CODE_REGISTER_REPLY = 11; |
| 32 | + // TODO @haohao:【重要】一般心跳,服务端会回复一条;回复要搞独立的 code 码,还是继续用原来的,因为 requestId 可以映射; | |
| 32 | 33 | /** |
| 33 | 34 | * 心跳 |
| 34 | 35 | */ |
| 35 | 36 | public static final short CODE_HEARTBEAT = 20; |
| 37 | + // TODO @haohao:【重要】下面的,是不是融合成消息上行(client -> server),消息下行(server -> client);然后把 method 放到 body 里? | |
| 36 | 38 | /** |
| 37 | 39 | * 数据上报 |
| 38 | 40 | */ |
| ... | ... | @@ -60,6 +62,8 @@ public class TcpDataPackage { |
| 60 | 62 | |
| 61 | 63 | // ==================== 数据包字段 ==================== |
| 62 | 64 | |
| 65 | + // TODO @haohao:设备 addrLength、addr 是不是非必要呀? | |
| 66 | + | |
| 63 | 67 | /** |
| 64 | 68 | * 设备地址长度 |
| 65 | 69 | */ |
| ... | ... | @@ -87,6 +91,8 @@ public class TcpDataPackage { |
| 87 | 91 | |
| 88 | 92 | // ==================== 辅助方法 ==================== |
| 89 | 93 | |
| 94 | + // TODO @haohao:用不到的方法,可以清理掉哈; | |
| 95 | + | |
| 90 | 96 | /** |
| 91 | 97 | * 是否为注册消息 |
| 92 | 98 | */ |
| ... | ... | @@ -123,6 +129,7 @@ public class TcpDataPackage { |
| 123 | 129 | code == CODE_PROPERTY_SET || code == CODE_PROPERTY_GET; |
| 124 | 130 | } |
| 125 | 131 | |
| 132 | + // TODO @haohao:这个是不是去掉呀?多了一些维护成本; | |
| 126 | 133 | /** |
| 127 | 134 | * 获取功能码描述 |
| 128 | 135 | */ | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/protocol/TcpDataReader.java
| ... | ... | @@ -13,7 +13,7 @@ import java.util.function.Consumer; |
| 13 | 13 | * 负责从 TCP 流中读取完整的数据包 |
| 14 | 14 | * <p> |
| 15 | 15 | * 数据包格式: |
| 16 | - * 包头(4字节长度) | 设备地址长度(2字节) | 设备地址(不定长) | 功能码(2字节) | 消息序号(2字节) | 包体(不定长) | |
| 16 | + * 包头(4 字节长度) | 设备地址长度(2 字节) | 设备地址(不定长) | 功能码(2 字节) | 消息序号(2 字节) | 包体(不定长) | |
| 17 | 17 | * |
| 18 | 18 | * @author 芋道源码 |
| 19 | 19 | */ |
| ... | ... | @@ -27,12 +27,12 @@ public class TcpDataReader { |
| 27 | 27 | * @return RecordParser 解析器 |
| 28 | 28 | */ |
| 29 | 29 | public static RecordParser createParser(Consumer<Buffer> receiveHandler) { |
| 30 | - // 首先读取4字节的长度信息 | |
| 30 | + // 首先读取 4 字节的长度信息 | |
| 31 | 31 | RecordParser parser = RecordParser.newFixed(4); |
| 32 | 32 | |
| 33 | 33 | // 设置处理器 |
| 34 | 34 | parser.setOutput(new Handler<Buffer>() { |
| 35 | - // 当前数据包的长度,-1表示还没有读取到长度信息 | |
| 35 | + // 当前数据包的长度,-1 表示还没有读取到长度信息 | |
| 36 | 36 | private int dataLength = -1; |
| 37 | 37 | |
| 38 | 38 | @Override |
| ... | ... | @@ -43,8 +43,9 @@ public class TcpDataReader { |
| 43 | 43 | // 从包头中读取数据长度 |
| 44 | 44 | dataLength = buffer.getInt(0); |
| 45 | 45 | |
| 46 | - // 校验数据长度 | |
| 47 | - if (dataLength <= 0 || dataLength > 1024 * 1024) { // 最大1MB | |
| 46 | + // 校验数据长度(最大 1 MB) | |
| 47 | + // TODO @haohao:1m 蛮多地方在写死,最好配置管理下。或者有个全局的枚举; | |
| 48 | + if (dataLength <= 0 || dataLength > 1024 * 1024) { | |
| 48 | 49 | log.error("[handle][无效的数据包长度: {}]", dataLength); |
| 49 | 50 | reset(); |
| 50 | 51 | return; |
| ... | ... | @@ -86,6 +87,8 @@ public class TcpDataReader { |
| 86 | 87 | return parser; |
| 87 | 88 | } |
| 88 | 89 | |
| 90 | + // TODO @haohao:用不到的方法,可以清理掉哈; | |
| 91 | + | |
| 89 | 92 | /** |
| 90 | 93 | * 创建带异常处理的数据包解析器 |
| 91 | 94 | * | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java
| ... | ... | @@ -55,6 +55,7 @@ public class IotTcpDownstreamHandler { |
| 55 | 55 | } |
| 56 | 56 | |
| 57 | 57 | // 2. 根据消息方法处理不同类型的下行消息 |
| 58 | + // TODO @芋艿、@haohao:看看有没什么办法,减少这样的编码。拓展新消息类型,成本高; | |
| 58 | 59 | switch (message.getMethod()) { |
| 59 | 60 | case "thing.property.set": |
| 60 | 61 | handlePropertySet(client, message); |
| ... | ... | @@ -75,8 +76,8 @@ public class IotTcpDownstreamHandler { |
| 75 | 76 | log.warn("[handle][未知的下行消息方法: {}]", message.getMethod()); |
| 76 | 77 | break; |
| 77 | 78 | } |
| 78 | - | |
| 79 | 79 | } catch (Exception e) { |
| 80 | + // TODO @haohao:最好消息的内容,打印下; | |
| 80 | 81 | log.error("[handle][处理下行消息失败]", e); |
| 81 | 82 | } |
| 82 | 83 | } |
| ... | ... | @@ -104,7 +105,6 @@ public class IotTcpDownstreamHandler { |
| 104 | 105 | log.debug("[handlePropertySet][属性设置消息已发送(降级)] 设备地址: {}, 消息序号: {}", |
| 105 | 106 | client.getDeviceAddr(), mid); |
| 106 | 107 | }); |
| 107 | - | |
| 108 | 108 | } catch (Exception e) { |
| 109 | 109 | log.error("[handlePropertySet][属性设置失败]", e); |
| 110 | 110 | } |
| ... | ... | @@ -133,7 +133,6 @@ public class IotTcpDownstreamHandler { |
| 133 | 133 | log.debug("[handlePropertyGet][属性获取消息已发送(降级)] 设备地址: {}, 消息序号: {}", |
| 134 | 134 | client.getDeviceAddr(), mid); |
| 135 | 135 | }); |
| 136 | - | |
| 137 | 136 | } catch (Exception e) { |
| 138 | 137 | log.error("[handlePropertyGet][属性获取失败]", e); |
| 139 | 138 | } |
| ... | ... | @@ -162,7 +161,6 @@ public class IotTcpDownstreamHandler { |
| 162 | 161 | |
| 163 | 162 | log.debug("[handleServiceInvoke][服务调用消息已发送] 设备地址: {}, 消息序号: {}", |
| 164 | 163 | client.getDeviceAddr(), mid); |
| 165 | - | |
| 166 | 164 | } catch (Exception e) { |
| 167 | 165 | log.error("[handleServiceInvoke][服务调用失败]", e); |
| 168 | 166 | } |
| ... | ... | @@ -191,7 +189,6 @@ public class IotTcpDownstreamHandler { |
| 191 | 189 | |
| 192 | 190 | log.debug("[handleConfigPush][配置推送消息已发送] 设备地址: {}, 消息序号: {}", |
| 193 | 191 | client.getDeviceAddr(), mid); |
| 194 | - | |
| 195 | 192 | } catch (Exception e) { |
| 196 | 193 | log.error("[handleConfigPush][配置推送失败]", e); |
| 197 | 194 | } |
| ... | ... | @@ -262,6 +259,7 @@ public class IotTcpDownstreamHandler { |
| 262 | 259 | } |
| 263 | 260 | } |
| 264 | 261 | |
| 262 | + // TODO @haohao:用不到的,要不暂时不提供; | |
| 265 | 263 | /** |
| 266 | 264 | * 批量发送下行消息 |
| 267 | 265 | * |
| ... | ... | @@ -287,7 +285,6 @@ public class IotTcpDownstreamHandler { |
| 287 | 285 | // 处理单个设备消息 |
| 288 | 286 | handle(copyMessage); |
| 289 | 287 | } |
| 290 | - | |
| 291 | 288 | } catch (Exception e) { |
| 292 | 289 | log.error("[broadcastMessage][批量发送消息失败]", e); |
| 293 | 290 | } |
| ... | ... | @@ -341,7 +338,6 @@ public class IotTcpDownstreamHandler { |
| 341 | 338 | |
| 342 | 339 | log.debug("[{}][消息已发送] 设备地址: {}, 消息序号: {}", |
| 343 | 340 | methodName, client.getDeviceAddr(), dataPackage.getMid()); |
| 344 | - | |
| 345 | 341 | } catch (Exception e) { |
| 346 | 342 | log.warn("[{}][使用编解码器编码失败,降级使用原始编码] 错误: {}", |
| 347 | 343 | methodName, e.getMessage()); |
| ... | ... | @@ -353,6 +349,7 @@ public class IotTcpDownstreamHandler { |
| 353 | 349 | } |
| 354 | 350 | } |
| 355 | 351 | |
| 352 | + // TODO @haohao:看看这个要不要删除掉 | |
| 356 | 353 | /** |
| 357 | 354 | * 获取连接统计信息 |
| 358 | 355 | * | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpUpstreamHandler.java
| ... | ... | @@ -39,6 +39,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 39 | 39 | |
| 40 | 40 | private final IotGatewayProperties.TcpProperties tcpConfig; |
| 41 | 41 | |
| 42 | + // TODO @haohao:可以把 TcpDeviceConnectionManager 能力放大一点:1)handle 里的 client 初始化,可以拿到 TcpDeviceConnectionManager 里;2)handleDeviceRegister 也是; | |
| 42 | 43 | private final TcpDeviceConnectionManager connectionManager; |
| 43 | 44 | |
| 44 | 45 | private final IotDeviceService deviceService; |
| ... | ... | @@ -53,26 +54,25 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 53 | 54 | public void handle(NetSocket socket) { |
| 54 | 55 | log.info("[handle][收到设备连接: {}]", socket.remoteAddress()); |
| 55 | 56 | |
| 56 | - // 创建客户端ID和设备客户端 | |
| 57 | + // 创建客户端 ID 和设备客户端 | |
| 58 | + // TODO @haohao:clientid 给 TcpDeviceClient 生成会简洁一点;减少 upsteramhanlder 的非核心逻辑; | |
| 57 | 59 | String clientId = IdUtil.simpleUUID() + "_" + socket.remoteAddress(); |
| 58 | 60 | TcpDeviceClient client = new TcpDeviceClient(clientId, tcpConfig.getKeepAliveTimeoutMs()); |
| 59 | 61 | |
| 60 | 62 | try { |
| 61 | 63 | // 设置连接异常和关闭处理 |
| 62 | 64 | socket.exceptionHandler(ex -> { |
| 65 | + // TODO @haohao:这里的日志,可能把 clientid 都打上?因为 address 会重复么? | |
| 63 | 66 | log.error("[handle][连接({})异常]", socket.remoteAddress(), ex); |
| 64 | 67 | handleConnectionClose(client); |
| 65 | 68 | }); |
| 66 | - | |
| 67 | 69 | socket.closeHandler(v -> { |
| 68 | 70 | log.info("[handle][连接({})关闭]", socket.remoteAddress()); |
| 69 | 71 | handleConnectionClose(client); |
| 70 | 72 | }); |
| 71 | - | |
| 72 | - // 设置网络连接 | |
| 73 | 73 | client.setSocket(socket); |
| 74 | 74 | |
| 75 | - // 创建数据解析器 | |
| 75 | + // 设置解析器 | |
| 76 | 76 | RecordParser parser = TcpDataReader.createParser(buffer -> { |
| 77 | 77 | try { |
| 78 | 78 | handleDataPackage(client, buffer); |
| ... | ... | @@ -80,13 +80,12 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 80 | 80 | log.error("[handle][处理数据包异常]", e); |
| 81 | 81 | } |
| 82 | 82 | }); |
| 83 | - | |
| 84 | - // 设置解析器 | |
| 85 | 83 | client.setParser(parser); |
| 86 | 84 | |
| 85 | + // TODO @haohao:socket.remoteAddress()) 打印进去 | |
| 87 | 86 | log.info("[handle][设备连接处理器初始化完成: {}]", clientId); |
| 88 | - | |
| 89 | 87 | } catch (Exception e) { |
| 88 | + // TODO @haohao:socket.remoteAddress()) 打印进去 | |
| 90 | 89 | log.error("[handle][初始化连接处理器失败]", e); |
| 91 | 90 | client.shutdown(); |
| 92 | 91 | } |
| ... | ... | @@ -102,12 +101,12 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 102 | 101 | try { |
| 103 | 102 | // 解码数据包 |
| 104 | 103 | TcpDataPackage dataPackage = TcpDataDecoder.decode(buffer); |
| 105 | - | |
| 106 | 104 | log.info("[handleDataPackage][接收数据包] 设备地址: {}, 功能码: {}, 消息序号: {}", |
| 107 | 105 | dataPackage.getAddr(), dataPackage.getCodeDescription(), dataPackage.getMid()); |
| 108 | 106 | |
| 109 | 107 | // 根据功能码处理不同类型的消息 |
| 110 | 108 | switch (dataPackage.getCode()) { |
| 109 | + // TODO @haohao:【重要】code 要不要改成 opCode。这样和 data 里的 code 好区分; | |
| 111 | 110 | case TcpDataPackage.CODE_REGISTER: |
| 112 | 111 | handleDeviceRegister(client, dataPackage); |
| 113 | 112 | break; |
| ... | ... | @@ -124,8 +123,8 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 124 | 123 | log.warn("[handleDataPackage][未知功能码: {}]", dataPackage.getCode()); |
| 125 | 124 | break; |
| 126 | 125 | } |
| 127 | - | |
| 128 | 126 | } catch (Exception e) { |
| 127 | + // TODO @haohao:最好有 client 标识; | |
| 129 | 128 | log.error("[handleDataPackage][处理数据包失败]", e); |
| 130 | 129 | } |
| 131 | 130 | } |
| ... | ... | @@ -140,7 +139,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 140 | 139 | try { |
| 141 | 140 | String deviceAddr = dataPackage.getAddr(); |
| 142 | 141 | String productKey = dataPackage.getPayload(); |
| 143 | - | |
| 144 | 142 | log.info("[handleDeviceRegister][设备注册] 设备地址: {}, 产品密钥: {}", deviceAddr, productKey); |
| 145 | 143 | |
| 146 | 144 | // 获取设备信息 |
| ... | ... | @@ -152,6 +150,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 152 | 150 | } |
| 153 | 151 | |
| 154 | 152 | // 更新客户端信息 |
| 153 | + // TODO @haohao:一个 set 方法,统一处理掉会好点哈; | |
| 155 | 154 | client.setProductKey(productKey); |
| 156 | 155 | client.setDeviceName(deviceAddr); |
| 157 | 156 | client.setDeviceId(device.getId()); |
| ... | ... | @@ -169,7 +168,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 169 | 168 | sendRegisterReply(client, dataPackage, true); |
| 170 | 169 | |
| 171 | 170 | log.info("[handleDeviceRegister][设备注册成功] 设备地址: {}, 设备ID: {}", deviceAddr, device.getId()); |
| 172 | - | |
| 173 | 171 | } catch (Exception e) { |
| 174 | 172 | log.error("[handleDeviceRegister][设备注册失败]", e); |
| 175 | 173 | sendRegisterReply(client, dataPackage, false); |
| ... | ... | @@ -185,7 +183,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 185 | 183 | private void handleHeartbeat(TcpDeviceClient client, TcpDataPackage dataPackage) { |
| 186 | 184 | try { |
| 187 | 185 | String deviceAddr = dataPackage.getAddr(); |
| 188 | - | |
| 189 | 186 | log.debug("[handleHeartbeat][收到心跳] 设备地址: {}", deviceAddr); |
| 190 | 187 | |
| 191 | 188 | // 更新心跳时间 |
| ... | ... | @@ -230,7 +227,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 230 | 227 | |
| 231 | 228 | // 3. 发送解码后的消息 |
| 232 | 229 | messageService.sendDeviceMessage(message, client.getProductKey(), client.getDeviceName(), serverId); |
| 233 | - | |
| 234 | 230 | } catch (Exception e) { |
| 235 | 231 | log.warn("[handleDataUp][使用编解码器解码失败,降级使用原始解析] 错误: {}", e.getMessage()); |
| 236 | 232 | |
| ... | ... | @@ -242,7 +238,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 242 | 238 | |
| 243 | 239 | // 发送数据上报回复 |
| 244 | 240 | sendDataUpReply(client, dataPackage); |
| 245 | - | |
| 246 | 241 | } catch (Exception e) { |
| 247 | 242 | log.error("[handleDataUp][处理数据上报失败]", e); |
| 248 | 243 | } |
| ... | ... | @@ -279,11 +274,11 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 279 | 274 | |
| 280 | 275 | // 3. 发送解码后的消息 |
| 281 | 276 | messageService.sendDeviceMessage(message, client.getProductKey(), client.getDeviceName(), serverId); |
| 282 | - | |
| 283 | 277 | } catch (Exception e) { |
| 284 | 278 | log.warn("[handleEventUp][使用编解码器解码失败,降级使用原始解析] 错误: {}", e.getMessage()); |
| 285 | 279 | |
| 286 | 280 | // 降级处理:使用原始方式解析数据 |
| 281 | + // TODO @芋艿:降级处理逻辑; | |
| 287 | 282 | JSONObject eventJson = JSONUtil.parseObj(payload); |
| 288 | 283 | IotDeviceMessage message = IotDeviceMessage.requestOf("thing.event.post", eventJson); |
| 289 | 284 | messageService.sendDeviceMessage(message, client.getProductKey(), client.getDeviceName(), serverId); |
| ... | ... | @@ -291,7 +286,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 291 | 286 | |
| 292 | 287 | // 发送事件上报回复 |
| 293 | 288 | sendEventUpReply(client, dataPackage); |
| 294 | - | |
| 295 | 289 | } catch (Exception e) { |
| 296 | 290 | log.error("[handleEventUp][处理事件上报失败]", e); |
| 297 | 291 | } |
| ... | ... | @@ -329,13 +323,13 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 329 | 323 | .addr(dataPackage.getAddr()) |
| 330 | 324 | .code(TcpDataPackage.CODE_DATA_UP) |
| 331 | 325 | .mid(dataPackage.getMid()) |
| 332 | - .payload("0") // 0表示成功 | |
| 326 | + .payload("0") // 0 表示成功 TODO @haohao:最好枚举到 TcpDataPackage 里? | |
| 333 | 327 | .build(); |
| 334 | 328 | |
| 335 | 329 | io.vertx.core.buffer.Buffer replyBuffer = TcpDataEncoder.encode(replyPackage); |
| 336 | 330 | client.sendMessage(replyBuffer); |
| 337 | - | |
| 338 | 331 | } catch (Exception e) { |
| 332 | + // TODO @haohao:可以有个 client id | |
| 339 | 333 | log.error("[sendDataUpReply][发送数据上报回复失败]", e); |
| 340 | 334 | } |
| 341 | 335 | } |
| ... | ... | @@ -352,12 +346,11 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 352 | 346 | .addr(dataPackage.getAddr()) |
| 353 | 347 | .code(TcpDataPackage.CODE_EVENT_UP) |
| 354 | 348 | .mid(dataPackage.getMid()) |
| 355 | - .payload("0") // 0表示成功 | |
| 349 | + .payload("0") // 0 表示成功 | |
| 356 | 350 | .build(); |
| 357 | 351 | |
| 358 | 352 | io.vertx.core.buffer.Buffer replyBuffer = TcpDataEncoder.encode(replyPackage); |
| 359 | 353 | client.sendMessage(replyBuffer); |
| 360 | - | |
| 361 | 354 | } catch (Exception e) { |
| 362 | 355 | log.error("[sendEventUpReply][发送事件上报回复失败]", e); |
| 363 | 356 | } |
| ... | ... | @@ -385,7 +378,6 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { |
| 385 | 378 | } |
| 386 | 379 | |
| 387 | 380 | log.info("[handleConnectionClose][处理连接关闭完成] 设备地址: {}", deviceAddr); |
| 388 | - | |
| 389 | 381 | } catch (Exception e) { |
| 390 | 382 | log.error("[handleConnectionClose][处理连接关闭失败]", e); |
| 391 | 383 | } | ... | ... |