> 技术文档 > ModBus通讯-封装

ModBus通讯-封装


Modbus 协议分类与详解

Modbus 是工业自动化领域最常用的应用层通信协议,由施耐德电气于 1979 年推出,旨在实现控制器与现场设备(如传感器、执行器、PLC)之间的标准化数据交互。其核心优势是结构简单、开源免费、兼容性强,已成为工业通信的 “通用语言”。根据传输层介质和实现方式的不同,Modbus 主要分为以下几类,各类别在物理层、传输方式、应用场景上存在显著差异。

一、按传输介质与底层协议分类(核心分类)

这是 Modbus 最主流的分类方式,根据数据传输的 “物理载体” 和 “底层通信协议”,可分为 Modbus RTU、Modbus ASCII、Modbus TCP、Modbus UDP 四大类,其中前两者基于串行通信,后两者基于以太网。

1. Modbus RTU(Remote Terminal Unit)

核心特点
  • 底层协议:基于串行通信(RS-232/RS-485/RS-422),是工业现场最常用的 Modbus 类型。
  • 数据格式:采用二进制编码(16 进制),数据紧凑、传输效率高(无冗余字符)。
  • 帧结构:每帧数据包含 “地址码→功能码→数据段→CRC 校验码”,帧长度不固定(取决于数据长度)。

    plaintext

    ┌──────────┬──────────┬──────────┬──────────┐│ 地址码 │ 功能码 │ 数据段 │ CRC校验 ││ 1字节 │ 1字节 │ N字节 │ 2字节 │└──────────┴──────────┴──────────┴──────────┘
  • 校验方式:CRC-16 循环冗余校验(强校验,适合工业复杂电磁环境)。
  • 传输速率:波特率可配置(常见 9600/19200/38400/115200 bps),受串行通信限制,传输距离取决于物理层(RS-485 最大 1200 米)。
优势与局限
  • ✅ 优势:传输效率高(二进制无冗余)、抗干扰能力强(CRC 校验)、硬件成本低(仅需 RS-485 芯片)。
  • ❌ 局限:仅支持串行总线拓扑(1 对多,最多 32 个从站)、不支持跨网段通信、需手动配置串口参数(波特率、数据位等)。
典型应用
  • 工业现场近距离设备通信(如 PLC 与传感器、变频器的连接);
  • 嵌入式设备(如 STM32、Arduino)的低成本数据交互。

2. Modbus ASCII

核心特点
  • 底层协议:同样基于串行通信(RS-232/RS-485),是 Modbus RTU 的 “人类可读版本”。
  • 数据格式:采用ASCII 编码(文本字符),每个字节的二进制数据转换为 2 个 ASCII 字符(如二进制 0x12 转为 \"12\")。
  • 帧结构:每帧以起始符 :(冒号,ASCII 0x3A)开头,以结束符 \\r\\n(回车 + 换行)结尾,包含 “地址码→功能码→数据段→LRC 校验码”。

    plaintext

    ┌──────┬──────────┬──────────┬──────────┬───────┐│ 起始符: │ 地址码 │ 功能码 │ 数据段 │ LRC校验 │ 结束符\\r\\n ││ 1字节 │ 2字符 │ 2字符 │ 2N字符 │ 2字符 │ 2字节 │└──────┴──────────┴──────────┴──────────┴───────┘
  • 校验方式:LRC(纵向冗余校验),计算简单但校验强度弱于 CRC-16。
  • 传输速率:因每个字节需 2 个 ASCII 字符,传输效率仅为 Modbus RTU 的 50%,波特率支持范围与 RTU 一致。
优势与局限
  • ✅ 优势:数据可直接通过串口助手读取(如 \"12 03 00 01 00 02\"),便于调试;校验计算简单(适合资源受限的低端设备)。
  • ❌ 局限:传输效率低(冗余字符多)、抗干扰能力弱(LRC 校验)、仅适用于调试或低速场景。
典型应用
  • 早期工业设备的调试与诊断(如手动发送 ASCII 指令测试设备响应);
  • 对传输效率无要求、需人工干预的简单场景。

3. Modbus TCP(Transmission Control Protocol)

核心特点
  • 底层协议:基于以太网(TCP/IP 协议栈),是 Modbus 协议在局域网 / 互联网的扩展,也是当前工业以太网的主流选择。
  • 数据格式:保留 Modbus RTU 的二进制数据结构,但在传输层封装为 TCP 数据包,增加 “MBAP 报文头”(Modbus Application Protocol Header)。
  • 帧结构:TCP 数据包 = MBAP 头(7 字节) + Modbus RTU 帧(地址码 + 功能码 + 数据 + CRC),其中 CRC 校验可省略(TCP 协议自带校验)。

    plaintext

    ┌────────────────────────────────┬───────────────────────────────┐│ MBAP 报文头(7字节)  │ Modbus RTU 帧(N字节) ││ 事务ID(2)+协议ID(2)+长度(2)+从站地址(1) │ 功能码(1)+数据段(N-2) │└────────────────────────────────┴───────────────────────────────┘
  • 通信方式:采用 “客户端 - 服务器” 模式(主站 = 客户端,从站 = 服务器),主站通过 IP 地址 + 端口(默认 502)访问从站,支持跨网段、广域网通信。
  • 传输速率:取决于以太网带宽(100Mbps/1Gbps),无距离限制(可通过路由器、交换机扩展)。
优势与局限
  • ✅ 优势:传输速率高(以太网带宽)、支持跨网段 / 广域网、拓扑灵活(星型、树型)、可同时连接大量从站(理论无上限,取决于网络设备)。
  • ❌ 局限:硬件成本高(需以太网芯片 / 模块)、依赖 TCP/IP 协议栈(嵌入式设备需额外适配)、在强电磁干扰环境下需做好网络防护。
典型应用
  • 工业以太网控制系统(如车间级 PLC 与上位机的通信);
  • 远程监控系统(如通过互联网访问异地设备数据);
  • 智慧城市、智能家居中的设备联网(如智能电表、网关)。

4. Modbus UDP(User Datagram Protocol)

核心特点
  • 底层协议:基于以太网(UDP/IP 协议栈),是 Modbus TCP 的 “无连接版本”。
  • 数据格式:与 Modbus TCP 类似,同样包含 MBAP 报文头,但基于 UDP 数据包传输(无 TCP 的三次握手、重传机制)。
  • 通信方式:无连接模式,主站发送请求后不等待确认,从站收到请求后直接回复;支持广播 / 组播(可同时向多个从站发送指令)。
  • 传输速率:因无 TCP 的连接开销,理论传输效率高于 TCP,但可靠性依赖应用层处理(需手动实现重传、超时机制)。
优势与局限
  • ✅ 优势:低延迟(无连接开销)、支持广播 / 组播(适合批量控制)、资源占用少(嵌入式设备适配更简单)。
  • ❌ 局限:不可靠(UDP 数据包可能丢失、乱序)、需应用层实现可靠性机制(如超时重传)、适用场景有限。
典型应用
  • 对实时性要求高、可接受少量数据丢失的场景(如实时监控、批量状态查询);
  • 物联网(IoT)中的低功耗设备(如传感器周期性上报数据)。

二、按功能角色分类

根据设备在 Modbus 通信中的角色,可分为 Modbus 主站(Master) 和 Modbus 从站(Slave),二者职责明确,遵循 “主从问答” 机制。

1. Modbus 主站(Master)

  • 核心职责:主动发起通信请求(如读取从站数据、向从站写入指令),控制通信时序;
  • 特点:一个 Modbus 网络中通常只有 1 个主站(避免冲突),可同时与多个从站通信;
  • 典型设备:上位机(如电脑、触摸屏)、PLC(作为主站控制其他从站设备)、工业网关。

2. Modbus 从站(Slave)

  • 核心职责:被动响应主站请求(如返回数据、执行写入指令),不能主动发起通信;
  • 特点:每个从站有唯一的 “从站地址”(1~247,0 为广播地址),主站通过地址区分不同从站;
  • 典型设备:传感器、变频器、执行器、智能仪表(如流量计、温控器)。

三、按数据存储类型分类

Modbus 协议定义了不同类型的 “寄存器 / 线圈” 用于存储设备数据,主站通过 “功能码” 指定操作的数据类型,常见分类如下:

数据类型 功能码范围 读写权限 存储单位 典型用途 离散输入(Discrete Inputs) 0x01~0x02 只读 1 位(bit) 检测开关状态(如传感器是否触发) 线圈(Coils) 0x05~0x0F 读写 1 位(bit) 控制设备开关(如继电器、LED) 输入寄存器(Input Registers) 0x04 只读 16 位(word) 存储模拟量输入(如温度、电压) 保持寄存器(Holding Registers) 0x03~0x06 读写 16 位(word) 存储设备参数(如变频器频率、设定值)
  • 示例:主站发送功能码 0x03,表示 “读取从站的保持寄存器数据”;发送功能码 0x06,表示 “向从站的单个保持寄存器写入数据”。

四、其他衍生分类

1. Modbus Plus(MB+)

  • 施耐德推出的专有 Modbus 扩展,基于令牌环拓扑的串行总线,支持更高的传输速率(1Mbps)和更多从站(最多 64 个);
  • 主要用于施耐德 PLC 之间的高速通信,兼容性差(仅支持施耐德设备),目前已逐步被 Modbus TCP 替代。

2. Modbus RTU Over TCP

  • 将 Modbus RTU 帧直接封装在 TCP 数据包中(不添加 MBAP 头),本质是 “通过 TCP 传输 RTU 数据”;
  • 适用于需兼容旧 RTU 设备,但需通过以太网扩展传输距离的场景,需主从站均支持该模式。

3. Modbus Security

  • 针对 Modbus TCP 的安全扩展,添加 TLS/SSL 加密、身份认证机制,解决传统 Modbus 无安全防护的问题;
  • 适用于工业互联网场景(如跨企业设备通信),防止数据被窃听或篡改。

五、各类 Modbus 协议对比总结

分类 底层协议 数据格式 校验方式 传输速率 拓扑结构 典型应用场景 Modbus RTU RS-232/485 二进制 CRC-16 9600~115200bps 总线型(1 对多) 现场近距离设备通信 Modbus ASCII RS-232/485 ASCII LRC 同 RTU(效率低 50%) 总线型(1 对多) 调试、低速简单场景 Modbus TCP TCP/IP 二进制 + MBAP TCP 校验 100Mbps~1Gbps 星型 / 树型 工业以太网、远程监控 Modbus UDP UDP/IP 二进制 + MBAP 无(需应用层) 同 TCP(低延迟) 星型 / 组播 实时监控、批量数据上报

六、选择建议

  1. 现场近距离、低成本场景:优先选择 Modbus RTU(效率高、抗干扰强);
  2. 调试或人工干预场景:选择 Modbus ASCII(数据可读,便于排查问题);
  3. 跨网段、高速通信场景:选择 Modbus TCP(兼容性强、支持广域网);
  4. 实时性要求高、批量控制场景:选择 Modbus UDP(低延迟、支持广播);
  5. 安全敏感场景:选择 Modbus Security(加密传输、身份认证)。

通过理解 Modbus 协议的分类与差异,可根据实际项目的 “传输距离、速率、成本、拓扑” 需求,选择最适配的通信方案。

TouchSocket.3.0.25

public interface IModbusMaster{void OpenConnection();void CloseConnection(); ///  /// 读取保持寄存器 ///  /// 从设备地址 /// 起始地址 /// 读取数据 /// 读取数量 void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count); ///  /// 写入单个保持寄存器 ///  /// 从设备地址 /// 寄存器地址 /// 寄存器值 void WriteSingleRegister(int slaveAddress, int registerAddress, short data); ///  /// 写入多个保持寄存器 ///  /// 从设备地址 /// 起始地址 /// 寄存器值数组 /// 写入数量 void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count); #region 暂时不用的接口接口 void ReadDigitalInputs(int slaveAddress, int startAddress, bool[] data, int numofInputs); void ReadDigitalOutputs(int slaveAddress, int startAddress, bool[] data, int numofOutputs); void WriteDigitalOutput(int slaveAddress, int startAddress, bool data); void ReadFloatInputs(int slaveAddress, int startAddress, float[] data, int numofInputs);void ReadFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofOutputs);void WriteFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofInputs); #endregion}/// /// Modbus Tcp Master Communication/// public class ModbusTcpMasterComm : LifecycleBase, Skyverse.Cerasus.Kernel.Hardware.DeviceComm.Interface.IModbusMaster{ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private string _ipAddress; private int _port; private Measure _timeout; private int retries = 3; private ModbusTcpMaster _modbusTcpMaster; ///  /// Initializes a new instance of the  class. ///  ///  The name of the component.  ///  The key of the component.  public ModbusTcpMasterComm(string name, ComponentKey key) : base(name, key) { loggerName = $\"Hardware#{name}\"; } public void OpenConnection() {  try { if (_modbusTcpMaster == null) { _modbusTcpMaster = new ModbusTcpMaster(); _modbusTcpMaster.Setup(new TouchSocketConfig()  .SetRemoteIPHost(new IPHost($\"{_ipAddress}:{_port}\"))  .ConfigureContainer(a =>  { a.AddEasyLogger(new Action(str => { LogTrace($\"Touchsocket info:{str}\"); }));  })); } if (_modbusTcpMaster.Online) { LogInfo(\"Modbus Connection to controller is already Established\"); } else { Result result = _modbusTcpMaster.TryConnect(); if (result.IsSuccess) {  LogInfo($\"Modbus Connection to controller Established Successfully.IpAddress:{_ipAddress},Timeout:{_timeout},Retries:{retries}\", InfoLevel.Important); } else {  throw new Exception($\"Modbus Connection to controller failed:{result.Message}\"); } } } catch (Exception ex) { LogError(\"Attempt to establish Modbus Connection failed\", ex); throw; } } public void CloseConnection() { if (_modbusTcpMaster.Online) { _modbusTcpMaster.SafeClose(); LogInfo(\"ModbusTcpMaster closed\", InfoLevel.Important); } _modbusTcpMaster?.Dispose(); LogInfo(\"ModbusTcpMaster disposed\", InfoLevel.Important); } public void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count) { ThrowIfNotInitialized(); if (data == null) { throw new ArgumentNullException(nameof(data), \"Data array cannot be null\"); } if (data.Length < count) { throw new ArgumentException(\"Data array length is insufficient for requested count\", nameof(data)); } _semaphore.Wait(); try { ReconnectIfOffline(); //var response = _modbusTcpMaster.ReadHoldingRegisters((ushort)startAddress, (ushort)count); ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadHoldingRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.Quantity = (ushort)count; var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode == ModbusErrorCode.Success) { var reader = response.CreateReader(); var values = reader.ToInt16s(EndianType.Big); Array.Copy(values.ToArray(), data, count); } else { throw new Exception($\"ReadHoldingRegisters failed: {response.ErrorCode.GetDescription()}\"); } } catch (Exception exception) { LogError($\"Encountered error while reading holding registers from slave:{slaveAddress} at address:{startAddress}\", exception); throw; } finally { _semaphore.Release(); } } public void WriteSingleRegister(int slaveAddress, int registerAddress, short data) { ThrowIfNotInitialized(); _semaphore.Wait(); try { ReconnectIfOffline(); ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteSingleRegister); modbusRequest.StartingAddress = (ushort)registerAddress; modbusRequest.SetValue(TouchSocketBitConverter.BigEndian.GetBytes(data)); var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"WriteSingleRegister failed: {response.ErrorCode.GetDescription()}\"); }} catch (Exception exception) { LogError($\"Encountered error while writing sinagle register to slave:{slaveAddress} at address:{registerAddress}.{exception.Message}\"); throw; } finally { _semaphore.Release(); } } public void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count) { ThrowIfNotInitialized(); if (data == null) { throw new ArgumentNullException(nameof(data), \"Data array cannot be null\"); } if (data.Length < count) { throw new ArgumentException(\"Data array length is insufficient for requested count\", nameof(data)); }_semaphore.Wait(); try { ReconnectIfOffline(); byte[] bytes = new byte[count * 2]; // 每个short占2字节 for (int i = 0; i < count; i++) { TouchSocketBitConverter.BigEndian.GetBytes(data[i]).CopyTo(bytes, i * 2); } ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteMultipleRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.SetValue(bytes.ToArray()); var response = _modbusTcpMaster.SendModbusRequest(modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"WriteMultipleRegisters failed: {response.ErrorCode.GetDescription()}\"); } } catch (Exception exception) { LogError($\"Encountered Error while writing sinagle register to slave:{slaveAddress} at address:{startAddress}.{exception.Message}\"); throw; } finally { _semaphore.Release(); } } #region NotImplemented public void ReadDigitalInputs(int slaveAddress, int startAddress, bool[] data, int numofInputs) { throw new NotImplementedException(); } public void ReadDigitalOutputs(int slaveAddress, int startAddress, bool[] data, int numofOutputs) { throw new NotImplementedException(); } public void WriteDigitalOutput(int slaveAddress, int startAddress, bool data) { throw new NotImplementedException(); } public void ReadFloatInputs(int slaveAddress, int startAddress, float[] data, int numofInputs) { throw new NotImplementedException(); } public void ReadFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofOutputs) { throw new NotImplementedException(); } public void WriteFloatOutputs(int slaveAddress, int startAddress, float[] data, int numofInputs) { throw new NotImplementedException(); } #endregion ///  /// Builds the subcomponents of the component. ///  protected override void buildSubcomponents() { TraceEnter(); IComponentService instance = ComponentService.Instance; try { TCPClientConfig tcpConfig = instance.GetComponent(\"SystemConfigService\").Load(GetComponentName()); _ipAddress = tcpConfig.IPAddress; _timeout = Util.StringToTimeout(tcpConfig.Timeout, 5000, GetComponentName()).ConvertTo(Unit.Millisecond); _port = tcpConfig.Port; LogInfo($\"TCP Configuration loaded successfully,ipAddress={_ipAddress} port ={_port}\", InfoLevel.Important); } catch (Exception exception) { LogError(\"There was a problem loading the configuration for  from the database. Database key = \" + GetComponentName(), exception); throw; } finally { TraceExit(); } } ///  /// Initializes the component. ///  protected override void initializeThis() { TraceEnter(); try { OpenConnection(); } catch (Exception ex) { LogError(\"Exception occured in initializeThis()\", ex); throw; } finally { TraceExit(); } } ///  /// Uninitializes the component. ///  protected override void uninitializeThis() { TraceEnter(); try { CloseConnection(); } catch (Exception ex) { LogError(\"Exception occured in uninitializeThis()\", ex); throw; } finally { TraceExit(); } } private void ReconnectIfOffline() { if (!_modbusTcpMaster.Online) { Result result = _modbusTcpMaster.TryConnect(); if (result.ResultCode != ResultCode.Success) { throw new Exception($\"Reconnect failed:{result.Message}\"); } else { LogInfo(\"Modbus Connection Reestablished Successfully\", InfoLevel.Important); } } }}[ConfigurationDescription(\"This is a Description for TCPClientConfig\")]public class TCPClientConfig{public string IPAddress { get; set; }public int Port { get; set; }public string Timeout { get; set; }public bool ReconnectOnFailures { get; set; }}

ModbusTcpMasterComm 类解析:Modbus TCP 主站通信组件

一、类的核心定位与功能

ModbusTcpMasterComm 是一个基于 Modbus TCP 协议的通信组件,实现了 IModbusMaster 接口,作为 Modbus 主站(Master)与从站(Slave)设备进行数据交互。该类继承自 LifecycleBase,遵循组件化生命周期管理,主要用于工业自动化场景中与支持 Modbus TCP 协议的设备(如 PLC、传感器、执行器等)进行通信。

核心功能包括:

  • 建立和关闭与 Modbus 从站的 TCP 连接
  • 读取保持寄存器(Holding Registers)
  • 写入单个寄存器和多个寄存器
  • 连接状态监测与自动重连
  • 线程安全的数据操作

二、技术依赖与关键库

该组件主要依赖以下技术和库:

  1. Modbus 协议:基于 Modbus TCP 规范,使用标准功能码进行数据交互
  2. TouchSocket:第三方网络通信库,提供 Modbus TCP 协议的实现(ModbusTcpMaster 类)
  3. Skyverse 框架:提供生命周期管理、配置服务、日志服务等基础能力
  4. System.Net.Sockets:基础网络通信支持

三、核心字段与属性解析

字段 / 属性 类型 作用 关键细节 _semaphore SemaphoreSlim 信号量 确保多线程环境下通信操作的线程安全,初始化为 1 个并发量 _ipAddress string 从站 IP 地址 从配置加载,用于建立 TCP 连接 _port int 端口号 Modbus TCP 默认端口为 502,从配置加载 _timeout Measure 超时时间 包含值和单位的度量对象,用于通信超时控制 retries int 重试次数 默认为 3 次,用于失败重试机制 _modbusTcpMaster ModbusTcpMaster Modbus TCP 主站实例 TouchSocket 库提供的核心通信对象

四、核心方法解析

1. 生命周期相关方法

(1)buildSubcomponents():构建子组件,加载配置

csharp

protected override void buildSubcomponents(){ // 从配置服务加载TCP客户端配置 TCPClientConfig tcpConfig = instance.GetComponent(\"SystemConfigService\") .Load(GetComponentName()); // 解析配置参数 _ipAddress = tcpConfig.IPAddress; _timeout = Util.StringToTimeout(tcpConfig.Timeout, 5000, GetComponentName()) .ConvertTo(Unit.Millisecond); _port = tcpConfig.Port;}
  • 作用:从系统配置服务加载 Modbus 通信所需的配置参数(IP 地址、端口、超时时间等)
  • 关键:使用框架的配置服务实现配置与代码解耦,便于后期维护和配置变更
(2)initializeThis():初始化组件

csharp

protected override void initializeThis(){ OpenConnection(); // 初始化时建立连接}
(3)uninitializeThis():销毁组件

csharp

protected override void uninitializeThis(){ CloseConnection(); // 销毁时关闭连接}

2. 连接管理方法

(1)OpenConnection():建立连接

csharp

public void OpenConnection(){ if (_modbusTcpMaster == null) { // 初始化Modbus TCP主站并配置连接参数 _modbusTcpMaster = new ModbusTcpMaster(); _modbusTcpMaster.Setup(new TouchSocketConfig() .SetRemoteIPHost(new IPHost($\"{_ipAddress}:{_port}\")) .ConfigureContainer(a => { // 配置日志输出 a.AddEasyLogger(new Action(str => {  LogTrace($\"Touchsocket info:{str}\"); })); })); } if (!_modbusTcpMaster.Online) { // 尝试连接并处理结果 Result result = _modbusTcpMaster.TryConnect(); if (!result.IsSuccess) { throw new Exception($\"Modbus Connection failed:{result.Message}\"); } } }
  • 作用:初始化 ModbusTcpMaster 实例并建立与从站的连接
  • 关键:使用 TouchSocket 的配置模式进行灵活配置,包括远程主机地址和日志输出
(2)CloseConnection():关闭连接

csharp

public void CloseConnection(){ if (_modbusTcpMaster.Online) { _modbusTcpMaster.SafeClose(); // 安全关闭连接 _modbusTcpMaster.Dispose(); // 释放资源 }}
(3)ReconnectIfOffline():离线自动重连

csharp

private void ReconnectIfOffline(){ if (!_modbusTcpMaster.Online) { Result result = _modbusTcpMaster.TryConnect(); if (result.ResultCode != ResultCode.Success) { throw new Exception($\"Reconnect failed:{result.Message}\"); } } }
  • 作用:在执行通信操作前检查连接状态,如已断开则自动重连
  • 关键:提高通信可靠性,自动处理临时网络故障

3. Modbus 数据操作方法

(1)ReadHoldingRegisters():读取保持寄存器

csharp

public void ReadHoldingRegisters(int slaveAddress, int startAddress, short[] data, int count){ ThrowIfNotInitialized(); // 参数校验 if (data == null) throw new ArgumentNullException(nameof(data)); if (data.Length < count) throw new ArgumentException(\"Data array too small\"); _semaphore.Wait(); // 确保线程安全 try { ReconnectIfOffline(); // 检查并确保连接 // 构建读取保持寄存器的请求 ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.ReadHoldingRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.Quantity = (ushort)count; // 发送请求并获取响应 var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode == ModbusErrorCode.Success) { // 解析响应数据 var reader = response.CreateReader(); var values = reader.ToInt16s(EndianType.Big); Array.Copy(values.ToArray(), data, count); } else { throw new Exception($\"Read failed: {response.ErrorCode.GetDescription()}\"); } } finally { _semaphore.Release(); // 释放信号量 }}
  • 作用:读取从站的保持寄存器数据(功能码 0x03)
  • 关键:
    • 使用信号量确保线程安全
    • 发送前检查连接状态
    • 处理响应并转换为合适的数据格式(大端模式)
(2)WriteSingleRegister():写入单个寄存器

csharp

public void WriteSingleRegister(int slaveAddress, int registerAddress, short data){ ThrowIfNotInitialized(); _semaphore.Wait(); try { ReconnectIfOffline(); // 构建写入单个寄存器的请求(功能码0x06) ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteSingleRegister); modbusRequest.StartingAddress = (ushort)registerAddress; modbusRequest.SetValue(TouchSocketBitConverter.BigEndian.GetBytes(data)); var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"Write failed: {response.ErrorCode.GetDescription()}\"); }  } finally { _semaphore.Release(); }}
  • 作用:向从站的指定寄存器写入一个 16 位值(功能码 0x06)
  • 关键:使用大端模式(BigEndian)进行数据编码,符合 Modbus 标准
(3)WriteMultipleRegisters():写入多个寄存器

csharp

public void WriteMultipleRegisters(int slaveAddress, int startAddress, short[] data, int count){ // 参数校验... _semaphore.Wait(); try { ReconnectIfOffline(); // 将short数组转换为字节数组(大端模式) byte[] bytes = new byte[count * 2]; for (int i = 0; i < count; i++) { TouchSocketBitConverter.BigEndian.GetBytes(data[i]).CopyTo(bytes, i * 2); } // 构建写入多个寄存器的请求(功能码0x10) ModbusRequest modbusRequest = new ModbusRequest(FunctionCode.WriteMultipleRegisters); modbusRequest.StartingAddress = (ushort)startAddress; modbusRequest.SetValue(bytes.ToArray()); var response = _modbusTcpMaster.SendModbusRequest( modbusRequest, (int)_timeout.GetScalar(Unit.Millisecond), CancellationToken.None); if (response.ErrorCode != ModbusErrorCode.Success) { throw new Exception($\"Write failed: {response.ErrorCode.GetDescription()}\"); } } finally { _semaphore.Release(); }}
  • 作用:向从站的连续多个寄存器写入数据(功能码 0x10)
  • 关键:正确处理多字节数据的编码和长度计算

五、类的设计亮点

  1. 线程安全设计:使用 SemaphoreSlim 确保在多线程环境下,Modbus 操作的原子性和一致性,避免数据混乱。

  2. 连接可靠性保障

    • 提供自动重连机制(ReconnectIfOffline
    • 操作前检查连接状态
    • 完善的异常处理和日志记录
  3. 配置驱动设计:通过配置服务加载通信参数,实现了代码与配置的分离,便于不同环境下的部署和修改。

  4. 生命周期管理:继承 LifecycleBase 实现了标准化的初始化、销毁流程,符合框架规范,便于集成和管理。

  5. 异常处理:每个方法都有完善的异常捕获和日志记录,同时将异常向上抛出,由调用者决定具体处理策略。

六、未实现的方法

类中标记了 #region NotImplemented 的方法尚未实现,包括:

  • 读取数字输入(ReadDigitalInputs
  • 读取数字输出(ReadDigitalOutputs
  • 写入数字输出(WriteDigitalOutput
  • 读取和写入浮点型数据的相关方法

这些方法对应 Modbus 协议的其他功能码,如读取离散输入(0x02)、读取线圈状态(0x01)、写入单个线圈(0x05)、写入多个线圈(0x0F)等,可以根据实际需求逐步实现。

七、使用场景与注意事项

典型使用场景

  • 工业自动化系统中与 PLC 通信
  • 读取传感器数据(通过 Modbus TCP 接口)
  • 控制执行器(如阀门、电机等)
  • 工业监控系统的数据采集

注意事项

  1. 参数匹配:确保与从站设备的 IP 地址、端口号、超时时间等参数匹配
  2. 数据格式:Modbus 协议通常使用大端模式,需注意数据字节序转换
  3. 线程安全:该类已实现线程安全处理,可在多线程环境下直接使用
  4. 异常处理:调用者需处理可能的通信异常(如网络故障、超时等)
  5. 从站地址:当前实现中未实际使用 slaveAddress 参数,如需支持多从站需进一步完善

该组件为工业环境下的 Modbus TCP 通信提供了可靠的基础实现,可根据具体项目需求在此基础上扩展更多功能。

摘抄