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,7 +70,6 @@ public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigSe | ||
| 70 | return modbusConfigMapper.selectList(listReqDTO); | 70 | return modbusConfigMapper.selectList(listReqDTO); |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | - // TODO @AI:还是希望在 IotDeviceModbusConfigSaveReqVO 里,通过 validator 校验?!或者通过 group 来处理? | ||
| 74 | private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) { | 73 | private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) { |
| 75 | IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType); | 74 | IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType); |
| 76 | if (protocolTypeEnum == null) { | 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,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 | public IotCoapProtocol(ProtocolProperties properties) { | 69 | public IotCoapProtocol(ProtocolProperties properties) { |
| 70 | IotCoapConfig coapConfig = properties.getCoap(); | 70 | IotCoapConfig coapConfig = properties.getCoap(); |
| 71 | Assert.notNull(coapConfig, "CoAP 协议配置(coap)不能为空"); | 71 | Assert.notNull(coapConfig, "CoAP 协议配置(coap)不能为空"); |
| 72 | this.properties = properties; | 72 | this.properties = properties; |
| 73 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); | 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 | @Override | 76 | @Override |
| @@ -94,9 +90,9 @@ public class IotCoapProtocol implements IotProtocol { | @@ -94,9 +90,9 @@ public class IotCoapProtocol implements IotProtocol { | ||
| 94 | return; | 90 | return; |
| 95 | } | 91 | } |
| 96 | 92 | ||
| 97 | - IotCoapConfig coapConfig = properties.getCoap(); | ||
| 98 | try { | 93 | try { |
| 99 | // 1.1 创建 CoAP 配置 | 94 | // 1.1 创建 CoAP 配置 |
| 95 | + IotCoapConfig coapConfig = properties.getCoap(); | ||
| 100 | Configuration config = Configuration.createStandardWithoutFile(); | 96 | Configuration config = Configuration.createStandardWithoutFile(); |
| 101 | config.set(CoapConfig.COAP_PORT, properties.getPort()); | 97 | config.set(CoapConfig.COAP_PORT, properties.getPort()); |
| 102 | config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize()); | 98 | config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize()); |
| @@ -131,13 +127,12 @@ public class IotCoapProtocol implements IotProtocol { | @@ -131,13 +127,12 @@ public class IotCoapProtocol implements IotProtocol { | ||
| 131 | getId(), properties.getPort(), serverId); | 127 | getId(), properties.getPort(), serverId); |
| 132 | 128 | ||
| 133 | // 4. 启动下行消息订阅者 | 129 | // 4. 启动下行消息订阅者 |
| 130 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | ||
| 131 | + this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus); | ||
| 134 | this.downstreamSubscriber.start(); | 132 | this.downstreamSubscriber.start(); |
| 135 | } catch (Exception e) { | 133 | } catch (Exception e) { |
| 136 | log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e); | 134 | log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e); |
| 137 | - if (coapServer != null) { | ||
| 138 | - coapServer.destroy(); | ||
| 139 | - coapServer = null; | ||
| 140 | - } | 135 | + stop0(); |
| 141 | throw e; | 136 | throw e; |
| 142 | } | 137 | } |
| 143 | } | 138 | } |
| @@ -147,12 +142,19 @@ public class IotCoapProtocol implements IotProtocol { | @@ -147,12 +142,19 @@ public class IotCoapProtocol implements IotProtocol { | ||
| 147 | if (!running) { | 142 | if (!running) { |
| 148 | return; | 143 | return; |
| 149 | } | 144 | } |
| 145 | + stop0(); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + private void stop0() { | ||
| 150 | // 1. 停止下行消息订阅者 | 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 | // 2. 关闭 CoAP 服务器 | 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,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 | public IotEmqxProtocol(ProtocolProperties properties) { | 95 | public IotEmqxProtocol(ProtocolProperties properties) { |
| 96 | Assert.notNull(properties, "协议实例配置不能为空"); | 96 | Assert.notNull(properties, "协议实例配置不能为空"); |
| @@ -101,10 +101,6 @@ public class IotEmqxProtocol implements IotProtocol { | @@ -101,10 +101,6 @@ public class IotEmqxProtocol implements IotProtocol { | ||
| 101 | "MQTT 连接超时时间(emqx.connect-timeout-seconds)不能为空"); | 101 | "MQTT 连接超时时间(emqx.connect-timeout-seconds)不能为空"); |
| 102 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); | 102 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); |
| 103 | this.upstreamHandler = new IotEmqxUpstreamHandler(serverId); | 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 | @Override | 106 | @Override |
| @@ -124,7 +120,7 @@ public class IotEmqxProtocol implements IotProtocol { | @@ -124,7 +120,7 @@ public class IotEmqxProtocol implements IotProtocol { | ||
| 124 | return; | 120 | return; |
| 125 | } | 121 | } |
| 126 | 122 | ||
| 127 | - // 1.1 创建 Vertx 实例 | 123 | + // 1.1 创建 Vertx 实例 和 下行消息订阅者 |
| 128 | this.vertx = Vertx.vertx(); | 124 | this.vertx = Vertx.vertx(); |
| 129 | 125 | ||
| 130 | try { | 126 | try { |
| @@ -138,6 +134,8 @@ public class IotEmqxProtocol implements IotProtocol { | @@ -138,6 +134,8 @@ public class IotEmqxProtocol implements IotProtocol { | ||
| 138 | getId(), properties.getPort(), serverId); | 134 | getId(), properties.getPort(), serverId); |
| 139 | 135 | ||
| 140 | // 2. 启动下行消息订阅者 | 136 | // 2. 启动下行消息订阅者 |
| 137 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | ||
| 138 | + this.downstreamSubscriber = new IotEmqxDownstreamSubscriber(this, messageBus); | ||
| 141 | this.downstreamSubscriber.start(); | 139 | this.downstreamSubscriber.start(); |
| 142 | } catch (Exception e) { | 140 | } catch (Exception e) { |
| 143 | log.error("[start][IoT EMQX 协议 {} 启动失败]", getId(), e); | 141 | log.error("[start][IoT EMQX 协议 {} 启动失败]", getId(), e); |
| @@ -157,11 +155,14 @@ public class IotEmqxProtocol implements IotProtocol { | @@ -157,11 +155,14 @@ public class IotEmqxProtocol implements IotProtocol { | ||
| 157 | 155 | ||
| 158 | private void stop0() { | 156 | private void stop0() { |
| 159 | // 1. 停止下行消息订阅者 | 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 | // 2.1 先置为 false:避免 closeHandler 触发重连 | 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,10 +64,6 @@ public class IotHttpProtocol implements IotProtocol { | ||
| 64 | public IotHttpProtocol(ProtocolProperties properties) { | 64 | public IotHttpProtocol(ProtocolProperties properties) { |
| 65 | this.properties = properties; | 65 | this.properties = properties; |
| 66 | this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort()); | 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 | @Override | 69 | @Override |
| @@ -87,7 +83,7 @@ public class IotHttpProtocol implements IotProtocol { | @@ -87,7 +83,7 @@ public class IotHttpProtocol implements IotProtocol { | ||
| 87 | return; | 83 | return; |
| 88 | } | 84 | } |
| 89 | 85 | ||
| 90 | - // 1.1 创建 Vertx 实例(每个 Protocol 独立管理) | 86 | + // 1.1 创建 Vertx 实例 |
| 91 | this.vertx = Vertx.vertx(); | 87 | this.vertx = Vertx.vertx(); |
| 92 | 88 | ||
| 93 | // 1.2 创建路由 | 89 | // 1.2 创建路由 |
| @@ -123,18 +119,12 @@ public class IotHttpProtocol implements IotProtocol { | @@ -123,18 +119,12 @@ public class IotHttpProtocol implements IotProtocol { | ||
| 123 | getId(), properties.getPort(), serverId); | 119 | getId(), properties.getPort(), serverId); |
| 124 | 120 | ||
| 125 | // 2. 启动下行消息订阅者 | 121 | // 2. 启动下行消息订阅者 |
| 122 | + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class); | ||
| 123 | + this.downstreamSubscriber = new IotHttpDownstreamSubscriber(this, messageBus); | ||
| 126 | this.downstreamSubscriber.start(); | 124 | this.downstreamSubscriber.start(); |
| 127 | } catch (Exception e) { | 125 | } catch (Exception e) { |
| 128 | log.error("[start][IoT HTTP 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 128 | throw e; |
| 139 | } | 129 | } |
| 140 | } | 130 | } |
| @@ -144,6 +134,10 @@ public class IotHttpProtocol implements IotProtocol { | @@ -144,6 +134,10 @@ public class IotHttpProtocol implements IotProtocol { | ||
| 144 | if (!running) { | 134 | if (!running) { |
| 145 | return; | 135 | return; |
| 146 | } | 136 | } |
| 137 | + stop0(); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + private void stop0() { | ||
| 147 | // 1. 停止下行消息订阅者 | 141 | // 1. 停止下行消息订阅者 |
| 148 | if (downstreamSubscriber != null) { | 142 | if (downstreamSubscriber != null) { |
| 149 | try { | 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,8 +381,11 @@ public class IotModbusCommonUtils { | ||
| 381 | case UINT16: | 381 | case UINT16: |
| 382 | return new int[]{rawValue.intValue() & 0xFFFF}; | 382 | return new int[]{rawValue.intValue() & 0xFFFF}; |
| 383 | case INT32: | 383 | case INT32: |
| 384 | - case UINT32: | ||
| 385 | return encodeInt32(rawValue.intValue(), byteOrder); | 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 | case FLOAT: | 389 | case FLOAT: |
| 387 | return encodeFloat(rawValue.floatValue(), byteOrder); | 390 | return encodeFloat(rawValue.floatValue(), byteOrder); |
| 388 | case DOUBLE: | 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,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 | private final IotModbusTcpMasterConfigCacheService configCacheService; | 70 | private final IotModbusTcpMasterConfigCacheService configCacheService; |
| 71 | private final IotModbusTcpMasterPollScheduler pollScheduler; | 71 | private final IotModbusTcpMasterPollScheduler pollScheduler; |
| @@ -89,15 +89,9 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -89,15 +89,9 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 89 | 89 | ||
| 90 | // 初始化 Handler | 90 | // 初始化 Handler |
| 91 | IotModbusTcpMasterUpstreamHandler upstreamHandler = new IotModbusTcpMasterUpstreamHandler(messageService, serverId); | 91 | IotModbusTcpMasterUpstreamHandler upstreamHandler = new IotModbusTcpMasterUpstreamHandler(messageService, serverId); |
| 92 | - IotModbusTcpMasterDownstreamHandler downstreamHandler = new IotModbusTcpMasterDownstreamHandler(connectionManager, | ||
| 93 | - configCacheService); | ||
| 94 | 92 | ||
| 95 | // 初始化轮询调度器 | 93 | // 初始化轮询调度器 |
| 96 | this.pollScheduler = new IotModbusTcpMasterPollScheduler(vertx, connectionManager, upstreamHandler, configCacheService); | 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 | @Override | 97 | @Override |
| @@ -130,13 +124,14 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -130,13 +124,14 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 130 | log.info("[start][IoT Modbus TCP Master 协议 {} 启动成功,serverId={}]", getId(), serverId); | 124 | log.info("[start][IoT Modbus TCP Master 协议 {} 启动成功,serverId={}]", getId(), serverId); |
| 131 | 125 | ||
| 132 | // 2. 启动下行消息订阅者 | 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 | this.downstreamSubscriber.start(); | 131 | this.downstreamSubscriber.start(); |
| 134 | } catch (Exception e) { | 132 | } catch (Exception e) { |
| 135 | log.error("[start][IoT Modbus TCP Master 协议 {} 启动失败]", getId(), e); | 133 | log.error("[start][IoT Modbus TCP Master 协议 {} 启动失败]", getId(), e); |
| 136 | - // 启动失败时关闭资源 | ||
| 137 | - if (vertx != null) { | ||
| 138 | - vertx.close(); | ||
| 139 | - } | 134 | + stop0(); |
| 140 | throw e; | 135 | throw e; |
| 141 | } | 136 | } |
| 142 | } | 137 | } |
| @@ -146,12 +141,19 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -146,12 +141,19 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 146 | if (!running) { | 141 | if (!running) { |
| 147 | return; | 142 | return; |
| 148 | } | 143 | } |
| 144 | + stop0(); | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + private void stop0() { | ||
| 149 | // 1. 停止下行消息订阅者 | 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 | // 2.1 取消配置刷新定时器 | 159 | // 2.1 取消配置刷新定时器 |
| @@ -161,10 +163,8 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | @@ -161,10 +163,8 @@ public class IotModbusTcpMasterProtocol implements IotProtocol { | ||
| 161 | } | 163 | } |
| 162 | // 2.2 停止轮询调度器 | 164 | // 2.2 停止轮询调度器 |
| 163 | pollScheduler.stopAll(); | 165 | pollScheduler.stopAll(); |
| 164 | - log.info("[stop][IoT Modbus TCP Master 协议 {} 轮询调度器已停止]", getId()); | ||
| 165 | // 2.3 关闭所有连接 | 166 | // 2.3 关闭所有连接 |
| 166 | connectionManager.closeAll(); | 167 | connectionManager.closeAll(); |
| 167 | - log.info("[stop][IoT Modbus TCP Master 协议 {} 连接管理器已关闭]", getId()); | ||
| 168 | 168 | ||
| 169 | // 3. 关闭 Vert.x 实例 | 169 | // 3. 关闭 Vert.x 实例 |
| 170 | if (vertx != null) { | 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,6 +14,8 @@ import org.redisson.api.RLock; | ||
| 14 | import org.redisson.api.RedissonClient; | 14 | import org.redisson.api.RedissonClient; |
| 15 | 15 | ||
| 16 | import java.net.InetAddress; | 16 | import java.net.InetAddress; |
| 17 | +import java.util.ArrayList; | ||
| 18 | +import java.util.List; | ||
| 17 | import java.util.Map; | 19 | import java.util.Map; |
| 18 | import java.util.concurrent.ConcurrentHashMap; | 20 | import java.util.concurrent.ConcurrentHashMap; |
| 19 | 21 | ||
| @@ -185,8 +187,11 @@ public class IotModbusTcpMasterConnectionManager { | @@ -185,8 +187,11 @@ public class IotModbusTcpMasterConnectionManager { | ||
| 185 | */ | 187 | */ |
| 186 | private void addDeviceAndOnline(ModbusConnection connection, | 188 | private void addDeviceAndOnline(ModbusConnection connection, |
| 187 | IotModbusDeviceConfigRespDTO config) { | 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,7 +252,9 @@ public class IotModbusTcpMasterConnectionManager { | ||
| 247 | * 关闭所有连接 | 252 | * 关闭所有连接 |
| 248 | */ | 253 | */ |
| 249 | public void closeAll() { | 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 | closeConnection(connectionKey); | 258 | closeConnection(connectionKey); |
| 252 | } | 259 | } |
| 253 | deviceConnectionMap.clear(); | 260 | deviceConnectionMap.clear(); |
| @@ -282,8 +289,8 @@ public class IotModbusTcpMasterConnectionManager { | @@ -282,8 +289,8 @@ public class IotModbusTcpMasterConnectionManager { | ||
| 282 | */ | 289 | */ |
| 283 | private Context context; | 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 | public void removeDevice(Long deviceId) { | 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,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 | private final IotModbusFrameDecoder frameDecoder; | 93 | private final IotModbusFrameDecoder frameDecoder; |
| 94 | @SuppressWarnings("FieldCanBeLocal") | 94 | @SuppressWarnings("FieldCanBeLocal") |
| @@ -121,7 +121,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -121,7 +121,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 121 | 121 | ||
| 122 | // 初始化共享事务 ID 自增器(PollScheduler 和 DownstreamHandler 共用,避免 transactionId 冲突) | 122 | // 初始化共享事务 ID 自增器(PollScheduler 和 DownstreamHandler 共用,避免 transactionId 冲突) |
| 123 | AtomicInteger transactionIdCounter = new AtomicInteger(0); | 123 | AtomicInteger transactionIdCounter = new AtomicInteger(0); |
| 124 | - | ||
| 125 | // 初始化轮询调度器 | 124 | // 初始化轮询调度器 |
| 126 | this.pollScheduler = new IotModbusTcpSlavePollScheduler( | 125 | this.pollScheduler = new IotModbusTcpSlavePollScheduler( |
| 127 | vertx, connectionManager, frameEncoder, pendingRequestManager, | 126 | vertx, connectionManager, frameEncoder, pendingRequestManager, |
| @@ -134,13 +133,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -134,13 +133,6 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 134 | deviceApi, this.messageService, frameEncoder, | 133 | deviceApi, this.messageService, frameEncoder, |
| 135 | connectionManager, configCacheService, pendingRequestManager, | 134 | connectionManager, configCacheService, pendingRequestManager, |
| 136 | pollScheduler, deviceService, serverId); | 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 | @Override | 138 | @Override |
| @@ -179,25 +171,15 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -179,25 +171,15 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 179 | getId(), serverId, properties.getPort()); | 171 | getId(), serverId, properties.getPort()); |
| 180 | 172 | ||
| 181 | // 3. 启动下行消息订阅 | 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 | downstreamSubscriber.start(); | 179 | downstreamSubscriber.start(); |
| 183 | } catch (Exception e) { | 180 | } catch (Exception e) { |
| 184 | log.error("[start][IoT Modbus TCP Slave 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 183 | throw e; |
| 202 | } | 184 | } |
| 203 | } | 185 | } |
| @@ -207,12 +189,18 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -207,12 +189,18 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 207 | if (!running) { | 189 | if (!running) { |
| 208 | return; | 190 | return; |
| 209 | } | 191 | } |
| 192 | + stop0(); | ||
| 193 | + } | ||
| 210 | 194 | ||
| 195 | + private void stop0() { | ||
| 211 | // 1. 停止下行消息订阅 | 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 | // 2.1 取消定时器 | 206 | // 2.1 取消定时器 |
| @@ -238,6 +226,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | @@ -238,6 +226,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { | ||
| 238 | } catch (Exception e) { | 226 | } catch (Exception e) { |
| 239 | log.error("[stop][TCP Server 关闭失败]", e); | 227 | log.error("[stop][TCP Server 关闭失败]", e); |
| 240 | } | 228 | } |
| 229 | + netServer = null; | ||
| 241 | } | 230 | } |
| 242 | 231 | ||
| 243 | // 3. 关闭 Vertx | 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,6 +8,8 @@ import lombok.Data; | ||
| 8 | import lombok.experimental.Accessors; | 8 | import lombok.experimental.Accessors; |
| 9 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 10 | 10 | ||
| 11 | +import java.util.ArrayList; | ||
| 12 | +import java.util.List; | ||
| 11 | import java.util.Map; | 13 | import java.util.Map; |
| 12 | import java.util.Set; | 14 | import java.util.Set; |
| 13 | import java.util.concurrent.ConcurrentHashMap; | 15 | import java.util.concurrent.ConcurrentHashMap; |
| @@ -155,15 +157,18 @@ public class IotModbusTcpSlaveConnectionManager { | @@ -155,15 +157,18 @@ public class IotModbusTcpSlaveConnectionManager { | ||
| 155 | * 关闭所有连接 | 157 | * 关闭所有连接 |
| 156 | */ | 158 | */ |
| 157 | public void closeAll() { | 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 | try { | 166 | try { |
| 160 | socket.close(); | 167 | socket.close(); |
| 161 | } catch (Exception e) { | 168 | } catch (Exception e) { |
| 162 | log.error("[closeAll][关闭连接失败]", e); | 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,6 +11,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotMod | ||
| 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo; | 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo; |
| 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlavePendingRequestManager.PendingRequest; | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlavePendingRequestManager.PendingRequest; |
| 13 | import io.vertx.core.Vertx; | 13 | import io.vertx.core.Vertx; |
| 14 | +import lombok.Getter; | ||
| 14 | import lombok.extern.slf4j.Slf4j; | 15 | import lombok.extern.slf4j.Slf4j; |
| 15 | 16 | ||
| 16 | import java.util.concurrent.atomic.AtomicInteger; | 17 | import java.util.concurrent.atomic.AtomicInteger; |
| @@ -31,6 +32,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | @@ -31,6 +32,7 @@ public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollSchedul | ||
| 31 | /** | 32 | /** |
| 32 | * TCP 事务 ID 自增器(与 DownstreamHandler 共享) | 33 | * TCP 事务 ID 自增器(与 DownstreamHandler 共享) |
| 33 | */ | 34 | */ |
| 35 | + @Getter | ||
| 34 | private final AtomicInteger transactionIdCounter; | 36 | private final AtomicInteger transactionIdCounter; |
| 35 | 37 | ||
| 36 | public IotModbusTcpSlavePollScheduler(Vertx vertx, | 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,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 | private final IotDeviceMessageService deviceMessageService; | 86 | private final IotDeviceMessageService deviceMessageService; |
| 87 | 87 | ||
| @@ -104,11 +104,6 @@ public class IotMqttProtocol implements IotProtocol { | @@ -104,11 +104,6 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 104 | this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId); | 104 | this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId); |
| 105 | this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService); | 105 | this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService); |
| 106 | this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId); | 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 | @Override | 109 | @Override |
| @@ -157,18 +152,13 @@ public class IotMqttProtocol implements IotProtocol { | @@ -157,18 +152,13 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 157 | getId(), properties.getPort(), serverId); | 152 | getId(), properties.getPort(), serverId); |
| 158 | 153 | ||
| 159 | // 2. 启动下行消息订阅者 | 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 | this.downstreamSubscriber.start(); | 158 | this.downstreamSubscriber.start(); |
| 161 | } catch (Exception e) { | 159 | } catch (Exception e) { |
| 162 | log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 162 | throw e; |
| 173 | } | 163 | } |
| 174 | } | 164 | } |
| @@ -178,15 +168,24 @@ public class IotMqttProtocol implements IotProtocol { | @@ -178,15 +168,24 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 178 | if (!running) { | 168 | if (!running) { |
| 179 | return; | 169 | return; |
| 180 | } | 170 | } |
| 171 | + stop0(); | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + private void stop0() { | ||
| 181 | // 1. 停止下行消息订阅者 | 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 | if (mqttServer != null) { | 189 | if (mqttServer != null) { |
| 191 | try { | 190 | try { |
| 192 | mqttServer.close().result(); | 191 | mqttServer.close().result(); |
| @@ -196,7 +195,7 @@ public class IotMqttProtocol implements IotProtocol { | @@ -196,7 +195,7 @@ public class IotMqttProtocol implements IotProtocol { | ||
| 196 | } | 195 | } |
| 197 | mqttServer = null; | 196 | mqttServer = null; |
| 198 | } | 197 | } |
| 199 | - // 2.2 关闭 Vertx 实例 | 198 | + // 2.3 关闭 Vertx 实例 |
| 200 | if (vertx != null) { | 199 | if (vertx != null) { |
| 201 | try { | 200 | try { |
| 202 | vertx.close().result(); | 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,6 +8,8 @@ import lombok.Data; | ||
| 8 | import lombok.extern.slf4j.Slf4j; | 8 | import lombok.extern.slf4j.Slf4j; |
| 9 | import org.springframework.stereotype.Component; | 9 | import org.springframework.stereotype.Component; |
| 10 | 10 | ||
| 11 | +import java.util.ArrayList; | ||
| 12 | +import java.util.List; | ||
| 11 | import java.util.Map; | 13 | import java.util.Map; |
| 12 | import java.util.concurrent.ConcurrentHashMap; | 14 | import java.util.concurrent.ConcurrentHashMap; |
| 13 | 15 | ||
| @@ -167,6 +169,24 @@ public class IotMqttConnectionManager { | @@ -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 | @Data | 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,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,11 +94,6 @@ public class IotTcpProtocol implements IotProtocol { | ||
| 94 | 94 | ||
| 95 | // 初始化连接管理器 | 95 | // 初始化连接管理器 |
| 96 | this.connectionManager = new IotTcpConnectionManager(tcpConfig.getMaxConnections()); | 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 | @Override | 99 | @Override |
| @@ -152,18 +147,13 @@ public class IotTcpProtocol implements IotProtocol { | @@ -152,18 +147,13 @@ public class IotTcpProtocol implements IotProtocol { | ||
| 152 | getId(), properties.getPort(), serverId); | 147 | getId(), properties.getPort(), serverId); |
| 153 | 148 | ||
| 154 | // 2. 启动下行消息订阅者 | 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 | this.downstreamSubscriber.start(); | 153 | this.downstreamSubscriber.start(); |
| 156 | } catch (Exception e) { | 154 | } catch (Exception e) { |
| 157 | log.error("[start][IoT TCP 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 157 | throw e; |
| 168 | } | 158 | } |
| 169 | } | 159 | } |
| @@ -173,15 +163,24 @@ public class IotTcpProtocol implements IotProtocol { | @@ -173,15 +163,24 @@ public class IotTcpProtocol implements IotProtocol { | ||
| 173 | if (!running) { | 163 | if (!running) { |
| 174 | return; | 164 | return; |
| 175 | } | 165 | } |
| 166 | + stop0(); | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + private void stop0() { | ||
| 176 | // 1. 停止下行消息订阅者 | 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 | if (tcpServer != null) { | 184 | if (tcpServer != null) { |
| 186 | try { | 185 | try { |
| 187 | tcpServer.close().result(); | 186 | tcpServer.close().result(); |
| @@ -191,7 +190,7 @@ public class IotTcpProtocol implements IotProtocol { | @@ -191,7 +190,7 @@ public class IotTcpProtocol implements IotProtocol { | ||
| 191 | } | 190 | } |
| 192 | tcpServer = null; | 191 | tcpServer = null; |
| 193 | } | 192 | } |
| 194 | - // 2.2 关闭 Vertx 实例 | 193 | + // 2.3 关闭 Vertx 实例 |
| 195 | if (vertx != null) { | 194 | if (vertx != null) { |
| 196 | try { | 195 | try { |
| 197 | vertx.close().result(); | 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,6 +5,8 @@ import io.vertx.core.net.NetSocket; | ||
| 5 | import lombok.Data; | 5 | import lombok.Data; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | 7 | ||
| 8 | +import java.util.ArrayList; | ||
| 9 | +import java.util.List; | ||
| 8 | import java.util.Map; | 10 | import java.util.Map; |
| 9 | import java.util.concurrent.ConcurrentHashMap; | 11 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | 12 | ||
| @@ -123,6 +125,24 @@ public class IotTcpConnectionManager { | @@ -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 | @Data | 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,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,10 +85,6 @@ public class IotUdpProtocol implements IotProtocol { | ||
| 85 | // 初始化会话管理器 | 85 | // 初始化会话管理器 |
| 86 | this.sessionManager = new IotUdpSessionManager(udpConfig.getMaxSessions(), udpConfig.getSessionTimeoutMs()); | 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 | @Override | 90 | @Override |
| @@ -108,8 +104,11 @@ public class IotUdpProtocol implements IotProtocol { | @@ -108,8 +104,11 @@ public class IotUdpProtocol implements IotProtocol { | ||
| 108 | return; | 104 | return; |
| 109 | } | 105 | } |
| 110 | 106 | ||
| 111 | - // 1.1 创建 Vertx 实例 | 107 | + // 1.1 创建 Vertx 实例 和 下行消息订阅者 |
| 112 | this.vertx = Vertx.vertx(); | 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 | // 1.2 创建 UDP Socket 选项 | 113 | // 1.2 创建 UDP Socket 选项 |
| 115 | IotUdpConfig udpConfig = properties.getUdp(); | 114 | IotUdpConfig udpConfig = properties.getUdp(); |
| @@ -137,15 +136,7 @@ public class IotUdpProtocol implements IotProtocol { | @@ -137,15 +136,7 @@ public class IotUdpProtocol implements IotProtocol { | ||
| 137 | this.downstreamSubscriber.start(); | 136 | this.downstreamSubscriber.start(); |
| 138 | } catch (Exception e) { | 137 | } catch (Exception e) { |
| 139 | log.error("[start][IoT UDP 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 140 | throw e; |
| 150 | } | 141 | } |
| 151 | } | 142 | } |
| @@ -155,12 +146,19 @@ public class IotUdpProtocol implements IotProtocol { | @@ -155,12 +146,19 @@ public class IotUdpProtocol implements IotProtocol { | ||
| 155 | if (!running) { | 146 | if (!running) { |
| 156 | return; | 147 | return; |
| 157 | } | 148 | } |
| 149 | + stop0(); | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + private void stop0() { | ||
| 158 | // 1. 停止下行消息订阅者 | 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 | // 2.1 关闭 UDP Socket | 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 | package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.websocket; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.lang.Assert; | ||
| 3 | import cn.hutool.core.util.ObjUtil; | 4 | import cn.hutool.core.util.ObjUtil; |
| 4 | import cn.hutool.extra.spring.SpringUtil; | 5 | import cn.hutool.extra.spring.SpringUtil; |
| 5 | import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; |
| @@ -9,8 +10,8 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | @@ -9,8 +10,8 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | ||
| 9 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; | 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties; | 11 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties; |
| 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; |
| 12 | -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamSubscriber; | ||
| 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamHandler; | 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 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream.IotWebSocketUpstreamHandler; | 15 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream.IotWebSocketUpstreamHandler; |
| 15 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager; | 16 | import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager; |
| 16 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | 17 | import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; |
| @@ -21,7 +22,6 @@ import io.vertx.core.http.HttpServerOptions; | @@ -21,7 +22,6 @@ import io.vertx.core.http.HttpServerOptions; | ||
| 21 | import io.vertx.core.net.PemKeyCertOptions; | 22 | import io.vertx.core.net.PemKeyCertOptions; |
| 22 | import lombok.Getter; | 23 | import lombok.Getter; |
| 23 | import lombok.extern.slf4j.Slf4j; | 24 | import lombok.extern.slf4j.Slf4j; |
| 24 | -import cn.hutool.core.lang.Assert; | ||
| 25 | 25 | ||
| 26 | /** | 26 | /** |
| 27 | * IoT WebSocket 协议实现 | 27 | * IoT WebSocket 协议实现 |
| @@ -65,7 +65,7 @@ public class IotWebSocketProtocol implements IotProtocol { | @@ -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,10 +87,6 @@ public class IotWebSocketProtocol implements IotProtocol { | ||
| 87 | // 初始化连接管理器 | 87 | // 初始化连接管理器 |
| 88 | this.connectionManager = new IotWebSocketConnectionManager(); | 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 | @Override | 92 | @Override |
| @@ -152,17 +148,13 @@ public class IotWebSocketProtocol implements IotProtocol { | @@ -152,17 +148,13 @@ public class IotWebSocketProtocol implements IotProtocol { | ||
| 152 | getId(), properties.getPort(), wsConfig.getPath(), serverId); | 148 | getId(), properties.getPort(), wsConfig.getPath(), serverId); |
| 153 | 149 | ||
| 154 | // 2. 启动下行消息订阅者 | 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 | downstreamSubscriber.start(); | 154 | downstreamSubscriber.start(); |
| 156 | } catch (Exception e) { | 155 | } catch (Exception e) { |
| 157 | log.error("[start][IoT WebSocket 协议 {} 启动失败]", getId(), e); | 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 | throw e; | 158 | throw e; |
| 167 | } | 159 | } |
| 168 | } | 160 | } |
| @@ -172,15 +164,24 @@ public class IotWebSocketProtocol implements IotProtocol { | @@ -172,15 +164,24 @@ public class IotWebSocketProtocol implements IotProtocol { | ||
| 172 | if (!running) { | 164 | if (!running) { |
| 173 | return; | 165 | return; |
| 174 | } | 166 | } |
| 167 | + stop0(); | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + private void stop0() { | ||
| 175 | // 1. 停止下行消息订阅者 | 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 | if (httpServer != null) { | 185 | if (httpServer != null) { |
| 185 | try { | 186 | try { |
| 186 | httpServer.close().result(); | 187 | httpServer.close().result(); |
| @@ -190,7 +191,7 @@ public class IotWebSocketProtocol implements IotProtocol { | @@ -190,7 +191,7 @@ public class IotWebSocketProtocol implements IotProtocol { | ||
| 190 | } | 191 | } |
| 191 | httpServer = null; | 192 | httpServer = null; |
| 192 | } | 193 | } |
| 193 | - // 2.2 关闭 Vertx 实例 | 194 | + // 2.3 关闭 Vertx 实例 |
| 194 | if (vertx != null) { | 195 | if (vertx != null) { |
| 195 | try { | 196 | try { |
| 196 | vertx.close().result(); | 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,6 +5,8 @@ import lombok.Data; | ||
| 5 | import lombok.experimental.Accessors; | 5 | import lombok.experimental.Accessors; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 7 | 7 | ||
| 8 | +import java.util.ArrayList; | ||
| 9 | +import java.util.List; | ||
| 8 | import java.util.Map; | 10 | import java.util.Map; |
| 9 | import java.util.concurrent.ConcurrentHashMap; | 11 | import java.util.concurrent.ConcurrentHashMap; |
| 10 | 12 | ||
| @@ -115,6 +117,24 @@ public class IotWebSocketConnectionManager { | @@ -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 | @Data | 140 | @Data |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
| @@ -167,7 +167,7 @@ yudao: | @@ -167,7 +167,7 @@ yudao: | ||
| 167 | # 针对引入的 Modbus TCP Master 组件的配置 | 167 | # 针对引入的 Modbus TCP Master 组件的配置 |
| 168 | # ==================================== | 168 | # ==================================== |
| 169 | - id: modbus-tcp-master-1 | 169 | - id: modbus-tcp-master-1 |
| 170 | - enabled: true | 170 | + enabled: false |
| 171 | protocol: modbus_tcp_master | 171 | protocol: modbus_tcp_master |
| 172 | port: 502 | 172 | port: 502 |
| 173 | modbus-tcp-master: | 173 | modbus-tcp-master: |
| @@ -176,7 +176,7 @@ yudao: | @@ -176,7 +176,7 @@ yudao: | ||
| 176 | # 针对引入的 Modbus TCP Slave 组件的配置 | 176 | # 针对引入的 Modbus TCP Slave 组件的配置 |
| 177 | # ==================================== | 177 | # ==================================== |
| 178 | - id: modbus-tcp-slave-1 | 178 | - id: modbus-tcp-slave-1 |
| 179 | - enabled: true | 179 | + enabled: false |
| 180 | protocol: modbus_tcp_slave | 180 | protocol: modbus_tcp_slave |
| 181 | port: 503 | 181 | port: 503 |
| 182 | modbus-tcp-slave: | 182 | modbus-tcp-slave: |