Commit fcca74ac7d1335b805a07f191cefa43445306eda

Authored by YunaiV
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:
... ...