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,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: