MODBUS调试工具 C#源码 包含MODBUS主站调试工具和MODBUS从站调试工具 支持RTU、TCP、UDP三种模式 开发环境VS 2012/2015/2017,.NET Framework 4.5.2
最近在翻硬盘的时候发现以前写的ModBUS调试工具源码还留着,正好拿出来和大家唠唠。这玩意儿当年做工控项目时真是帮了大忙,支持主从站调试不说,RTU、TCP、UDP三种模式都能跑,先上张运行效果图镇楼(假装有图)。

主站工具的核心在于协议封装,咱们先看看TCP模式连接部分的代码:
csharp
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才解决。
从站模拟器的数据存储用了挺有意思的设计,用字典缓存寄存器状态:
csharp
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。

UDP模式的处理就更有意思了,因为不需要保持长连接,代码里直接用了UdpClient:
csharp
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校验的查表法实现:
csharp
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改进缓冲区管理),但作为快速开发调试工具完全够用。需要源码的老铁可以评论区留言,不过先说好------变量命名有拼音混搭的祖传代码,看的时候记得备好降压药。