Skip to content

建议:在 ExecuteRequestAsync 中增加自动重连机制 #1

@Himmelt

Description

@Himmelt

建议:在 ExecuteRequestAsync 中增加自动重连机制

问题描述

ModbusTcpClient 在长期运行的数采任务中,一旦 TCP 连接因网络中断(如网线松动、设备重启)断开,无法自动恢复通信。

分析调用链:

TcpModbusDevice.StartLoopAsync
  └─ Client.ConnectAsync(cancelToken)   // 只在循环开始前连接一次
  └─ while (true)
       └─ Client.ReadHoldingRegisters(...)
            └─ ModbusClientBase.ExecuteRequestAsync
                 ├─ if (!IsConnected)
                 │    └─ throw ModbusConnectionException("客户端未连接")
                 └─ Transport.SendReceiveAsync(...)
                      └─ if (!IsConnected)
                           └─ throw ModbusConnectionException("TCP 连接未建立")

连接断开后的演变:

  1. TcpTransport.SendReceiveAsync 检测到 !IsConnected,抛 "TCP 连接未建立"
  2. ModbusClientBase.ExecuteRequestAsync 捕获后重试(受 Retries 控制),重试耗尽后抛给上层
  3. 上层 catch (Exception ex) 仅记录日志,不触发重连
  4. 从此每次轮询都秒抛异常,网线恢复后也无法恢复通信

涉及文件

  • ModbusClientBase.cs - ExecuteRequestAsync
  • TcpTransport.cs - SendReceiveAsync, IsConnected

改进建议

建议1:ExecuteRequestAsync 中自动重连(核心)

protected async Task<ModbusResponse> ExecuteRequestAsync(ModbusRequest request, CancellationToken cancelToken)
{
    ObjectDisposedException.ThrowIf(_disposed, this);
    if (!IsConnected)
    {
        await ConnectAsync(cancelToken); // 自动重连一次
    }
    // ... 后续逻辑
}

通过可配置属性控制,保持向后兼容:

public bool AutoReconnect { get; set; } = false;

建议2:区分"未连接"异常类型

目前 ModbusConnectionException 在以下场景被抛出,但异常消息是字符串,上层难以做针对性处理:

场景 异常消息 改进建议
ConnectAsync 连接失败 "TCP 连接失败: ..." 维持现状
ExecuteRequestAsync 检测到未连接 "客户端未连接" 建议添加连接状态枚举属性或定义子类
SendReceiveAsync 检测到未连接 "TCP 连接未建立" 同上

建议:

public enum ConnectionState
{
    NeverConnected,    // 从未连接过
    Disconnected,      // 曾连接但已断开
    Connected          // 已连接
}

public class ModbusConnectionException : Exception
{
    public ConnectionState ConnectionState { get; }
}

建议3:配置 KeepAlive 探测间隔

TcpTransport.ConnectAsync 中已启用 KeepAlive:

_tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

但 Windows 默认 keepalive 探测间隔为 2 小时(7200秒),断线检测极其滞后。建议增加可配置的 KeepAlive 参数:

public class NetworkConfig
{
    // 新增选项
    public bool KeepAlive { get; set; } = true;
    public int KeepAliveInterval { get; set; } = 10;  // 探测间隔(秒)
    public int KeepAliveRetries { get; set; } = 5;     // 重试次数
}

对应实现(Windows IOControl):

if (config.KeepAlive && _tcpClient.Client != null)
{
    byte[] keepAlive = new byte[12];
    Buffer.BlockCopy(BitConverter.GetBytes(1), 0, keepAlive, 0, 4);
    Buffer.BlockCopy(BitConverter.GetBytes(config.KeepAliveInterval * 1000), 0, keepAlive, 4, 4);
    Buffer.BlockCopy(BitConverter.GetBytes(config.KeepAliveInterval * 1000), 0, keepAlive, 8, 4);
    _tcpClient.Client.IOControl(IOControlCode.KeepAliveValues, keepAlive, null);
}

影响范围

  • ModbusClientBase(所有客户端类型的基类)
  • TcpTransport(TCP 传输层)
  • 所有使用 IModbusClient 的上层调用方

版本信息

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions