引言
Modbus 协议是工业自动化领域应用最广泛的通信协议之一,广泛应用于 PLC、传感器、仪表等设备之间的数据交换。在 .NET 8 中实现 Modbus 通讯工具类可以大大简化工业控制系统的开发工作。本文将详细介绍如何封装一个功能完整的 Modbus 工具类,支持 RTU 和 TCP 两种传输模式。
1. Modbus 协议基础
Modbus 协议主要有两种传输模式:
- Modbus RTU:基于串行通信(RS232/RS485),使用二进制数据格式
- Modbus TCP:基于以太网通信,使用 TCP/IP 协议栈
1.1 Modbus 功能码
常用功能码:
- 0x01:读线圈状态
- 0x02:读输入状态
- 0x03:读保持寄存器
- 0x04:读输入寄存器
- 0x05:写单个线圈
- 0x06:写单个寄存器
- 0x0F:写多个线圈
- 0x10:写多个寄存器
2. Modbus 工具类设计
2.1 基础接口和枚举
public enum ModbusType
{
RTU,
TCP
}
public enum ModbusFunctionCode : byte
{
ReadCoils = 0x01,
ReadDiscreteInputs = 0x02,
ReadHoldingRegisters = 0x03,
ReadInputRegisters = 0x04,
WriteSingleCoil = 0x05,
WriteSingleRegister = 0x06,
WriteMultipleCoils = 0x0F,
WriteMultipleRegisters = 0x10
}
public interface IModbusClient : IDisposable
{
Task ConnectAsync();
void Disconnect();
bool IsConnected { get; }
// 读取操作
Task ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils);
Task ReadDiscreteInputsAsync(byte slaveId, ushort startAddress, ushort numberOfInputs);
Task ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
Task ReadInputRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters);
// 写入操作
Task WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value);
Task WriteSingleRegisterAsync(byte slaveId, ushort registerAddress, ushort value);
Task WriteMultipleCoilsAsync(byte slaveId, ushort startAddress, bool[] values);
Task WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values);
}
2.2 Modbus 异常处理
public class ModbusException : Exception
{
public byte ExceptionCode { get; }
public ModbusException(byte exceptionCode, string message)
: base(message)
{
ExceptionCode = exceptionCode;
}
}
public static class ModbusErrorCodes
{
public static readonly Dictionary Errors = new Dictionary
{
{ 0x01, "非法功能码" },
{ 0x02, "非法数据地址" },
{ 0x03, "非法数据值" },
{ 0x04, "从站设备故障" },
{ 0x05, "确认" },
{ 0x06, "从属设备忙" },
{ 0x07, "存储奇偶性错误" },
{ 0x08, "不可用网关路径" },
{ 0x09, "网关目标设备响应失败" },
{ 0x0A, "网关目标设备响应失败" }
};
}
3. Modbus RTU 实现
3.1 ModbusRtuClient 类
using System.IO.Ports;
using System.Threading.Tasks;
public class ModbusRtuClient : IModbusClient
{
private readonly SerialPort _serialPort;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
public bool IsConnected => _serialPort?.IsOpen ?? false;
public ModbusRtuClient(string portName, int baudRate = 9600, Parity parity = Parity.None,
int dataBits = 8, StopBits stopBits = StopBits.One,
int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = responseTimeout,
WriteTimeout = responseTimeout
};
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task ConnectAsync()
{
if (IsConnected) return true;
try
{
_serialPort.Open();
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_serialPort.Close();
}
}
public void Dispose()
{
Disconnect();
_serialPort.Dispose();
}
// 核心通信方法
private async Task SendReceiveAsync(byte slaveId, byte[] request)
{
if (!IsConnected) throw new InvalidOperationException("串口未连接");
// 添加CRC校验
byte[] frame = new byte[request.Length + 2];
Array.Copy(request, frame, request.Length);
ushort crc = CalculateCrc(request);
frame[request.Length] = (byte)(crc & 0xFF);
frame[request.Length + 1] = (byte)((crc >> 8) & 0xFF);
// 发送请求
_serialPort.DiscardInBuffer();
_serialPort.Write(frame, 0, frame.Length);
// 接收响应
int expectedLength = GetExpectedResponseLength(request[1]);
byte[] response = new byte[expectedLength + 2]; // +2 for CRC
int bytesRead = 0;
int totalBytesToRead = response.Length;
DateTime startTime = DateTime.Now;
while (bytesRead _responseTimeout)
{
throw new TimeoutException("读取响应超时");
}
if (_serialPort.BytesToRead > 0)
{
bytesRead += _serialPort.Read(response, bytesRead, totalBytesToRead - bytesRead);
}
else
{
await Task.Delay(10);
}
}
// 验证CRC
ushort receivedCrc = (ushort)(response[response.Length - 2] | (response[response.Length - 1] >= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
private int GetExpectedResponseLength(byte functionCode)
{
switch (functionCode)
{
case (byte)ModbusFunctionCode.ReadCoils:
case (byte)ModbusFunctionCode.ReadDiscreteInputs:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.ReadHoldingRegisters:
case (byte)ModbusFunctionCode.ReadInputRegisters:
return 3; // SlaveId + FC + ByteCount + (Data bytes) + CRC
case (byte)ModbusFunctionCode.WriteSingleCoil:
case (byte)ModbusFunctionCode.WriteSingleRegister:
return 6; // SlaveId + FC + Address + Value + CRC
case (byte)ModbusFunctionCode.WriteMultipleCoils:
case (byte)ModbusFunctionCode.WriteMultipleRegisters:
return 6; // SlaveId + FC + Address + Quantity + CRC
default:
throw new ArgumentException($"未知功能码: 0x{functionCode:X2}");
}
}
// 具体功能实现将在下面展开...
}
3.2 Modbus RTU 功能实现
// 在 ModbusRtuClient 类中添加以下方法
public async Task ReadCoilsAsync(byte slaveId, ushort startAddress, ushort numberOfCoils)
{
if (numberOfCoils 2000)
throw new ArgumentException("线圈数量必须在1-2000之间");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadCoils;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfCoils >> 8);
request[5] = (byte)(numberOfCoils & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
bool[] coils = new bool[numberOfCoils];
for (int i = 0; i ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters 125)
throw new ArgumentException("寄存器数量必须在1-125之间");
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(numberOfRegisters >> 8);
request[5] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, request);
int byteCount = response[2];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i WriteSingleCoilAsync(byte slaveId, ushort coilAddress, bool value)
{
byte[] request = new byte[6];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteSingleCoil;
request[2] = (byte)(coilAddress >> 8);
request[3] = (byte)(coilAddress & 0xFF);
request[4] = value ? (byte)0xFF : (byte)0x00;
request[5] = 0x00;
byte[] response = await SendReceiveAsync(slaveId, request);
// 验证响应
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
public async Task WriteMultipleRegistersAsync(byte slaveId, ushort startAddress, ushort[] values)
{
if (values == null || values.Length == 0 || values.Length > 123)
throw new ArgumentException("寄存器数量必须在1-123之间");
byte[] request = new byte[7 + values.Length * 2];
request[0] = slaveId;
request[1] = (byte)ModbusFunctionCode.WriteMultipleRegisters;
request[2] = (byte)(startAddress >> 8);
request[3] = (byte)(startAddress & 0xFF);
request[4] = (byte)(values.Length >> 8);
request[5] = (byte)(values.Length & 0xFF);
request[6] = (byte)(values.Length * 2);
for (int i = 0; i > 8);
request[8 + i * 2] = (byte)(values[i] & 0xFF);
}
byte[] response = await SendReceiveAsync(slaveId, request);
// 验证响应
if (response.Length != 6) return false;
if (response[2] != request[2] || response[3] != request[3]) return false;
if (response[4] != request[4] || response[5] != request[5]) return false;
return true;
}
// 其他功能实现类似...
4. Modbus TCP 实现
4.1 ModbusTcpClient 类
using System.Net.Sockets;
using System.Threading.Tasks;
public class ModbusTcpClient : IModbusClient
{
private TcpClient _tcpClient;
private NetworkStream _networkStream;
private readonly string _host;
private readonly int _port;
private readonly int _responseTimeout;
private readonly byte _defaultSlaveId;
private ushort _transactionId = 0;
public bool IsConnected => _tcpClient?.Connected ?? false;
public ModbusTcpClient(string host, int port = 502, int responseTimeout = 1000, byte defaultSlaveId = 1)
{
_host = host;
_port = port;
_responseTimeout = responseTimeout;
_defaultSlaveId = defaultSlaveId;
}
public async Task ConnectAsync()
{
if (IsConnected) return true;
try
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_host, _port);
_networkStream = _tcpClient.GetStream();
_networkStream.ReadTimeout = _responseTimeout;
return true;
}
catch
{
return false;
}
}
public void Disconnect()
{
if (IsConnected)
{
_networkStream?.Close();
_tcpClient?.Close();
_tcpClient = null;
_networkStream = null;
}
}
public void Dispose()
{
Disconnect();
_tcpClient?.Dispose();
_networkStream?.Dispose();
}
// 核心通信方法
private async Task SendReceiveAsync(byte slaveId, byte[] pdu)
{
if (!IsConnected) throw new InvalidOperationException("TCP连接未建立");
// 构建MBAP头
byte[] mbapHeader = new byte[7];
ushort transactionId = _transactionId++;
mbapHeader[0] = (byte)(transactionId >> 8);
mbapHeader[1] = (byte)(transactionId & 0xFF);
mbapHeader[2] = 0x00; // 协议标识符高位
mbapHeader[3] = 0x00; // 协议标识符低位
mbapHeader[4] = (byte)((pdu.Length + 1) >> 8); // 长度高位
mbapHeader[5] = (byte)((pdu.Length + 1) & 0xFF); // 长度低位
mbapHeader[6] = slaveId; // 单元标识符
// 构建完整请求
byte[] request = new byte[mbapHeader.Length + pdu.Length];
Array.Copy(mbapHeader, request, mbapHeader.Length);
Array.Copy(pdu, 0, request, mbapHeader.Length, pdu.Length);
// 发送请求
await _networkStream.WriteAsync(request, 0, request.Length);
// 接收MBAP头
byte[] mbapResponse = new byte[7];
int bytesRead = await _networkStream.ReadAsync(mbapResponse, 0, mbapResponse.Length);
if (bytesRead != mbapResponse.Length)
{
throw new IOException("读取MBAP头不完整");
}
// 验证事务ID
ushort responseTransactionId = (ushort)((mbapResponse[0] ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
if (numberOfRegisters 125)
throw new ArgumentException("寄存器数量必须在1-125之间");
byte[] pdu = new byte[5];
pdu[0] = (byte)ModbusFunctionCode.ReadHoldingRegisters;
pdu[1] = (byte)(startAddress >> 8);
pdu[2] = (byte)(startAddress & 0xFF);
pdu[3] = (byte)(numberOfRegisters >> 8);
pdu[4] = (byte)(numberOfRegisters & 0xFF);
byte[] response = await SendReceiveAsync(slaveId, pdu);
int byteCount = response[1];
ushort[] registers = new ushort[numberOfRegisters];
for (int i = 0; i
5. 工厂模式创建 Modbus 客户端
public static class ModbusClientFactory
{
public static IModbusClient CreateClient(ModbusType type, string connectionString, byte defaultSlaveId = 1)
{
switch (type)
{
case ModbusType.RTU:
// 连接字符串格式: COM1,9600,None,8,One
string[] rtuParams = connectionString.Split(',');
if (rtuParams.Length 1 ? int.Parse(rtuParams[1]) : 9600;
Parity parity = rtuParams.Length > 2 ? (Parity)Enum.Parse(typeof(Parity), rtuParams[2]) : Parity.None;
int dataBits = rtuParams.Length > 3 ? int.Parse(rtuParams[3]) : 8;
StopBits stopBits = rtuParams.Length > 4 ? (StopBits)Enum.Parse(typeof(StopBits), rtuParams[4]) : StopBits.One;
return new ModbusRtuClient(portName, baudRate, parity, dataBits, stopBits, defaultSlaveId: defaultSlaveId);
case ModbusType.TCP:
// 连接字符串格式: 192.168.1.100:502
string[] tcpParams = connectionString.Split(':');
if (tcpParams.Length 1 ? int.Parse(tcpParams[1]) : 502;
return new ModbusTcpClient(host, port, defaultSlaveId: defaultSlaveId);
default:
throw new ArgumentException("不支持的Modbus类型");
}
}
}
6. 高级功能扩展
6.1 批量读取优化
public static class ModbusExtensions
{
public static async Task> ReadHoldingRegistersRangeAsync(
this IModbusClient client, byte slaveId,
ushort startAddress, ushort endAddress, int maxPerRequest = 100)
{
var results = new Dictionary();
ushort current = startAddress;
while (current
6.2 自动重连机制
public class AutoReconnectModbusClient : IModbusClient
{
private readonly IModbusClient _innerClient;
private readonly int _maxRetries;
private readonly int _retryDelay;
public bool IsConnected => _innerClient.IsConnected;
public AutoReconnectModbusClient(IModbusClient innerClient, int maxRetries = 3, int retryDelay = 1000)
{
_innerClient = innerClient;
_maxRetries = maxRetries;
_retryDelay = retryDelay;
}
public async Task ConnectAsync()
{
for (int i = 0; i _innerClient.Disconnect();
public void Dispose() => _innerClient.Dispose();
private async Task ExecuteWithRetry(Func> operation)
{
for (int i = 0; i ReadHoldingRegistersAsync(byte slaveId, ushort startAddress, ushort numberOfRegisters)
{
return await ExecuteWithRetry(() =>
_innerClient.ReadHoldingRegistersAsync(slaveId, startAddress, numberOfRegisters));
}
// 其他方法实现类似...
}
7. 使用示例
class Program
{
static async Task Main(string[] args)
{
// 创建Modbus TCP客户端
var tcpClient = ModbusClientFactory.CreateClient(
ModbusType.TCP, "192.168.1.100:502", defaultSlaveId: 1);
// 创建带自动重连的客户端
var autoReconnectClient = new AutoReconnectModbusClient(tcpClient);
try
{
// 连接设备
if (await autoReconnectClient.ConnectAsync())
{
Console.WriteLine("Modbus连接成功");
// 读取保持寄存器
ushort[] registers = await autoReconnectClient.ReadHoldingRegistersAsync(1, 0, 10);
Console.WriteLine("寄存器值: " + string.Join(", ", registers));
// 写入单个寄存器
bool writeResult = await autoReconnectClient.WriteSingleRegisterAsync(1, 5, 1234);
Console.WriteLine($"写入寄存器结果: {writeResult}");
// 批量读取寄存器范围
var registerMap = await autoReconnectClient.ReadHoldingRegistersRangeAsync(1, 0, 100);
foreach (var kvp in registerMap)
{
Console.WriteLine($"地址 {kvp.Key}: {kvp.Value}");
}
}
else
{
Console.WriteLine("Modbus连接失败");
}
}
catch (ModbusException ex)
{
Console.WriteLine($"Modbus错误: {ex.Message} (代码: 0x{ex.ExceptionCode:X2})");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
finally
{
autoReconnectClient.Disconnect();
}
// 使用Modbus RTU
var rtuClient = ModbusClientFactory.CreateClient(
ModbusType.RTU, "COM3,9600,None,8,One", defaultSlaveId: 2);
try
{
if (await rtuClient.ConnectAsync())
{
// 读取线圈状态
bool[] coils = await rtuClient.ReadCoilsAsync(2, 0, 8);
Console.WriteLine("线圈状态: " + string.Join(", ", coils.Select(c => c ? "1" : "0")));
}
}
finally
{
rtuClient.Disconnect();
}
}
}
8. 最佳实践
1.连接管理:
- 保持连接时间尽可能短
- 使用自动重连机制处理网络波动
- 合理设置超时时间
2.错误处理:
- 捕获并处理所有Modbus异常
- 实现重试机制处理临时错误
- 记录详细错误日志
3.性能优化:
- 批量读取数据减少请求次数
- 使用异步方法避免阻塞
- 合理设置请求大小(避免过大PDU)
4.线程安全:
- Modbus客户端不是线程安全的
- 在多线程环境中使用锁或创建多个实例
- 避免并发访问同一连接
5.配置管理:
- 将Modbus配置存储在配置文件或数据库中
- 支持动态更新配置
- 提供配置验证功能
9. 总结
本文介绍了如何使用 .NET 8 实现一个功能完整的 Modbus 通讯工具类,主要特点包括:
1.双协议支持:
- 完整实现 Modbus RTU 和 Modbus TCP 协议
- 支持所有常用功能码
2.优雅封装:
- 统一的接口设计
- 工厂模式创建客户端
- 扩展方法提供高级功能
3.健壮性设计:
- 完善的错误处理机制
- 自动重连功能
- CRC 和事务ID验证
4.易用性:
- 简洁的API设计
- 详细的异常信息
- 丰富的使用示例
通过这种封装,开发者可以快速集成 Modbus 通讯功能到各种工业控制系统中,大大提高了开发效率和系统可靠性。该工具类适用于 SCADA 系统、MES 系统、设备监控平台等各种工业自动化场景。
以上就是.NET 8实现modbus通讯工具类封装的操作方法的详细内容,更多关于.NET 8 modbus工具类封装的资料请关注IT俱乐部其它相关文章!
