C# Modbus 协议实现

C#实现Modbus协议的解决方案,支持TCP和RTU两种通信模式,包含主站(客户端)和从站(服务器)功能。

解决方案结构

csharp 复制代码
ModbusSolution/
├── ModbusCore/          // 核心协议实现
│   ├── ModbusProtocol.cs
│   ├── ModbusException.cs
│   └── ModbusEnums.cs
├── ModbusTcp/           // TCP通信实现
│   ├── ModbusTcpClient.cs
│   └── ModbusTcpServer.cs
├── ModbusRtu/           // RTU通信实现
│   ├── ModbusRtuClient.cs
│   └── ModbusRtuServer.cs
└── ModbusDemo/          // 演示程序
    ├── Program.cs
    └── Form1.cs

核心代码实现

1. ModbusEnums.cs (枚举定义)

csharp 复制代码
namespace ModbusCore
{
    public enum FunctionCode : byte
    {
        ReadCoils = 0x01,
        ReadDiscreteInputs = 0x02,
        ReadHoldingRegisters = 0x03,
        ReadInputRegisters = 0x04,
        WriteSingleCoil = 0x05,
        WriteSingleRegister = 0x06,
        WriteMultipleCoils = 0x0F,
        WriteMultipleRegisters = 0x10,
        MaskWriteRegister = 0x16,
        ReadWriteMultipleRegisters = 0x17
    }

    public enum ExceptionCode : byte
    {
        IllegalFunction = 0x01,
        IllegalDataAddress = 0x02,
        IllegalDataValue = 0x03,
        ServerDeviceFailure = 0x04,
        Acknowledge = 0x05,
        ServerDeviceBusy = 0x06,
        MemoryParityError = 0x08,
        GatewayPathUnavailable = 0x0A,
        GatewayTargetDeviceFailedToRespond = 0x0B
    }

    public enum ModbusMode
    {
        Tcp,
        Rtu
    }
}

2. ModbusException.cs (异常处理)

csharp 复制代码
using System;

namespace ModbusCore
{
    public class ModbusException : Exception
    {
        public FunctionCode FunctionCode { get; }
        public ExceptionCode ExceptionCode { get; }

        public ModbusException(FunctionCode functionCode, ExceptionCode exceptionCode)
            : base($"Modbus exception: Function {functionCode:X2}, Error {exceptionCode:X2}")
        {
            FunctionCode = functionCode;
            ExceptionCode = exceptionCode;
        }
    }
}

3. ModbusProtocol.cs (核心协议处理)

csharp 复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ModbusCore
{
    public static class ModbusProtocol
    {
        private const ushort TcpHeaderSize = 6;
        private const ushort RtuCrcSize = 2;

        public static byte[] CreateReadRequest(FunctionCode functionCode, ushort address, ushort count, byte unitId = 1)
        {
            using (var stream = new MemoryStream())
            using (var writer = new BinaryWriter(stream))
            {
                // 设备地址
                writer.Write(unitId);
                
                // 功能码
                writer.Write((byte)functionCode);
                
                // 起始地址
                writer.Write(BitConverter.GetBytes(address).Reverse().ToArray());
                
                // 读取数量
                writer.Write(BitConverter.GetBytes(count).Reverse().ToArray());
                
                return stream.ToArray();
            }
        }

        public static byte[] CreateWriteSingleRequest(FunctionCode functionCode, ushort address, ushort value, byte unitId = 1)
        {
            using (var stream = new MemoryStream())
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(unitId);
                writer.Write((byte)functionCode);
                writer.Write(BitConverter.GetBytes(address).Reverse().ToArray());
                
                if (functionCode == FunctionCode.WriteSingleCoil)
                {
                    writer.Write(BitConverter.GetBytes(value == 0 ? (ushort)0x0000 : (ushort)0xFF00).Reverse().ToArray());
                }
                else
                {
                    writer.Write(BitConverter.GetBytes(value).Reverse().ToArray());
                }
                
                return stream.ToArray();
            }
        }

        public static byte[] CreateWriteMultipleRequest(FunctionCode functionCode, ushort address, ushort count, byte[] data, byte unitId = 1)
        {
            using (var stream = new MemoryStream())
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(unitId);
                writer.Write((byte)functionCode);
                writer.Write(BitConverter.GetBytes(address).Reverse().ToArray());
                writer.Write(BitConverter.GetBytes(count).Reverse().ToArray());
                
                if (functionCode == FunctionCode.WriteMultipleCoils)
                {
                    int byteCount = (count + 7) / 8;
                    writer.Write((byte)byteCount);
                    writer.Write(data.Take(byteCount).ToArray());
                }
                else
                {
                    writer.Write((byte)(data.Length / 2));
                    writer.Write(data);
                }
                
                return stream.ToArray();
            }
        }

        public static (byte[] Data, ushort ByteCount) ParseReadResponse(byte[] response, FunctionCode expectedFunction)
        {
            if (response[1] == (byte)expectedFunction + 0x80)
            {
                throw new ModbusException(expectedFunction, (ExceptionCode)response[2]);
            }

            if (response[1] != (byte)expectedFunction)
            {
                throw new FormatException("Invalid function code in response");
            }

            ushort byteCount = response[2];
            byte[] data = new byte[byteCount];
            Array.Copy(response, 3, data, 0, byteCount);

            return (data, byteCount);
        }

        public static void ValidateWriteResponse(byte[] response, FunctionCode expectedFunction, ushort address, ushort? value = null)
        {
            if (response[1] == (byte)expectedFunction + 0x80)
            {
                throw new ModbusException(expectedFunction, (ExceptionCode)response[2]);
            }

            if (response[1] != (byte)expectedFunction)
            {
                throw new FormatException("Invalid function code in response");
            }

            ushort respAddress = BitConverter.ToUInt16(response.Skip(2).Take(2).Reverse().ToArray(), 0);
            if (respAddress != address)
            {
                throw new FormatException("Address mismatch in response");
            }

            if (value.HasValue)
            {
                ushort respValue = BitConverter.ToUInt16(response.Skip(4).Take(2).Reverse().ToArray(), 0);
                if (respValue != value.Value)
                {
                    throw new FormatException("Value mismatch in response");
                }
            }
        }

        public static ushort CalculateCrc(byte[] data)
        {
            ushort crc = 0xFFFF;
            foreach (byte b in data)
            {
                crc ^= b;
                for (int i = 0; i < 8; i++)
                {
                    if ((crc & 0x0001) != 0)
                    {
                        crc >>= 1;
                        crc ^= 0xA001;
                    }
                    else
                    {
                        crc >>= 1;
                    }
                }
            }
            return crc;
        }

        public static byte[] AddCrc(byte[] data)
        {
            ushort crc = CalculateCrc(data);
            return data.Concat(new[] { (byte)(crc & 0xFF), (byte)(crc >> 8) }).ToArray();
        }

        public static bool CheckCrc(byte[] data)
        {
            if (data.Length < 2) return false;
            
            byte[] frame = data.Take(data.Length - 2).ToArray();
            byte[] crcBytes = data.Skip(data.Length - 2).ToArray();
            ushort calculatedCrc = CalculateCrc(frame);
            
            return crcBytes[0] == (byte)(calculatedCrc & 0xFF) && 
                   crcBytes[1] == (byte)(calculatedCrc >> 8);
        }
    }
}

4. ModbusTcpClient.cs (TCP客户端)

csharp 复制代码
using System;
using System.IO;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace ModbusTcp
{
    public class ModbusTcpClient
    {
        private readonly string _ipAddress;
        private readonly int _port;
        private TcpClient _tcpClient;
        private NetworkStream _stream;
        private ushort _transactionId;

        public ModbusTcpClient(string ipAddress, int port = 502)
        {
            _ipAddress = ipAddress;
            _port = port;
            _transactionId = 0;
        }

        public async Task ConnectAsync()
        {
            _tcpClient = new TcpClient();
            await _tcpClient.ConnectAsync(_ipAddress, _port);
            _stream = _tcpClient.GetStream();
        }

        public async Task DisconnectAsync()
        {
            if (_stream != null) await _stream.DisposeAsync();
            _tcpClient?.Close();
        }

        private byte[] BuildTcpFrame(byte[] pdu)
        {
            _transactionId = (ushort)((_transactionId + 1) % 65536);
            
            using (var ms = new MemoryStream())
            using (var writer = new BinaryWriter(ms))
            {
                writer.Write(_transactionId);
                writer.Write((ushort)0); // Protocol ID (0 for Modbus)
                writer.Write((ushort)(pdu.Length + 1)); // Length (unit id + pdu length)
                writer.Write((byte)1); // Unit ID (default 1)
                writer.Write(pdu);
                return ms.ToArray();
            }
        }

        private async Task<byte[]> SendRequestAsync(byte[] request)
        {
            var tcpFrame = BuildTcpFrame(request);
            await _stream.WriteAsync(tcpFrame, 0, tcpFrame.Length);
            
            // 读取响应头 (MBAP header)
            byte[] header = new byte[6];
            await _stream.ReadExactlyAsync(header, 0, 6);
            
            // 解析长度字段
            ushort length = (ushort)((header[4] << 8) | header[5]);
            byte[] response = new byte[length];
            await _stream.ReadExactlyAsync(response, 0, length);
            
            // 验证事务ID
            ushort respTransactionId = (ushort)((header[0] << 8) | header[1]);
            if (respTransactionId != _transactionId)
            {
                throw new InvalidOperationException("Transaction ID mismatch");
            }
            
            return response;
        }

        public async Task<bool[]> ReadCoilsAsync(byte unitId, ushort address, ushort count)
        {
            var request = ModbusCore.ModbusProtocol.CreateReadRequest(
                ModbusCore.FunctionCode.ReadCoils, address, count, unitId);
            
            var response = await SendRequestAsync(request);
            var (data, byteCount) = ModbusCore.ModbusProtocol.ParseReadResponse(
                response, ModbusCore.FunctionCode.ReadCoils);
            
            // 将字节数组转换为布尔数组
            List<bool> coils = new List<bool>();
            for (int i = 0; i < byteCount; i++)
            {
                byte b = data[i];
                for (int j = 0; j < 8; j++)
                {
                    if (coils.Count < count)
                    {
                        coils.Add((b & (1 << j)) != 0);
                    }
                }
            }
            return coils.Take(count).ToArray();
        }

        // 其他读写方法类似实现...
        // 包括 ReadDiscreteInputsAsync, ReadHoldingRegistersAsync, ReadInputRegistersAsync
        // WriteSingleCoilAsync, WriteSingleRegisterAsync, WriteMultipleCoilsAsync, WriteMultipleRegistersAsync
    }
}

5. ModbusRtuClient.cs (RTU客户端)

csharp 复制代码
using System;
using System.IO.Ports;
using System.Threading.Tasks;

namespace ModbusRtu
{
    public class ModbusRtuClient
    {
        private SerialPort _serialPort;
        private readonly string _portName;
        private readonly int _baudRate;
        private readonly Parity _parity;
        private readonly int _dataBits;
        private readonly StopBits _stopBits;

        public ModbusRtuClient(string portName, int baudRate = 9600, 
                              Parity parity = Parity.None, int dataBits = 8, 
                              StopBits stopBits = StopBits.One)
        {
            _portName = portName;
            _baudRate = baudRate;
            _parity = parity;
            _dataBits = dataBits;
            _stopBits = stopBits;
        }

        public void Connect()
        {
            _serialPort = new SerialPort(_portName, _baudRate, _parity, _dataBits, _stopBits)
            {
                ReadTimeout = 1000,
                WriteTimeout = 1000
            };
            _serialPort.Open();
        }

        public void Disconnect()
        {
            if (_serialPort != null && _serialPort.IsOpen)
            {
                _serialPort.Close();
                _serialPort.Dispose();
            }
        }

        private async Task<byte[]> SendRequestAsync(byte[] request)
        {
            // 添加CRC校验
            byte[] frame = ModbusCore.ModbusProtocol.AddCrc(request);
            
            // 发送请求
            _serialPort.Write(frame, 0, frame.Length);
            
            // 读取响应
            int responseLength = 5; // 最小响应长度
            if (request[1] == (byte)ModbusCore.FunctionCode.ReadCoils ||
                request[1] == (byte)ModbusCore.FunctionCode.ReadDiscreteInputs ||
                request[1] == (byte)ModbusCore.FunctionCode.ReadHoldingRegisters ||
                request[1] == (byte)ModbusCore.FunctionCode.ReadInputRegisters)
            {
                // 读取请求需要知道返回的数据长度
                ushort byteCount = request[4]; // 请求中的数量
                if (request[1] == (byte)ModbusCore.FunctionCode.ReadCoils ||
                    request[1] == (byte)ModbusCore.FunctionCode.ReadDiscreteInputs)
                {
                    responseLength = 3 + (byteCount + 7) / 8 + 2; // 地址+功能码+字节数+数据+CRC
                }
                else
                {
                    responseLength = 3 + byteCount * 2 + 2; // 地址+功能码+字节数+数据+CRC
                }
            }
            else
            {
                responseLength = 8; // 写操作的固定响应长度
            }
            
            byte[] response = new byte[responseLength];
            _serialPort.Read(response, 0, responseLength);
            
            // 验证CRC
            if (!ModbusCore.ModbusProtocol.CheckCrc(response))
            {
                throw new InvalidOperationException("CRC check failed");
            }
            
            return response.Take(responseLength - 2).ToArray(); // 去掉CRC
        }

        public async Task<bool[]> ReadCoilsAsync(byte unitId, ushort address, ushort count)
        {
            var request = ModbusCore.ModbusProtocol.CreateReadRequest(
                ModbusCore.FunctionCode.ReadCoils, address, count, unitId);
            
            var response = await SendRequestAsync(request);
            var (data, byteCount) = ModbusCore.ModbusProtocol.ParseReadResponse(
                response, ModbusCore.FunctionCode.ReadCoils);
            
            // 转换字节数组为布尔数组
            List<bool> coils = new List<bool>();
            for (int i = 0; i < byteCount; i++)
            {
                byte b = data[i];
                for (int j = 0; j < 8; j++)
                {
                    if (coils.Count < count)
                    {
                        coils.Add((b & (1 << j)) != 0);
                    }
                }
            }
            return coils.Take(count).ToArray();
        }

        // 其他读写方法类似实现...
    }
}

6. ModbusDemo (演示程序)

csharp 复制代码
using System;
using System.Windows.Forms;
using ModbusTcp;
using ModbusRtu;

namespace ModbusDemo
{
    public partial class MainForm : Form
    {
        private ModbusTcpClient _tcpClient;
        private ModbusRtuClient _rtuClient;
        
        public MainForm()
        {
            InitializeComponent();
        }

        private async void btnTcpConnect_Click(object sender, EventArgs e)
        {
            try
            {
                _tcpClient = new ModbusTcpClient(txtIpAddress.Text, int.Parse(txtPort.Text));
                await _tcpClient.ConnectAsync();
                MessageBox.Show("TCP连接成功!");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}");
            }
        }

        private void btnRtuConnect_Click(object sender, EventArgs e)
        {
            try
            {
                _rtuClient = new ModbusRtuClient(
                    txtComPort.Text, 
                    int.Parse(txtBaudRate.Text),
                    GetParity(txtParity.Text),
                    int.Parse(txtDataBits.Text),
                    GetStopBits(txtStopBits.Text));
                
                _rtuClient.Connect();
                MessageBox.Show("RTU连接成功!");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接失败: {ex.Message}");
            }
        }

        private async void btnReadCoils_Click(object sender, EventArgs e)
        {
            try
            {
                if (_tcpClient != null)
                {
                    var coils = await _tcpClient.ReadCoilsAsync(
                        byte.Parse(txtUnitId.Text),
                        ushort.Parse(txtAddress.Text),
                        ushort.Parse(txtCount.Text));
                    
                    txtResult.Text = "线圈状态: " + string.Join(", ", coils);
                }
                else if (_rtuClient != null)
                {
                    var coils = await _rtuClient.ReadCoilsAsync(
                        byte.Parse(txtUnitId.Text),
                        ushort.Parse(txtAddress.Text),
                        ushort.Parse(txtCount.Text));
                    
                    txtResult.Text = "线圈状态: " + string.Join(", ", coils);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"读取失败: {ex.Message}");
            }
        }

        // 其他操作按钮事件处理...

        private Parity GetParity(string parityText)
        {
            switch (parityText.ToLower())
            {
                case "even": return Parity.Even;
                case "odd": return Parity.Odd;
                case "mark": return Parity.Mark;
                case "space": return Parity.Space;
                default: return Parity.None;
            }
        }

        private StopBits GetStopBits(string stopBitsText)
        {
            switch (stopBitsText)
            {
                case "1.5": return StopBits.OnePointFive;
                case "2": return StopBits.Two;
                default: return StopBits.One;
            }
        }
    }
}

功能特点

  1. 完整协议支持

    • 支持所有标准Modbus功能码

    • 实现TCP和RTU两种传输模式

    • 支持主站(客户端)和从站(服务器)功能

  2. 健壮的错误处理

    • 详细的异常类型

    • CRC校验和错误检测

    • 事务ID验证

  3. 易用API设计

    • 异步操作支持

    • 简单的读写方法

    • 清晰的参数命名

  4. 跨平台支持

参考代码 C#实现Modbus协议 www.youwenfan.com/contentcss/111839.html

使用示例

TCP客户端示例

csharp 复制代码
var client = new ModbusTcpClient("127.0.0.1", 502);
await client.ConnectAsync();

// 读取保持寄存器
ushort[] registers = await client.ReadHoldingRegistersAsync(1, 0, 10);

// 写入单个线圈
await client.WriteSingleCoilAsync(1, 5, true);

// 写入多个寄存器
await client.WriteMultipleRegistersAsync(1, 10, new ushort[] { 100, 200, 300 });

await client.DisconnectAsync();

RTU客户端示例

csharp 复制代码
var client = new ModbusRtuClient("COM3", 19200, Parity.Even);
client.Connect();

// 读取输入寄存器
ushort[] inputs = await client.ReadInputRegistersAsync(1, 0, 5);

// 写入多个线圈
await client.WriteMultipleCoilsAsync(1, 0, new bool[] { true, false, true, true, false });

client.Disconnect();

扩展建议

  1. 从站实现

    • 实现ModbusTcpServer和ModbusRtuServer

    • 添加寄存器映射和回调处理

  2. 性能优化

    • 添加连接池管理

    • 实现批量读写操作

    • 支持异步流处理

  3. 安全增强

    • 添加TLS加密支持(TCP)

    • 实现访问控制机制

    • 添加数据验证和过滤

  4. 诊断工具

    • 添加通信日志记录

    • 实现流量监控

    • 添加错误统计和报告

相关推荐
紫郢剑侠1 小时前
【C语言编程gcc@Kylin | 麒麟 】5:获取系统启动时间
c语言·开发语言·kylin·gcc·麒麟操作系统
晓晓hh2 小时前
JavaSe学习——基础
java·开发语言·学习
清水白石0082 小时前
Python 内存陷阱深度解析——浅拷贝、深拷贝与对象复制的正确姿势
开发语言·python
phltxy2 小时前
算法刷题|模拟思想高频题全解(Java版)
java·开发语言·算法
愚者游世2 小时前
template学习大纲
开发语言·c++·程序人生·面试·visual studio
阿里嘎多学长2 小时前
2026-03-11 GitHub 热点项目精选
开发语言·程序员·github·代码托管
宵时待雨2 小时前
C++笔记归纳10:继承
开发语言·数据结构·c++·笔记·算法
csbysj20202 小时前
TypeScript String
开发语言
bugcome_com2 小时前
C# 泛型(Generic)完全指南:从基础到高级应用
c#