Spring Boot集成Jamod与UPS通信:首次请求成功后续定时请求失败
问题诊断与解决方案
核心问题分析
从报错和代码来看,问题出在连接生命周期管理不当:
- 定时任务每次循环都创建新的
StatusProvider并调用connect(),导致重复创建TCP连接且未正确释放,UPS设备会主动断开闲置/过多的连接,后续请求使用失效连接就会抛出流中断、连接结束类错误。 - 首次请求成功后,后续定时请求复用的连接可能已被UPS端关闭,但代码未检查连接有效性,直接执行事务导致重试失败。
具体修复方案
1. 复用Modbus TCP连接,避免重复创建
将ModbusTCPConnection改为设备级实例,仅在连接失效时重建:
public class JamodStatusProvider implements StatusProvider { private ModbusTCPConnection connection; private final String deviceIp; private final int devicePort; public JamodStatusProvider(String deviceIp, int devicePort) throws UnknownHostException { this.deviceIp = deviceIp; this.devicePort = devicePort; this.connection = new ModbusTCPConnection(InetAddress.getByName(deviceIp), devicePort); // 配置连接参数 connection.setTimeout(10000); // 10秒超时 connection.setKeepAlive(true); // 启用TCP保活,维持长连接 } @Override public void connect() throws Exception { // 仅当连接断开时重建 if (!connection.isConnected()) { // 先关闭旧连接(如果存在) if (connection != null) { connection.close(); } connection = new ModbusTCPConnection(InetAddress.getByName(deviceIp), devicePort); connection.setTimeout(10000); connection.setKeepAlive(true); connection.connect(); } } // 应用关闭时释放连接 public void destroy() { if (connection != null && connection.isConnected()) { connection.close(); } } // 其他方法如getInputVoltage... }
2. 缓存StatusProvider,避免重复实例化
在定时任务类中添加缓存,每个设备对应一个StatusProvider实例,避免每次循环都新建:
private final Map<DeviceEntity, StatusProvider> providerCache = new ConcurrentHashMap<>(); public void logDeviceDataPeriodically() { try { List<DeviceEntity> devices = deviceEntityRepository.findAll(); for (DeviceEntity deviceEntity : devices) { String providerType = deviceEntity.getStatusProviderEntity().getProviderName(); StatusProvider statusProvider = providerCache.computeIfAbsent(deviceEntity, key -> { try { return providerFactory.createProvider(providerType); } catch (Exception e) { e.printStackTrace(); return null; } }); if (statusProvider != null) { try { statusProvider.connect(); // 确保连接有效 // 读取并保存数据逻辑 DeviceData deviceData = new DeviceData(); deviceData.setInputVoltage(statusProvider.getInputVoltage()); deviceData.setOutputVoltage(statusProvider.getOutputVoltage()); deviceData.setLoadPercentage(statusProvider.getLoadPercentage()); deviceData.setTime(statusProvider.getTime()); deviceData.setTimestamp(LocalDateTime.now()); deviceData.setDeviceEntity(deviceEntity); deviceDataRepository.save(deviceData); } catch (Exception e) { // 连接失效,移除缓存,下次循环重建 providerCache.remove(deviceEntity); e.printStackTrace(); } } else { System.err.println("Unknown provider - " + providerType); } } } catch (Exception e) { e.printStackTrace(); } }
3. 增强事务异常处理,失败时重试重建连接
修改readRegisters方法,捕获连接异常后主动重建连接并重试:
public Register[] readRegisters(int startingAddress, int quantity, int unitId) throws Exception { ReadMultipleRegistersRequest request = new ReadMultipleRegistersRequest(startingAddress, quantity); request.setUnitID(unitId); ModbusTCPTransaction transaction = new ModbusTCPTransaction(connection); transaction.setRequest(request); transaction.setRetries(2); // 自定义重试次数 try { transaction.execute(); ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse) transaction.getResponse(); return response.getRegisters(); } catch (ModbusException | IOException e) { // 连接失效,重建后重试一次 connection.close(); connection.connect(); transaction.execute(); ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse) transaction.getResponse(); return response.getRegisters(); } }
4. 额外优化建议
- 检查UPS设备的TCP连接闲置超时设置,如果允许,调大超时时间匹配你的定时周期(60秒)。
- 若UPS不支持长连接,可改为每次请求前新建连接,请求完成后立即关闭(需在finally块中确保关闭):
// 修改readRegisters为每次使用临时连接 public Register[] readRegisters(int startingAddress, int quantity, int unitId) throws Exception { ModbusTCPConnection tempConn = new ModbusTCPConnection(InetAddress.getByName(deviceIp), devicePort); tempConn.setTimeout(10000); try { tempConn.connect(); ReadMultipleRegistersRequest request = new ReadMultipleRegistersRequest(startingAddress, quantity); request.setUnitID(unitId); ModbusTCPTransaction transaction = new ModbusTCPTransaction(tempConn); transaction.setRequest(request); transaction.execute(); ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse) transaction.getResponse(); return response.getRegisters(); } finally { if (tempConn.isConnected()) { tempConn.close(); } } }
内容的提问来源于stack exchange,提问作者Wassabi
相关产品推荐
相关产品推荐

