记一个Mudbus TCP 帮助类
Modbus 协议的详细介绍,见这位大神的文章:ModbusTcp协议_modbus tcp协议-CSDN博客
本文仅记录项目中用到的ModbusTcp的帮助类
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using RadarLWQ;namespace ModbusTCP
{public class ModbusTcpClient : IDisposable{[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]public struct Union16{[System.Runtime.InteropServices.FieldOffset(0)]public ushort Value;[System.Runtime.InteropServices.FieldOffset(0)]public byte LowByte;[System.Runtime.InteropServices.FieldOffset(1)]public byte HighByte;}public enum Endianness{ABCD, // 高字高字节在前 (Big-endian)BADC, // 高字低字节在前CDAB, // 低字高字节在前DCBA // 低字低字节在前 (Little-endian)}private TcpClient _client;private NetworkStream _stream;private ushort _transactionId = 0;private bool _disposed = false;private readonly string _ipAddress;private readonly int _port;public ModbusTcpClient(string ipAddress, int port = 502){_ipAddress = ipAddress;_port = port;_client = new TcpClient();try{if (!_client.Connected){_client.Connect(IPAddress.Parse(_ipAddress), _port);_stream = _client.GetStream();_stream.ReadTimeout = 3000; // 设置读取超时为5秒_stream.WriteTimeout = 3000; // 设置写入超时为5秒}}catch (Exception ex){throw new Exception($"Modbus连接失败: {ex.Message}", ex);}//_client.ReceiveTimeout = 5000; // 设置接收超时为5秒//_client.SendTimeout = 5000; // 设置接收超时为5秒//Connect();}// 连接到Modbus设备private void Connect(){try{if (!_client.Connected){_client.Connect(IPAddress.Parse(_ipAddress), _port);_stream = _client.GetStream();}}catch (Exception ex){throw new Exception($"Modbus连接失败: {ex.Message}", ex);}}// 检查连接是否有效public bool IsConnected => _client != null && _client.Connected;#region 读取// 读取保持寄存器(支持读取多个寄存器)public ushort[] ReadHoldingRegisters(ushort startAddress, ushort quantity, ushort slaveId){byte[] request = BuildReadRequest(ModbusFunctionCode.ReadHoldingRegisters, startAddress, quantity, slaveId);byte[] response = SendRequest(request);// 验证响应if (response[7] == (byte)ModbusFunctionCode.ReadHoldingRegisters + 0x80){throw new Exception($"Modbus异常: {response[8]}");}int byteCount = response[8];ushort[] registers = new ushort[byteCount / 2];for (int i = 0; i < registers.Length; i++){registers[i] = (ushort)((response[9 + i * 2] << 8) | response[10 + i * 2]);}return registers;}// 读取输入寄存器(支持读取多个寄存器)public ushort[] ReadInputRegisters(ushort startAddress, ushort quantity, ushort slaveId){byte[] request = BuildReadRequest(ModbusFunctionCode.ReadInputRegisters, startAddress, quantity, slaveId);byte[] response = SendRequest(request);// 验证响应if (response[7] == (byte)ModbusFunctionCode.ReadInputRegisters + 0x80){throw new Exception($"Modbus异常: {response[8]}");}int byteCount = response[8];ushort[] registers = new ushort[byteCount / 2];for (int i = 0; i < registers.Length; i++){registers[i] = (ushort)((response[9 + i * 2] << 8) | response[10 + i * 2]);}return registers;}// 读取real(float)类型数据(输入寄存器)public float ReadRealInput(ushort startAddress, ushort slaveId, Endianness endianness = Endianness.ABCD){ushort[] registers = ReadInputRegisters(startAddress, 2, slaveId);//uint value = (uint)((registers[0] << 16) | registers[1]);//return BitConverter.ToSingle(BitConverter.GetBytes(value), 0);return ConvertRegistersToFloat(registers, endianness);}// 读取real(float)类型数据(保持寄存器)public float ReadReal(ushort startAddress, ushort slaveId, Endianness endianness = Endianness.ABCD){ushort[] registers = ReadHoldingRegisters(startAddress, 2, slaveId);//uint value = (uint)((registers[0] << 16) | registers[1]);//return BitConverter.ToSingle(BitConverter.GetBytes(value), 0);return ConvertRegistersToFloat(registers, endianness);}// 将寄存器值转换为浮点数,支持不同字节序private float ConvertRegistersToFloat(ushort[] registers, Endianness endianness){if (registers.Length < 2){throw new ArgumentException("需要至少两个寄存器来表示一个浮点数");}byte[] bytes = new byte[4];switch (endianness){case Endianness.ABCD: // 高字高字节在前 (Big-endian)bytes[0] = (byte)(registers[0] >> 8);bytes[1] = (byte)(registers[0] & 0xFF);bytes[2] = (byte)(registers[1] >> 8);bytes[3] = (byte)(registers[1] & 0xFF);break;case Endianness.BADC: // 高字低字节在前bytes[0] = (byte)(registers[0] & 0xFF);bytes[1] = (byte)(registers[0] >> 8);bytes[2] = (byte)(registers[1] & 0xFF);bytes[3] = (byte)(registers[1] >> 8);break;case Endianness.CDAB: // 低字高字节在前bytes[0] = (byte)(registers[1] >> 8);bytes[1] = (byte)(registers[1] & 0xFF);bytes[2] = (byte)(registers[0] >> 8);bytes[3] = (byte)(registers[0] & 0xFF);break;case Endianness.DCBA: // 低字低字节在前 (Little-endian)bytes[0] = (byte)(registers[1] & 0xFF);bytes[1] = (byte)(registers[1] >> 8);bytes[2] = (byte)(registers[0] & 0xFF);bytes[3] = (byte)(registers[0] >> 8);break;}uint value = (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);return BitConverter.ToSingle(BitConverter.GetBytes(value), 0);}// 读取uint(ushort)类型数据(输入寄存器)public ushort ReadUIntInput(ushort address, ushort slaveId){return ReadInputRegisters(address, 1, slaveId)[0];}// 读取uint(ushort)类型数据(保持寄存器)public ushort ReadUInt(ushort address,ushort slaveId){return ReadHoldingRegisters(address, 1, slaveId)[0];}#endregion#region 写入// 写入单个寄存器public bool WriteSingleRegister(ushort address, ushort value, ushort slaveId){byte[] request = BuildWriteSingleRegisterRequest(address, value, slaveId);byte[] response = SendRequest(request);// 验证响应if (response[7] == (byte)ModbusFunctionCode.WriteSingleRegister + 0x80){throw new Exception($"Modbus异常: {response[8]}");return false;}else{return true;}}// 写入多个寄存器public bool WriteMultipleRegisters(ushort startAddress, ushort[] values, ushort slaveId){byte[] request = BuildWriteMultipleRegistersRequest(startAddress, values, slaveId);byte[] response = SendRequest(request);// 验证响应if (response[7] == (byte)ModbusFunctionCode.WriteMultipleRegisters + 0x80){throw new Exception($"Modbus异常: {response[8]}");}else{return true;}}// 写入real(float)类型数据public bool WriteReal(ushort startAddress, float value, ushort slaveId,Endianness endianness = Endianness.ABCD){byte[] floatBytes = BitConverter.GetBytes(value);uint floatValue = BitConverter.ToUInt32(floatBytes, 0);ushort[] registers = new ushort[2];switch (endianness){case Endianness.ABCD: // 高字高字节在前 (Big-endian)registers[0] = (ushort)(floatValue >> 16);registers[1] = (ushort)(floatValue & 0xFFFF);break;case Endianness.BADC: // 高字低字节在前registers[0] = (ushort)(((floatValue & 0xFF00) >> 8) | ((floatValue & 0x00FF) << 8));registers[1] = (ushort)(((floatValue & 0xFF0000) >> 24) | ((floatValue & 0x00FF00) >> 8));break;case Endianness.CDAB: // 低字高字节在前registers[1] = (ushort)(floatValue >> 16);registers[0] = (ushort)(floatValue & 0xFFFF);break;case Endianness.DCBA: // 低字低字节在前 (Little-endian)registers[1] = (ushort)(((floatValue & 0xFF00) >> 8) | ((floatValue & 0x00FF) << 8));registers[0] = (ushort)(((floatValue & 0xFF0000) >> 24) | ((floatValue & 0x00FF00) >> 8));break;}WriteSingleRegister(startAddress, registers[0], slaveId);return WriteSingleRegister((ushort)(startAddress+1), registers[1], slaveId);}// 写入real(float)类型数据public void WriteReal(ushort startAddress, float value, ushort slaveId){byte[] floatBytes = BitConverter.GetBytes(value);uint floatValue = BitConverter.ToUInt32(floatBytes, 0);ushort highWord = (ushort)(floatValue >> 16);ushort lowWord = (ushort)(floatValue & 0xFFFF);WriteMultipleRegisters(startAddress, new ushort[] { highWord, lowWord }, slaveId);}// 写入uint(ushort)类型数据public bool WriteUInt(ushort address, ushort value, ushort slaveId){return WriteSingleRegister(address, value, slaveId);}#endregion// 构建读取请求private byte[] BuildReadRequest(ModbusFunctionCode functionCode, ushort startAddress, ushort quantity, ushort slaveId){byte[] request = new byte[12];ushort transactionId = GetNextTransactionId();request[0] = (byte)(transactionId >> 8); // 事务标识符高字节request[1] = (byte)transactionId; // 事务标识符低字节request[2] = 0; // 协议标识符高字节(0表示Modbus)request[3] = 0; // 协议标识符低字节request[4] = 0; // 长度高字节request[5] = 6; // 长度低字节(后续字节数)request[6] = Convert.ToByte(slaveId); // 单元标识符request[7] = (byte)functionCode; // 功能码request[8] = (byte)(startAddress >> 8); // 起始地址高字节request[9] = (byte)startAddress; // 起始地址低字节request[10] = (byte)(quantity >> 8); // 寄存器数量高字节request[11] = (byte)quantity; // 寄存器数量低字节return request;}/// <summary>/// 构建写入单个寄存器请求/// </summary>/// <param name="address"></param>/// <param name="value"></param>/// <param name="slaveId">站id,1个站可以代表1个设备</param>/// <returns></returns> private byte[] BuildWriteSingleRegisterRequest(ushort address, ushort value, ushort slaveId){byte[] request = new byte[12];ushort transactionId = GetNextTransactionId();request[0] = (byte)(transactionId >> 8); // 事务标识符高字节request[1] = (byte)transactionId; // 事务标识符低字节request[2] = 0; // 协议标识符高字节request[3] = 0; // 协议标识符低字节request[4] = 0; // 长度高字节request[5] = 6; // 长度低字节request[6] = Convert.ToByte(slaveId); // 单元标识符request[7] = (byte)ModbusFunctionCode.WriteSingleRegister; // 功能码request[8] = (byte)(address >> 8); // 寄存器地址高字节request[9] = (byte)address; // 寄存器地址低字节request[10] = (byte)(value >> 8); // 寄存器值高字节request[11] = (byte)value; // 寄存器值低字节return request;}/// <summary>/// 构建写入多个寄存器请求/// </summary>/// <param name="startAddress"></param>/// <param name="values"></param>/// <param name="slaveId">站id,1个站可以代表1个设备</param>/// <returns></returns> private byte[] BuildWriteMultipleRegistersRequest(ushort startAddress, ushort[] values, ushort slaveId){int byteCount = values.Length * 2;int requestLength = 13 + byteCount;byte[] request = new byte[requestLength];ushort transactionId = GetNextTransactionId();request[0] = (byte)(transactionId >> 8); // 事务标识符高字节request[1] = (byte)transactionId; // 事务标识符低字节request[2] = 0; // 协议标识符高字节request[3] = 0; // 协议标识符低字节request[4] = 0; // 长度高字节request[5] = (byte)(7 + byteCount); // 长度低字节request[6] = Convert.ToByte(slaveId); // 单元标识符request[7] = (byte)ModbusFunctionCode.WriteMultipleRegisters; // 功能码request[8] = (byte)(startAddress >> 8); // 起始地址高字节request[9] = (byte)startAddress; // 起始地址低字节request[10] = (byte)(values.Length >> 8); // 寄存器数量高字节request[11] = (byte)values.Length; // 寄存器数量低字节request[12] = (byte)byteCount; // 字节数// 填充寄存器值for (int i = 0; i < values.Length; i++){request[13 + i * 2] = (byte)(values[i] >> 8); // 值高字节request[14 + i * 2] = (byte)values[i]; // 值低字节}return request;}// 发送请求并接收响应private byte[] SendRequest(byte[] request){_stream.Write(request, 0, request.Length);// 读取响应头获取长度信息byte[] header = new byte[6];int bytesRead = 0;while (bytesRead < 6){int count = _stream.Read(header, bytesRead, 6 - bytesRead);if (count == 0){throw new Exception("连接已关闭");}bytesRead += count;}// 计算剩余响应长度int length = (header[4] << 8) | header[5];byte[] response = new byte[length + 6];// 复制头部Array.Copy(header, 0, response, 0, 6);// 读取剩余响应bytesRead = 0;while (bytesRead < length){int count = _stream.Read(response, 6 + bytesRead, length - bytesRead);if (count == 0){throw new Exception("连接已关闭");}bytesRead += count;}return response;}// 获取下一个事务IDprivate ushort GetNextTransactionId(){return _transactionId++;}// 功能码枚举//private enum FunctionCode : byte//{// ReadHoldingRegisters = 0x03,// ReadInputRegisters = 0x04,// WriteSingleRegister = 0x06,// WriteMultipleRegisters = 0x10//}// 实现IDisposable接口public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}protected virtual void Dispose(bool disposing){if (!_disposed){if (disposing){// 释放托管资源if (_stream != null){_stream.Close();_stream.Dispose();_stream = null;}if (_client != null){_client.Close();_client.Dispose();_client = null;}}_disposed = true;}}}
}
因为需要有多个主站,所以需要对连接进行管理,所以建立ModbusManager管理类
using ModbusTCP;
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static ModbusTCP.ModbusTcpClient;namespace ModbusHelper
{public class ModbusManager{private static readonly ConcurrentDictionary<string, ModbusTcpClient> _clients = new ConcurrentDictionary<string, ModbusTcpClient>();public static ModbusTcpClient GetClientAsync(string ip, int port, CancellationToken cancellationToken = default){try{string key = $"{ip}:{port}";if (!_clients.TryGetValue(key, out var client)){client = new ModbusTcpClient(ip, port);_clients[key] = client;}return client;}catch (Exception ex){WriteMsg($"{DateTime.Now}-- 创建Modbus TCP客户端失败,IP: {ip}, 错误: {ex.Message}");//throw ex;return null;}}public void RemoveClient(string ip, int port){string key = $"{ip}:{port}";_clients.TryRemove(key, out _);}/// <summary>/// 获取Modbus TCP客户端连接/// </summary>/// <param name="ip"></param>/// <param name="port"></param>/// <returns></returns>private ModbusTcpClient GetModbusTcpClient(string ip, int port){try{if (!_clients.TryGetValue(ip, out ModbusTcpClient client)){// 创建新的Modbus TCP客户端client = new ModbusTcpClient(ip, port);_clients[ip] = client; // 缓存客户端}return client;}catch (Exception ex){WriteMsg($"{DateTime.Now}-- 创建Modbus TCP客户端失败,IP: {ip}, 错误: {ex.Message}");//throw ex;return null;}}private static void WriteMsg(string text){text += "\r\n";string filePath = AppDomain.CurrentDomain.BaseDirectory + $"RadarLogs\\{DateTime.Now.ToString("yyyyMMdd")}MsgError.txt";// 确保目录存在string directory = Path.GetDirectoryName(filePath);if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)){Directory.CreateDirectory(directory);}System.IO.File.AppendAllText(filePath, text, Encoding.UTF8); // 修复错误:明确使用 System.IO.File}}}
使用时,仅需要获取连接即可,
//获取连接
var client = ModbusManager.GetClientAsync(ipAddr, port);//读取UInt
var val1 = client.ReadUIntInput((ushort)(30001), slaveId);
//读取Real
var val2 = client.ReadRealInput((ushort)readStartAddr, slaveId, Endianness.CDAB);//写UInt类型
var val3 = client.WriteUInt((ushort)(40001), 1, slaveId);
//写Real类型
var val4 = client.WriteReal((ushort)(40002), 1.314, slaveId, Endianness.CDAB);