Commit fcca74ac7d1335b805a07f191cefa43445306eda
1 parent
88f090b6
feat(iot):统一优化网关协议的资源清理逻辑,主要是 stop0
Showing
18 changed files
with
244 additions
and
185 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java
| ... | ... | @@ -70,7 +70,6 @@ public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigSe |
| 70 | 70 | return modbusConfigMapper.selectList(listReqDTO); |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | - // TODO @AI:还是希望在 IotDeviceModbusConfigSaveReqVO 里,通过 validator 校验?!或者通过 group 来处理? | |
| 74 | 73 | private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) { |
| 75 | 74 | IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType); |
| 76 | 75 | if (protocolTypeEnum == null) { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java
| ... | ... | @@ -64,17 +64,13 @@ public class IotCoapProtocol implements IotProtocol { |
| 64 | 64 | /** |
| 65 | 65 | * 下行消息订阅者 |
| 66 | 66 | */ |
| 67 | - private final IotCoapDownstreamSubscriber downstreamSubscriber; | |
| 67 | + private IotCoapDownstreamSubscriber downstreamSubscriber; | |
| 68 | 68 | |
| 69 | 69 | public IotCoapProtocol(ProtocolProperties properties) { |
| 70 | 70 | IotCoapConfig coapConfig = properties.getCoap(); |
| 71 | 71 | Assert.notNull(coapConfig, "CoAP 协议配置(coap)不能为空"); |
| 72 | 72 | this.properties = properties; |
| 73 | 73 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); |
| 74 | - | |
| 75 | - // 初始化下行消息订阅者 | |
| 76 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 77 | - this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus); | |
| 78 | 74 | } |
| 79 | 75 | |
| 80 | 76 | @Override |
| ... | ... | @@ -94,9 +90,9 @@ public class IotCoapProtocol implements IotProtocol { |
| 94 | 90 | return; |
| 95 | 91 | } |
| 96 | 92 | |
| 97 | - IotCoapConfig coapConfig = properties.getCoap(); | |
| 98 | 93 | try { |
| 99 | 94 | // 1.1 创建 CoAP 配置 |
| 95 | + IotCoapConfig coapConfig = properties.getCoap(); | |
| 100 | 96 | Configuration config = Configuration.createStandardWithoutFile(); |
| 101 | 97 | config.set(CoapConfig.COAP_PORT, properties.getPort()); |
| 102 | 98 | config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize()); |
| ... | ... | @@ -131,13 +127,12 @@ public class IotCoapProtocol implements IotProtocol { |
| 131 | 127 | getId(), properties.getPort(), serverId); |
| 132 | 128 | |
| 133 | 129 | // 4. 启动下行消息订阅者 |
| 130 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 131 | + this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus); | |
| 134 | 132 | this.downstreamSubscriber.start(); |
| 135 | 133 | } catch (Exception e) { |
| 136 | 134 | log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e); |
| 137 | - if (coapServer != null) { | |
| 138 | - coapServer.destroy(); | |
| 139 | - coapServer = null; | |
| 140 | - } | |
| 135 | + stop0(); | |
| 141 | 136 | throw e; |
| 142 | 137 | } |
| 143 | 138 | } |
| ... | ... | @@ -147,12 +142,19 @@ public class IotCoapProtocol implements IotProtocol { |
| 147 | 142 | if (!running) { |
| 148 | 143 | return; |
| 149 | 144 | } |
| 145 | + stop0(); | |
| 146 | + } | |
| 147 | + | |
| 148 | + private void stop0() { | |
| 150 | 149 | // 1. 停止下行消息订阅者 |
| 151 | - try { | |
| 152 | - downstreamSubscriber.stop(); | |
| 153 | - log.info("[stop][IoT CoAP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 154 | - } catch (Exception e) { | |
| 155 | - log.error("[stop][IoT CoAP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 150 | + if (downstreamSubscriber != null) { | |
| 151 | + try { | |
| 152 | + downstreamSubscriber.stop(); | |
| 153 | + log.info("[stop][IoT CoAP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 154 | + } catch (Exception e) { | |
| 155 | + log.error("[stop][IoT CoAP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 156 | + } | |
| 157 | + downstreamSubscriber = null; | |
| 156 | 158 | } |
| 157 | 159 | |
| 158 | 160 | // 2. 关闭 CoAP 服务器 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxProtocol.java
| ... | ... | @@ -90,7 +90,7 @@ public class IotEmqxProtocol implements IotProtocol { |
| 90 | 90 | /** |
| 91 | 91 | * 下行消息订阅者 |
| 92 | 92 | */ |
| 93 | - private final IotEmqxDownstreamSubscriber downstreamSubscriber; | |
| 93 | + private IotEmqxDownstreamSubscriber downstreamSubscriber; | |
| 94 | 94 | |
| 95 | 95 | public IotEmqxProtocol(ProtocolProperties properties) { |
| 96 | 96 | Assert.notNull(properties, "协议实例配置不能为空"); |
| ... | ... | @@ -101,10 +101,6 @@ public class IotEmqxProtocol implements IotProtocol { |
| 101 | 101 | "MQTT 连接超时时间(emqx.connect-timeout-seconds)不能为空"); |
| 102 | 102 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); |
| 103 | 103 | this.upstreamHandler = new IotEmqxUpstreamHandler(serverId); |
| 104 | - | |
| 105 | - // 初始化下行消息订阅者 | |
| 106 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 107 | - this.downstreamSubscriber = new IotEmqxDownstreamSubscriber(this, messageBus); | |
| 108 | 104 | } |
| 109 | 105 | |
| 110 | 106 | @Override |
| ... | ... | @@ -124,7 +120,7 @@ public class IotEmqxProtocol implements IotProtocol { |
| 124 | 120 | return; |
| 125 | 121 | } |
| 126 | 122 | |
| 127 | - // 1.1 创建 Vertx 实例 | |
| 123 | + // 1.1 创建 Vertx 实例 和 下行消息订阅者 | |
| 128 | 124 | this.vertx = Vertx.vertx(); |
| 129 | 125 | |
| 130 | 126 | try { |
| ... | ... | @@ -138,6 +134,8 @@ public class IotEmqxProtocol implements IotProtocol { |
| 138 | 134 | getId(), properties.getPort(), serverId); |
| 139 | 135 | |
| 140 | 136 | // 2. 启动下行消息订阅者 |
| 137 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 138 | + this.downstreamSubscriber = new IotEmqxDownstreamSubscriber(this, messageBus); | |
| 141 | 139 | this.downstreamSubscriber.start(); |
| 142 | 140 | } catch (Exception e) { |
| 143 | 141 | log.error("[start][IoT EMQX 协议 {} 启动失败]", getId(), e); |
| ... | ... | @@ -157,11 +155,14 @@ public class IotEmqxProtocol implements IotProtocol { |
| 157 | 155 | |
| 158 | 156 | private void stop0() { |
| 159 | 157 | // 1. 停止下行消息订阅者 |
| 160 | - try { | |
| 161 | - downstreamSubscriber.stop(); | |
| 162 | - log.info("[stop][IoT EMQX 协议 {} 下行消息订阅者已停止]", getId()); | |
| 163 | - } catch (Exception e) { | |
| 164 | - log.error("[stop][IoT EMQX 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 158 | + if (downstreamSubscriber != null) { | |
| 159 | + try { | |
| 160 | + downstreamSubscriber.stop(); | |
| 161 | + log.info("[stop][IoT EMQX 协议 {} 下行消息订阅者已停止]", getId()); | |
| 162 | + } catch (Exception e) { | |
| 163 | + log.error("[stop][IoT EMQX 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 164 | + } | |
| 165 | + downstreamSubscriber = null; | |
| 165 | 166 | } |
| 166 | 167 | |
| 167 | 168 | // 2.1 先置为 false:避免 closeHandler 触发重连 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/IotHttpProtocol.java
| ... | ... | @@ -64,10 +64,6 @@ public class IotHttpProtocol implements IotProtocol { |
| 64 | 64 | public IotHttpProtocol(ProtocolProperties properties) { |
| 65 | 65 | this.properties = properties; |
| 66 | 66 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); |
| 67 | - | |
| 68 | - // 初始化下行消息订阅者 | |
| 69 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 70 | - this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus); | |
| 71 | 67 | } |
| 72 | 68 | |
| 73 | 69 | @Override |
| ... | ... | @@ -87,7 +83,7 @@ public class IotHttpProtocol implements IotProtocol { |
| 87 | 83 | return; |
| 88 | 84 | } |
| 89 | 85 | |
| 90 | - // 1.1 创建 Vertx 实例(每个 Protocol 独立管理) | |
| 86 | + // 1.1 创建 Vertx 实例 | |
| 91 | 87 | this.vertx = Vertx.vertx(); |
| 92 | 88 | |
| 93 | 89 | // 1.2 创建路由 |
| ... | ... | @@ -123,18 +119,12 @@ public class IotHttpProtocol implements IotProtocol { |
| 123 | 119 | getId(), properties.getPort(), serverId); |
| 124 | 120 | |
| 125 | 121 | // 2. 启动下行消息订阅者 |
| 122 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 123 | + this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus); | |
| 126 | 124 | this.downstreamSubscriber.start(); |
| 127 | 125 | } catch (Exception e) { |
| 128 | 126 | log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e); |
| 129 | - // 启动失败时关闭资源 | |
| 130 | - if (httpServer != null) { | |
| 131 | - httpServer.close(); | |
| 132 | - httpServer = null; | |
| 133 | - } | |
| 134 | - if (vertx != null) { | |
| 135 | - vertx.close(); | |
| 136 | - vertx = null; | |
| 137 | - } | |
| 127 | + stop0(); | |
| 138 | 128 | throw e; |
| 139 | 129 | } |
| 140 | 130 | } |
| ... | ... | @@ -144,6 +134,10 @@ public class IotHttpProtocol implements IotProtocol { |
| 144 | 134 | if (!running) { |
| 145 | 135 | return; |
| 146 | 136 | } |
| 137 | + stop0(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + private void stop0() { | |
| 147 | 141 | // 1. 停止下行消息订阅者 |
| 148 | 142 | if (downstreamSubscriber != null) { |
| 149 | 143 | try { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusCommonUtils.java
| ... | ... | @@ -381,8 +381,11 @@ public class IotModbusCommonUtils { |
| 381 | 381 | case UINT16: |
| 382 | 382 | return new int[]{rawValue.intValue() & 0xFFFF}; |
| 383 | 383 | case INT32: |
| 384 | - case UINT32: | |
| 385 | 384 | return encodeInt32(rawValue.intValue(), byteOrder); |
| 385 | + case UINT32: | |
| 386 | + // 使用 longValue() 避免超过 Integer.MAX_VALUE 时溢出, | |
| 387 | + // 强转 int 保留低 32 位 bit pattern,写入寄存器的字节是正确的无符号值 | |
| 388 | + return encodeInt32((int) rawValue.longValue(), byteOrder); | |
| 386 | 389 | case FLOAT: |
| 387 | 390 | return encodeFloat(rawValue.floatValue(), byteOrder); |
| 388 | 391 | case DOUBLE: | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/IotModbusTcpMasterProtocol.java
| ... | ... | @@ -65,7 +65,7 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { |
| 65 | 65 | /** |
| 66 | 66 | * 下行消息订阅者 |
| 67 | 67 | */ |
| 68 | - private final IotModbusTcpMasterDownstreamSubscriber downstreamSubscriber; | |
| 68 | + private IotModbusTcpMasterDownstreamSubscriber downstreamSubscriber; | |
| 69 | 69 | |
| 70 | 70 | private final IotModbusTcpMasterConfigCacheService configCacheService; |
| 71 | 71 | private final IotModbusTcpMasterPollScheduler pollScheduler; |
| ... | ... | @@ -89,15 +89,9 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { |
| 89 | 89 | |
| 90 | 90 | // 初始化 Handler |
| 91 | 91 | IotModbusTcpMasterUpstreamHandler upstreamHandler = new IotModbusTcpMasterUpstreamHandler(messageService, serverId); |
| 92 | - IotModbusTcpMasterDownstreamHandler downstreamHandler = new IotModbusTcpMasterDownstreamHandler(connectionManager, | |
| 93 | - configCacheService); | |
| 94 | 92 | |
| 95 | 93 | // 初始化轮询调度器 |
| 96 | 94 | this.pollScheduler = new IotModbusTcpMasterPollScheduler(vertx, connectionManager, upstreamHandler, configCacheService); |
| 97 | - | |
| 98 | - // 初始化下行消息订阅者 | |
| 99 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 100 | - this.downstreamSubscriber = new IotModbusTcpMasterDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 101 | 95 | } |
| 102 | 96 | |
| 103 | 97 | @Override |
| ... | ... | @@ -130,13 +124,14 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { |
| 130 | 124 | log.info("[start][IoT Modbus TCP Master 协议 {} 启动成功,serverId={}]", getId(), serverId); |
| 131 | 125 | |
| 132 | 126 | // 2. 启动下行消息订阅者 |
| 127 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 128 | + IotModbusTcpMasterDownstreamHandler downstreamHandler = new IotModbusTcpMasterDownstreamHandler(connectionManager, | |
| 129 | + configCacheService); | |
| 130 | + this.downstreamSubscriber = new IotModbusTcpMasterDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 133 | 131 | this.downstreamSubscriber.start(); |
| 134 | 132 | } catch (Exception e) { |
| 135 | 133 | log.error("[start][IoT Modbus TCP Master 协议 {} 启动失败]", getId(), e); |
| 136 | - // 启动失败时关闭资源 | |
| 137 | - if (vertx != null) { | |
| 138 | - vertx.close(); | |
| 139 | - } | |
| 134 | + stop0(); | |
| 140 | 135 | throw e; |
| 141 | 136 | } |
| 142 | 137 | } |
| ... | ... | @@ -146,12 +141,19 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { |
| 146 | 141 | if (!running) { |
| 147 | 142 | return; |
| 148 | 143 | } |
| 144 | + stop0(); | |
| 145 | + } | |
| 146 | + | |
| 147 | + private void stop0() { | |
| 149 | 148 | // 1. 停止下行消息订阅者 |
| 150 | - try { | |
| 151 | - downstreamSubscriber.stop(); | |
| 152 | - log.info("[stop][IoT Modbus TCP Master 协议 {} 下行消息订阅者已停止]", getId()); | |
| 153 | - } catch (Exception e) { | |
| 154 | - log.error("[stop][IoT Modbus TCP Master 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 149 | + if (downstreamSubscriber != null) { | |
| 150 | + try { | |
| 151 | + downstreamSubscriber.stop(); | |
| 152 | + log.info("[stop][IoT Modbus TCP Master 协议 {} 下行消息订阅者已停止]", getId()); | |
| 153 | + } catch (Exception e) { | |
| 154 | + log.error("[stop][IoT Modbus TCP Master 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 155 | + } | |
| 156 | + downstreamSubscriber = null; | |
| 155 | 157 | } |
| 156 | 158 | |
| 157 | 159 | // 2.1 取消配置刷新定时器 |
| ... | ... | @@ -161,10 +163,8 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { |
| 161 | 163 | } |
| 162 | 164 | // 2.2 停止轮询调度器 |
| 163 | 165 | pollScheduler.stopAll(); |
| 164 | - log.info("[stop][IoT Modbus TCP Master 协议 {} 轮询调度器已停止]", getId()); | |
| 165 | 166 | // 2.3 关闭所有连接 |
| 166 | 167 | connectionManager.closeAll(); |
| 167 | - log.info("[stop][IoT Modbus TCP Master 协议 {} 连接管理器已关闭]", getId()); | |
| 168 | 168 | |
| 169 | 169 | // 3. 关闭 Vert.x 实例 |
| 170 | 170 | if (vertx != null) { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpMasterConnectionManager.java
| ... | ... | @@ -14,6 +14,8 @@ import org.redisson.api.RLock; |
| 14 | 14 | import org.redisson.api.RedissonClient; |
| 15 | 15 | |
| 16 | 16 | import java.net.InetAddress; |
| 17 | +import java.util.ArrayList; | |
| 18 | +import java.util.List; | |
| 17 | 19 | import java.util.Map; |
| 18 | 20 | import java.util.concurrent.ConcurrentHashMap; |
| 19 | 21 | |
| ... | ... | @@ -185,8 +187,11 @@ public class IotModbusTcpMasterConnectionManager { |
| 185 | 187 | */ |
| 186 | 188 | private void addDeviceAndOnline(ModbusConnection connection, |
| 187 | 189 | IotModbusDeviceConfigRespDTO config) { |
| 188 | - connection.addDevice(config.getDeviceId(), config.getSlaveId()); | |
| 189 | - sendOnlineMessage(config); | |
| 190 | + Integer previous = connection.addDevice(config.getDeviceId(), config.getSlaveId()); | |
| 191 | + // 首次注册,发送上线消息 | |
| 192 | + if (previous == null) { | |
| 193 | + sendOnlineMessage(config); | |
| 194 | + } | |
| 190 | 195 | } |
| 191 | 196 | |
| 192 | 197 | /** |
| ... | ... | @@ -247,7 +252,9 @@ public class IotModbusTcpMasterConnectionManager { |
| 247 | 252 | * 关闭所有连接 |
| 248 | 253 | */ |
| 249 | 254 | public void closeAll() { |
| 250 | - for (String connectionKey : connectionPool.keySet()) { | |
| 255 | + // 先复制再遍历,避免 closeConnection 中 remove 导致并发修改 | |
| 256 | + List<String> connectionKeys = new ArrayList<>(connectionPool.keySet()); | |
| 257 | + for (String connectionKey : connectionKeys) { | |
| 251 | 258 | closeConnection(connectionKey); |
| 252 | 259 | } |
| 253 | 260 | deviceConnectionMap.clear(); |
| ... | ... | @@ -282,8 +289,8 @@ public class IotModbusTcpMasterConnectionManager { |
| 282 | 289 | */ |
| 283 | 290 | private Context context; |
| 284 | 291 | |
| 285 | - public void addDevice(Long deviceId, Integer slaveId) { | |
| 286 | - deviceSlaveMap.put(deviceId, slaveId); | |
| 292 | + public Integer addDevice(Long deviceId, Integer slaveId) { | |
| 293 | + return deviceSlaveMap.putIfAbsent(deviceId, slaveId); | |
| 287 | 294 | } |
| 288 | 295 | |
| 289 | 296 | public void removeDevice(Long deviceId) { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveProtocol.java
| ... | ... | @@ -88,7 +88,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 88 | 88 | /** |
| 89 | 89 | * 下行消息订阅者 |
| 90 | 90 | */ |
| 91 | - private final IotModbusTcpSlaveDownstreamSubscriber downstreamSubscriber; | |
| 91 | + private IotModbusTcpSlaveDownstreamSubscriber downstreamSubscriber; | |
| 92 | 92 | |
| 93 | 93 | private final IotModbusFrameDecoder frameDecoder; |
| 94 | 94 | @SuppressWarnings("FieldCanBeLocal") |
| ... | ... | @@ -121,7 +121,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 121 | 121 | |
| 122 | 122 | // 初始化共享事务 ID 自增器(PollScheduler 和 DownstreamHandler 共用,避免 transactionId 冲突) |
| 123 | 123 | AtomicInteger transactionIdCounter = new AtomicInteger(0); |
| 124 | - | |
| 125 | 124 | // 初始化轮询调度器 |
| 126 | 125 | this.pollScheduler = new IotModbusTcpSlavePollScheduler( |
| 127 | 126 | vertx, connectionManager, frameEncoder, pendingRequestManager, |
| ... | ... | @@ -134,13 +133,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 134 | 133 | deviceApi, this.messageService, frameEncoder, |
| 135 | 134 | connectionManager, configCacheService, pendingRequestManager, |
| 136 | 135 | pollScheduler, deviceService, serverId); |
| 137 | - | |
| 138 | - // 初始化下行消息订阅者 | |
| 139 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 140 | - IotModbusTcpSlaveDownstreamHandler downstreamHandler = new IotModbusTcpSlaveDownstreamHandler( | |
| 141 | - connectionManager, configCacheService, frameEncoder, transactionIdCounter); | |
| 142 | - this.downstreamSubscriber = new IotModbusTcpSlaveDownstreamSubscriber( | |
| 143 | - this, downstreamHandler, messageBus); | |
| 144 | 136 | } |
| 145 | 137 | |
| 146 | 138 | @Override |
| ... | ... | @@ -179,25 +171,15 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 179 | 171 | getId(), serverId, properties.getPort()); |
| 180 | 172 | |
| 181 | 173 | // 3. 启动下行消息订阅 |
| 174 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 175 | + IotModbusTcpSlaveDownstreamHandler downstreamHandler = new IotModbusTcpSlaveDownstreamHandler( | |
| 176 | + connectionManager, configCacheService, frameEncoder, this.pollScheduler.getTransactionIdCounter()); | |
| 177 | + this.downstreamSubscriber = new IotModbusTcpSlaveDownstreamSubscriber( | |
| 178 | + this, downstreamHandler, messageBus); | |
| 182 | 179 | downstreamSubscriber.start(); |
| 183 | 180 | } catch (Exception e) { |
| 184 | 181 | log.error("[start][IoT Modbus TCP Slave 协议 {} 启动失败]", getId(), e); |
| 185 | - // TODO @芋艿:后续统一优化 stop 逻辑; | |
| 186 | - if (configRefreshTimerId != null) { | |
| 187 | - vertx.cancelTimer(configRefreshTimerId); | |
| 188 | - configRefreshTimerId = null; | |
| 189 | - } | |
| 190 | - if (requestCleanupTimerId != null) { | |
| 191 | - vertx.cancelTimer(requestCleanupTimerId); | |
| 192 | - requestCleanupTimerId = null; | |
| 193 | - } | |
| 194 | - connectionManager.closeAll(); | |
| 195 | - if (netServer != null) { | |
| 196 | - netServer.close(); | |
| 197 | - } | |
| 198 | - if (vertx != null) { | |
| 199 | - vertx.close(); | |
| 200 | - } | |
| 182 | + stop0(); | |
| 201 | 183 | throw e; |
| 202 | 184 | } |
| 203 | 185 | } |
| ... | ... | @@ -207,12 +189,18 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 207 | 189 | if (!running) { |
| 208 | 190 | return; |
| 209 | 191 | } |
| 192 | + stop0(); | |
| 193 | + } | |
| 210 | 194 | |
| 195 | + private void stop0() { | |
| 211 | 196 | // 1. 停止下行消息订阅 |
| 212 | - try { | |
| 213 | - downstreamSubscriber.stop(); | |
| 214 | - } catch (Exception e) { | |
| 215 | - log.error("[stop][下行消息订阅器停止失败]", e); | |
| 197 | + if (downstreamSubscriber != null) { | |
| 198 | + try { | |
| 199 | + downstreamSubscriber.stop(); | |
| 200 | + } catch (Exception e) { | |
| 201 | + log.error("[stop][下行消息订阅器停止失败]", e); | |
| 202 | + } | |
| 203 | + downstreamSubscriber = null; | |
| 216 | 204 | } |
| 217 | 205 | |
| 218 | 206 | // 2.1 取消定时器 |
| ... | ... | @@ -238,6 +226,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { |
| 238 | 226 | } catch (Exception e) { |
| 239 | 227 | log.error("[stop][TCP Server 关闭失败]", e); |
| 240 | 228 | } |
| 229 | + netServer = null; | |
| 241 | 230 | } |
| 242 | 231 | |
| 243 | 232 | // 3. 关闭 Vertx | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConnectionManager.java
| ... | ... | @@ -8,6 +8,8 @@ import lombok.Data; |
| 8 | 8 | import lombok.experimental.Accessors; |
| 9 | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | 10 | |
| 11 | +import java.util.ArrayList; | |
| 12 | +import java.util.List; | |
| 11 | 13 | import java.util.Map; |
| 12 | 14 | import java.util.Set; |
| 13 | 15 | import java.util.concurrent.ConcurrentHashMap; |
| ... | ... | @@ -155,15 +157,18 @@ public class IotModbusTcpSlaveConnectionManager { |
| 155 | 157 | * 关闭所有连接 |
| 156 | 158 | */ |
| 157 | 159 | public void closeAll() { |
| 158 | - for (NetSocket socket : connectionMap.keySet()) { | |
| 160 | + // 1. 先复制再清空,避免 closeHandler 回调时并发修改 | |
| 161 | + List<NetSocket> sockets = new ArrayList<>(connectionMap.keySet()); | |
| 162 | + connectionMap.clear(); | |
| 163 | + deviceSocketMap.clear(); | |
| 164 | + // 2. 关闭所有 socket(closeHandler 中 removeConnection 发现 map 为空会安全跳过) | |
| 165 | + for (NetSocket socket : sockets) { | |
| 159 | 166 | try { |
| 160 | 167 | socket.close(); |
| 161 | 168 | } catch (Exception e) { |
| 162 | 169 | log.error("[closeAll][关闭连接失败]", e); |
| 163 | 170 | } |
| 164 | 171 | } |
| 165 | - connectionMap.clear(); | |
| 166 | - deviceSocketMap.clear(); | |
| 167 | 172 | } |
| 168 | 173 | |
| 169 | 174 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePollScheduler.java
| ... | ... | @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotMod |
| 11 | 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo; |
| 12 | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlavePendingRequestManager.PendingRequest; |
| 13 | 13 | import io.vertx.core.Vertx; |
| 14 | +import lombok.Getter; | |
| 14 | 15 | import lombok.extern.slf4j.Slf4j; |
| 15 | 16 | |
| 16 | 17 | import java.util.concurrent.atomic.AtomicInteger; |
| ... | ... | @@ -31,6 +32,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul |
| 31 | 32 | /** |
| 32 | 33 | * TCP 事务 ID 自增器(与 DownstreamHandler 共享) |
| 33 | 34 | */ |
| 35 | + @Getter | |
| 34 | 36 | private final AtomicInteger transactionIdCounter; |
| 35 | 37 | |
| 36 | 38 | public IotModbusTcpSlavePollScheduler(Vertx vertx, | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttProtocol.java
| ... | ... | @@ -81,7 +81,7 @@ public class IotMqttProtocol implements IotProtocol { |
| 81 | 81 | /** |
| 82 | 82 | * 下行消息订阅者 |
| 83 | 83 | */ |
| 84 | - private final IotMqttDownstreamSubscriber downstreamSubscriber; | |
| 84 | + private IotMqttDownstreamSubscriber downstreamSubscriber; | |
| 85 | 85 | |
| 86 | 86 | private final IotDeviceMessageService deviceMessageService; |
| 87 | 87 | |
| ... | ... | @@ -104,11 +104,6 @@ public class IotMqttProtocol implements IotProtocol { |
| 104 | 104 | this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId); |
| 105 | 105 | this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService); |
| 106 | 106 | this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId); |
| 107 | - | |
| 108 | - // 初始化下行消息订阅者 | |
| 109 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 110 | - IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(deviceMessageService, connectionManager); | |
| 111 | - this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 112 | 107 | } |
| 113 | 108 | |
| 114 | 109 | @Override |
| ... | ... | @@ -157,18 +152,13 @@ public class IotMqttProtocol implements IotProtocol { |
| 157 | 152 | getId(), properties.getPort(), serverId); |
| 158 | 153 | |
| 159 | 154 | // 2. 启动下行消息订阅者 |
| 155 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 156 | + IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(deviceMessageService, connectionManager); | |
| 157 | + this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 160 | 158 | this.downstreamSubscriber.start(); |
| 161 | 159 | } catch (Exception e) { |
| 162 | 160 | log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e); |
| 163 | - // 启动失败时关闭资源 | |
| 164 | - if (mqttServer != null) { | |
| 165 | - mqttServer.close(); | |
| 166 | - mqttServer = null; | |
| 167 | - } | |
| 168 | - if (vertx != null) { | |
| 169 | - vertx.close(); | |
| 170 | - vertx = null; | |
| 171 | - } | |
| 161 | + stop0(); | |
| 172 | 162 | throw e; |
| 173 | 163 | } |
| 174 | 164 | } |
| ... | ... | @@ -178,15 +168,24 @@ public class IotMqttProtocol implements IotProtocol { |
| 178 | 168 | if (!running) { |
| 179 | 169 | return; |
| 180 | 170 | } |
| 171 | + stop0(); | |
| 172 | + } | |
| 173 | + | |
| 174 | + private void stop0() { | |
| 181 | 175 | // 1. 停止下行消息订阅者 |
| 182 | - try { | |
| 183 | - downstreamSubscriber.stop(); | |
| 184 | - log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId()); | |
| 185 | - } catch (Exception e) { | |
| 186 | - log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 176 | + if (downstreamSubscriber != null) { | |
| 177 | + try { | |
| 178 | + downstreamSubscriber.stop(); | |
| 179 | + log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId()); | |
| 180 | + } catch (Exception e) { | |
| 181 | + log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 182 | + } | |
| 183 | + downstreamSubscriber = null; | |
| 187 | 184 | } |
| 188 | 185 | |
| 189 | - // 2.1 关闭 MQTT 服务器 | |
| 186 | + // 2.1 关闭所有连接 | |
| 187 | + connectionManager.closeAll(); | |
| 188 | + // 2.2 关闭 MQTT 服务器 | |
| 190 | 189 | if (mqttServer != null) { |
| 191 | 190 | try { |
| 192 | 191 | mqttServer.close().result(); |
| ... | ... | @@ -196,7 +195,7 @@ public class IotMqttProtocol implements IotProtocol { |
| 196 | 195 | } |
| 197 | 196 | mqttServer = null; |
| 198 | 197 | } |
| 199 | - // 2.2 关闭 Vertx 实例 | |
| 198 | + // 2.3 关闭 Vertx 实例 | |
| 200 | 199 | if (vertx != null) { |
| 201 | 200 | try { |
| 202 | 201 | vertx.close().result(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
| ... | ... | @@ -8,6 +8,8 @@ import lombok.Data; |
| 8 | 8 | import lombok.extern.slf4j.Slf4j; |
| 9 | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | |
| 11 | +import java.util.ArrayList; | |
| 12 | +import java.util.List; | |
| 11 | 13 | import java.util.Map; |
| 12 | 14 | import java.util.concurrent.ConcurrentHashMap; |
| 13 | 15 | |
| ... | ... | @@ -167,6 +169,24 @@ public class IotMqttConnectionManager { |
| 167 | 169 | } |
| 168 | 170 | |
| 169 | 171 | /** |
| 172 | + * 关闭所有连接 | |
| 173 | + */ | |
| 174 | + public void closeAll() { | |
| 175 | + // 1. 先复制再清空,避免 closeHandler 回调时并发修改 | |
| 176 | + List<MqttEndpoint> endpoints = new ArrayList<>(connectionMap.keySet()); | |
| 177 | + connectionMap.clear(); | |
| 178 | + deviceEndpointMap.clear(); | |
| 179 | + // 2. 关闭所有连接(closeHandler 中 unregisterConnection 发现 map 为空会安全跳过) | |
| 180 | + for (MqttEndpoint endpoint : endpoints) { | |
| 181 | + try { | |
| 182 | + endpoint.close(); | |
| 183 | + } catch (Exception ignored) { | |
| 184 | + // 连接可能已关闭,忽略异常 | |
| 185 | + } | |
| 186 | + } | |
| 187 | + } | |
| 188 | + | |
| 189 | + /** | |
| 170 | 190 | * 连接信息 |
| 171 | 191 | */ |
| 172 | 192 | @Data | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java
| ... | ... | @@ -66,7 +66,7 @@ public class IotTcpProtocol implements IotProtocol { |
| 66 | 66 | /** |
| 67 | 67 | * 下行消息订阅者 |
| 68 | 68 | */ |
| 69 | - private final IotTcpDownstreamSubscriber downstreamSubscriber; | |
| 69 | + private IotTcpDownstreamSubscriber downstreamSubscriber; | |
| 70 | 70 | |
| 71 | 71 | /** |
| 72 | 72 | * 消息序列化器 |
| ... | ... | @@ -94,11 +94,6 @@ public class IotTcpProtocol implements IotProtocol { |
| 94 | 94 | |
| 95 | 95 | // 初始化连接管理器 |
| 96 | 96 | this.connectionManager = new IotTcpConnectionManager(tcpConfig.getMaxConnections()); |
| 97 | - | |
| 98 | - // 初始化下行消息订阅者 | |
| 99 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 100 | - IotTcpDownstreamHandler downstreamHandler = new IotTcpDownstreamHandler(connectionManager, frameCodec, serializer); | |
| 101 | - this.downstreamSubscriber = new IotTcpDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 102 | 97 | } |
| 103 | 98 | |
| 104 | 99 | @Override |
| ... | ... | @@ -152,18 +147,13 @@ public class IotTcpProtocol implements IotProtocol { |
| 152 | 147 | getId(), properties.getPort(), serverId); |
| 153 | 148 | |
| 154 | 149 | // 2. 启动下行消息订阅者 |
| 150 | + IotTcpDownstreamHandler downstreamHandler = new IotTcpDownstreamHandler(connectionManager, frameCodec, serializer); | |
| 151 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 152 | + this.downstreamSubscriber = new IotTcpDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 155 | 153 | this.downstreamSubscriber.start(); |
| 156 | 154 | } catch (Exception e) { |
| 157 | 155 | log.error("[start][IoT TCP 协议 {} 启动失败]", getId(), e); |
| 158 | - // 启动失败时关闭资源 | |
| 159 | - if (tcpServer != null) { | |
| 160 | - tcpServer.close(); | |
| 161 | - tcpServer = null; | |
| 162 | - } | |
| 163 | - if (vertx != null) { | |
| 164 | - vertx.close(); | |
| 165 | - vertx = null; | |
| 166 | - } | |
| 156 | + stop0(); | |
| 167 | 157 | throw e; |
| 168 | 158 | } |
| 169 | 159 | } |
| ... | ... | @@ -173,15 +163,24 @@ public class IotTcpProtocol implements IotProtocol { |
| 173 | 163 | if (!running) { |
| 174 | 164 | return; |
| 175 | 165 | } |
| 166 | + stop0(); | |
| 167 | + } | |
| 168 | + | |
| 169 | + private void stop0() { | |
| 176 | 170 | // 1. 停止下行消息订阅者 |
| 177 | - try { | |
| 178 | - downstreamSubscriber.stop(); | |
| 179 | - log.info("[stop][IoT TCP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 180 | - } catch (Exception e) { | |
| 181 | - log.error("[stop][IoT TCP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 171 | + if (downstreamSubscriber != null) { | |
| 172 | + try { | |
| 173 | + downstreamSubscriber.stop(); | |
| 174 | + log.info("[stop][IoT TCP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 175 | + } catch (Exception e) { | |
| 176 | + log.error("[stop][IoT TCP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 177 | + } | |
| 178 | + downstreamSubscriber = null; | |
| 182 | 179 | } |
| 183 | 180 | |
| 184 | - // 2.1 关闭 TCP 服务器 | |
| 181 | + // 2.1 关闭所有连接 | |
| 182 | + connectionManager.closeAll(); | |
| 183 | + // 2.2 关闭 TCP 服务器 | |
| 185 | 184 | if (tcpServer != null) { |
| 186 | 185 | try { |
| 187 | 186 | tcpServer.close().result(); |
| ... | ... | @@ -191,7 +190,7 @@ public class IotTcpProtocol implements IotProtocol { |
| 191 | 190 | } |
| 192 | 191 | tcpServer = null; |
| 193 | 192 | } |
| 194 | - // 2.2 关闭 Vertx 实例 | |
| 193 | + // 2.3 关闭 Vertx 实例 | |
| 195 | 194 | if (vertx != null) { |
| 196 | 195 | try { |
| 197 | 196 | vertx.close().result(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpConnectionManager.java
| ... | ... | @@ -5,6 +5,8 @@ import io.vertx.core.net.NetSocket; |
| 5 | 5 | import lombok.Data; |
| 6 | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | 7 | |
| 8 | +import java.util.ArrayList; | |
| 9 | +import java.util.List; | |
| 8 | 10 | import java.util.Map; |
| 9 | 11 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | 12 | |
| ... | ... | @@ -123,6 +125,24 @@ public class IotTcpConnectionManager { |
| 123 | 125 | } |
| 124 | 126 | |
| 125 | 127 | /** |
| 128 | + * 关闭所有连接 | |
| 129 | + */ | |
| 130 | + public void closeAll() { | |
| 131 | + // 1. 先复制再清空,避免 closeHandler 回调时并发修改 | |
| 132 | + List<NetSocket> sockets = new ArrayList<>(connectionMap.keySet()); | |
| 133 | + connectionMap.clear(); | |
| 134 | + deviceSocketMap.clear(); | |
| 135 | + // 2. 关闭所有连接(closeHandler 中 unregisterConnection 发现 map 为空会安全跳过) | |
| 136 | + for (NetSocket socket : sockets) { | |
| 137 | + try { | |
| 138 | + socket.close(); | |
| 139 | + } catch (Exception ignored) { | |
| 140 | + // 连接可能已关闭,忽略异常 | |
| 141 | + } | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + /** | |
| 126 | 146 | * 连接信息(包含认证信息) |
| 127 | 147 | */ |
| 128 | 148 | @Data | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java
| ... | ... | @@ -63,7 +63,7 @@ public class IotUdpProtocol implements IotProtocol { |
| 63 | 63 | /** |
| 64 | 64 | * 下行消息订阅者 |
| 65 | 65 | */ |
| 66 | - private final IotUdpDownstreamSubscriber downstreamSubscriber; | |
| 66 | + private IotUdpDownstreamSubscriber downstreamSubscriber; | |
| 67 | 67 | |
| 68 | 68 | /** |
| 69 | 69 | * 消息序列化器 |
| ... | ... | @@ -85,10 +85,6 @@ public class IotUdpProtocol implements IotProtocol { |
| 85 | 85 | // 初始化会话管理器 |
| 86 | 86 | this.sessionManager = new IotUdpSessionManager(udpConfig.getMaxSessions(), udpConfig.getSessionTimeoutMs()); |
| 87 | 87 | |
| 88 | - // 初始化下行消息订阅者 | |
| 89 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 90 | - IotUdpDownstreamHandler downstreamHandler = new IotUdpDownstreamHandler(this, sessionManager, serializer); | |
| 91 | - this.downstreamSubscriber = new IotUdpDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 92 | 88 | } |
| 93 | 89 | |
| 94 | 90 | @Override |
| ... | ... | @@ -108,8 +104,11 @@ public class IotUdpProtocol implements IotProtocol { |
| 108 | 104 | return; |
| 109 | 105 | } |
| 110 | 106 | |
| 111 | - // 1.1 创建 Vertx 实例 | |
| 107 | + // 1.1 创建 Vertx 实例 和 下行消息订阅者 | |
| 112 | 108 | this.vertx = Vertx.vertx(); |
| 109 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 110 | + IotUdpDownstreamHandler downstreamHandler = new IotUdpDownstreamHandler(this, sessionManager, serializer); | |
| 111 | + this.downstreamSubscriber = new IotUdpDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 113 | 112 | |
| 114 | 113 | // 1.2 创建 UDP Socket 选项 |
| 115 | 114 | IotUdpConfig udpConfig = properties.getUdp(); |
| ... | ... | @@ -137,15 +136,7 @@ public class IotUdpProtocol implements IotProtocol { |
| 137 | 136 | this.downstreamSubscriber.start(); |
| 138 | 137 | } catch (Exception e) { |
| 139 | 138 | log.error("[start][IoT UDP 协议 {} 启动失败]", getId(), e); |
| 140 | - // 启动失败时关闭资源 | |
| 141 | - if (udpSocket != null) { | |
| 142 | - udpSocket.close(); | |
| 143 | - udpSocket = null; | |
| 144 | - } | |
| 145 | - if (vertx != null) { | |
| 146 | - vertx.close(); | |
| 147 | - vertx = null; | |
| 148 | - } | |
| 139 | + stop0(); | |
| 149 | 140 | throw e; |
| 150 | 141 | } |
| 151 | 142 | } |
| ... | ... | @@ -155,12 +146,19 @@ public class IotUdpProtocol implements IotProtocol { |
| 155 | 146 | if (!running) { |
| 156 | 147 | return; |
| 157 | 148 | } |
| 149 | + stop0(); | |
| 150 | + } | |
| 151 | + | |
| 152 | + private void stop0() { | |
| 158 | 153 | // 1. 停止下行消息订阅者 |
| 159 | - try { | |
| 160 | - downstreamSubscriber.stop(); | |
| 161 | - log.info("[stop][IoT UDP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 162 | - } catch (Exception e) { | |
| 163 | - log.error("[stop][IoT UDP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 154 | + if (downstreamSubscriber != null) { | |
| 155 | + try { | |
| 156 | + downstreamSubscriber.stop(); | |
| 157 | + log.info("[stop][IoT UDP 协议 {} 下行消息订阅者已停止]", getId()); | |
| 158 | + } catch (Exception e) { | |
| 159 | + log.error("[stop][IoT UDP 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 160 | + } | |
| 161 | + downstreamSubscriber = null; | |
| 164 | 162 | } |
| 165 | 163 | |
| 166 | 164 | // 2.1 关闭 UDP Socket | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 3 | 4 | import cn.hutool.core.util.ObjUtil; |
| 4 | 5 | import cn.hutool.extra.spring.SpringUtil; |
| 5 | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; |
| ... | ... | @@ -9,8 +10,8 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 9 | 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 10 | 11 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties; |
| 11 | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; |
| 12 | -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamSubscriber; | |
| 13 | 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamHandler; |
| 14 | +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamSubscriber; | |
| 14 | 15 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream.IotWebSocketUpstreamHandler; |
| 15 | 16 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager; |
| 16 | 17 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; |
| ... | ... | @@ -21,7 +22,6 @@ import io.vertx.core.http.HttpServerOptions; |
| 21 | 22 | import io.vertx.core.net.PemKeyCertOptions; |
| 22 | 23 | import lombok.Getter; |
| 23 | 24 | import lombok.extern.slf4j.Slf4j; |
| 24 | -import cn.hutool.core.lang.Assert; | |
| 25 | 25 | |
| 26 | 26 | /** |
| 27 | 27 | * IoT WebSocket 协议实现 |
| ... | ... | @@ -65,7 +65,7 @@ public class IotWebSocketProtocol implements IotProtocol { |
| 65 | 65 | /** |
| 66 | 66 | * 下行消息订阅者 |
| 67 | 67 | */ |
| 68 | - private final IotWebSocketDownstreamSubscriber downstreamSubscriber; | |
| 68 | + private IotWebSocketDownstreamSubscriber downstreamSubscriber; | |
| 69 | 69 | |
| 70 | 70 | /** |
| 71 | 71 | * 消息序列化器 |
| ... | ... | @@ -87,10 +87,6 @@ public class IotWebSocketProtocol implements IotProtocol { |
| 87 | 87 | // 初始化连接管理器 |
| 88 | 88 | this.connectionManager = new IotWebSocketConnectionManager(); |
| 89 | 89 | |
| 90 | - // 初始化下行消息订阅者 | |
| 91 | - IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 92 | - IotWebSocketDownstreamHandler downstreamHandler = new IotWebSocketDownstreamHandler(serializer, connectionManager); | |
| 93 | - this.downstreamSubscriber = new IotWebSocketDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 94 | 90 | } |
| 95 | 91 | |
| 96 | 92 | @Override |
| ... | ... | @@ -152,17 +148,13 @@ public class IotWebSocketProtocol implements IotProtocol { |
| 152 | 148 | getId(), properties.getPort(), wsConfig.getPath(), serverId); |
| 153 | 149 | |
| 154 | 150 | // 2. 启动下行消息订阅者 |
| 151 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | |
| 152 | + IotWebSocketDownstreamHandler downstreamHandler = new IotWebSocketDownstreamHandler(serializer, connectionManager); | |
| 153 | + this.downstreamSubscriber = new IotWebSocketDownstreamSubscriber(this, downstreamHandler, messageBus); | |
| 155 | 154 | downstreamSubscriber.start(); |
| 156 | 155 | } catch (Exception e) { |
| 157 | 156 | log.error("[start][IoT WebSocket 协议 {} 启动失败]", getId(), e); |
| 158 | - if (httpServer != null) { | |
| 159 | - httpServer.close(); | |
| 160 | - httpServer = null; | |
| 161 | - } | |
| 162 | - if (vertx != null) { | |
| 163 | - vertx.close(); | |
| 164 | - vertx = null; | |
| 165 | - } | |
| 157 | + stop0(); | |
| 166 | 158 | throw e; |
| 167 | 159 | } |
| 168 | 160 | } |
| ... | ... | @@ -172,15 +164,24 @@ public class IotWebSocketProtocol implements IotProtocol { |
| 172 | 164 | if (!running) { |
| 173 | 165 | return; |
| 174 | 166 | } |
| 167 | + stop0(); | |
| 168 | + } | |
| 169 | + | |
| 170 | + private void stop0() { | |
| 175 | 171 | // 1. 停止下行消息订阅者 |
| 176 | - try { | |
| 177 | - downstreamSubscriber.stop(); | |
| 178 | - log.info("[stop][IoT WebSocket 协议 {} 下行消息订阅者已停止]", getId()); | |
| 179 | - } catch (Exception e) { | |
| 180 | - log.error("[stop][IoT WebSocket 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 172 | + if (downstreamSubscriber != null) { | |
| 173 | + try { | |
| 174 | + downstreamSubscriber.stop(); | |
| 175 | + log.info("[stop][IoT WebSocket 协议 {} 下行消息订阅者已停止]", getId()); | |
| 176 | + } catch (Exception e) { | |
| 177 | + log.error("[stop][IoT WebSocket 协议 {} 下行消息订阅者停止失败]", getId(), e); | |
| 178 | + } | |
| 179 | + downstreamSubscriber = null; | |
| 181 | 180 | } |
| 182 | 181 | |
| 183 | - // 2.1 关闭 WebSocket 服务器 | |
| 182 | + // 2.1 关闭所有连接 | |
| 183 | + connectionManager.closeAll(); | |
| 184 | + // 2.2 关闭 WebSocket 服务器 | |
| 184 | 185 | if (httpServer != null) { |
| 185 | 186 | try { |
| 186 | 187 | httpServer.close().result(); |
| ... | ... | @@ -190,7 +191,7 @@ public class IotWebSocketProtocol implements IotProtocol { |
| 190 | 191 | } |
| 191 | 192 | httpServer = null; |
| 192 | 193 | } |
| 193 | - // 2.2 关闭 Vertx 实例 | |
| 194 | + // 2.3 关闭 Vertx 实例 | |
| 194 | 195 | if (vertx != null) { |
| 195 | 196 | try { |
| 196 | 197 | vertx.close().result(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/manager/IotWebSocketConnectionManager.java
| ... | ... | @@ -5,6 +5,8 @@ import lombok.Data; |
| 5 | 5 | import lombok.experimental.Accessors; |
| 6 | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | 7 | |
| 8 | +import java.util.ArrayList; | |
| 9 | +import java.util.List; | |
| 8 | 10 | import java.util.Map; |
| 9 | 11 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | 12 | |
| ... | ... | @@ -115,6 +117,24 @@ public class IotWebSocketConnectionManager { |
| 115 | 117 | } |
| 116 | 118 | |
| 117 | 119 | /** |
| 120 | + * 关闭所有连接 | |
| 121 | + */ | |
| 122 | + public void closeAll() { | |
| 123 | + // 1. 先复制再清空,避免 closeHandler 回调时并发修改 | |
| 124 | + List<ServerWebSocket> sockets = new ArrayList<>(connectionMap.keySet()); | |
| 125 | + connectionMap.clear(); | |
| 126 | + deviceSocketMap.clear(); | |
| 127 | + // 2. 关闭所有连接(closeHandler 中 unregisterConnection 发现 map 为空会安全跳过) | |
| 128 | + for (ServerWebSocket socket : sockets) { | |
| 129 | + try { | |
| 130 | + socket.close(); | |
| 131 | + } catch (Exception ignored) { | |
| 132 | + // 连接可能已关闭,忽略异常 | |
| 133 | + } | |
| 134 | + } | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 118 | 138 | * 连接信息(包含认证信息) |
| 119 | 139 | */ |
| 120 | 140 | @Data | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
| ... | ... | @@ -167,7 +167,7 @@ yudao: |
| 167 | 167 | # 针对引入的 Modbus TCP Master 组件的配置 |
| 168 | 168 | # ==================================== |
| 169 | 169 | - id: modbus-tcp-master-1 |
| 170 | - enabled: true | |
| 170 | + enabled: false | |
| 171 | 171 | protocol: modbus_tcp_master |
| 172 | 172 | port: 502 |
| 173 | 173 | modbus-tcp-master: |
| ... | ... | @@ -176,7 +176,7 @@ yudao: |
| 176 | 176 | # 针对引入的 Modbus TCP Slave 组件的配置 |
| 177 | 177 | # ==================================== |
| 178 | 178 | - id: modbus-tcp-slave-1 |
| 179 | - enabled: true | |
| 179 | + enabled: false | |
| 180 | 180 | protocol: modbus_tcp_slave |
| 181 | 181 | port: 503 |
| 182 | 182 | modbus-tcp-slave: | ... | ... |