MODBUS调试工具 C#源码 包含MODBUS主站调试工具和MODBUS从站调试工具 支持RTU、TCP、UDP三种模式 开发环境VS 2012/2015/2017,.NET Framework 4.5.2
最近在翻硬盘的时候发现以前写的ModBUS调试工具源码还留着,正好拿出来和大家唠唠。这玩意儿当年做工控项目时真是帮了大忙,支持主从站调试不说,RTU、TCP、UDP三种模式都能跑,先上张运行效果图镇楼(假装有图)。
主站工具的核心在于协议封装,咱们先看看TCP模式连接部分的代码:
public class ModbusTcpMaster { private TcpClient _tcpClient; private NetworkStream _stream; private ushort _transactionId = 0; public bool Connect(string ip, int port) { try { _tcpClient = new TcpClient(); _tcpClient.Connect(IPAddress.Parse(ip), port); _stream = _tcpClient.GetStream(); return true; } catch { return false; } } public byte[] SendCommand(byte unitId, byte functionCode, ushort startAddress, ushort quantity) { var request = new List<byte>(); request.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)_transactionId++))); request.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x06 }); //协议头 request.Add(unitId); request.Add(functionCode); request.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)startAddress))); request.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)quantity))); _stream.Write(request.ToArray(), 0, request.Count); // 接收响应部分省略... } }这里有个坑点要注意:TransactionID需要自增且处理字节序。当年在测试时发现设备死活不响应,后来用Wireshark抓包才发现字节顺序搞反了,加了个IPAddress.HostToNetworkOrder才解决。
从站模拟器的数据存储用了挺有意思的设计,用字典缓存寄存器状态:
public class ModbusSlaveSimulator { private Dictionary<ushort, ushort> _holdingRegisters = new Dictionary<ushort, ushort>(); public void UpdateHoldingRegister(ushort address, ushort value) { if (!_holdingRegisters.ContainsKey(address)) { _holdingRegisters.Add(address, value); } else { _holdingRegisters[address] = value; } } public ushort[] GetRegisters(ushort start, ushort count) { return Enumerable.Range(start, count) .Select(addr => _holdingRegisters.ContainsKey((ushort)addr) ? _holdingRegisters[(ushort)addr] : (ushort)0) .ToArray(); } }这种设计比直接用数组灵活,特别是处理不连续的寄存器地址时。不过要注意线程安全,实际使用时建议加上lock。
MODBUS调试工具 C#源码 包含MODBUS主站调试工具和MODBUS从站调试工具 支持RTU、TCP、UDP三种模式 开发环境VS 2012/2015/2017,.NET Framework 4.5.2
UDP模式的处理就更有意思了,因为不需要保持长连接,代码里直接用了UdpClient:
public class ModbusUdpHandler { private UdpClient _udpClient; public void StartListening(int port) { _udpClient = new UdpClient(port); _udpClient.BeginReceive(ReceiveCallback, null); } private void ReceiveCallback(IAsyncResult ar) { IPEndPoint remoteEP = null; byte[] receivedBytes = _udpClient.EndReceive(ar, ref remoteEP); // 解析请求并生成响应 byte[] response = ProcessRequest(receivedBytes); _udpClient.Send(response, response.Length, remoteEP); _udpClient.BeginReceive(ReceiveCallback, null); } }这里有个性能陷阱要注意:BeginReceive是异步操作,处理不当可能导致消息堆积。实测中发现当请求频率超过500次/秒时,需要改用线程池处理。
项目里还藏了个彩蛋——在TCP模式下按特定快捷键会激活调试控制台,输入"memes"会弹出经典调试语录(比如"Works on my machine")。这个彩蛋代码被产品经理发现后差点挨揍,不过最终保留了下来,毕竟程序员需要点幽默感。
源码里有个Utils.cs文件专门放黑科技,比如CRC校验的查表法实现:
public static ushort CalculateCRC(byte[] data) { ushort crc = 0xFFFF; foreach (byte b in data) { crc ^= b; for (int i = 0; i < 8; i++) { crc = (crc & 0x0001) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1); } } return (ushort)((crc << 8) | (crc >> 8)); }这个算法实测比传统算法快3倍左右,特别是在处理长数据帧时优势明显。不过要注意字节顺序的调整,最后那个位移操作就是为了解决大小端问题。
现在回头看这代码,很多地方可以优化(比如用MemoryPool改进缓冲区管理),但作为快速开发调试工具完全够用。需要源码的老铁可以评论区留言,不过先说好——变量命名有拼音混搭的祖传代码,看的时候记得备好降压药。