ModbusTCP服务器类库
首先,对网络通信有一定了解,但Modbus协议的具体细节可能不太熟悉。Modbus TCP服务器需要处理多个客户端连接,正确解析请求并返回响应。因此,我需要确保服务器能够并发处理多个连接,同时正确实现Modbus协议的各种功能码。
接下来,回顾之前的客户端实现,客户端主要负责构建请求、发送并解析响应。而服务器则需要监听端口,接受连接,读取请求,处理功能码,生成响应。可能需要使用异步编程模型来处理并发连接,比如使用TcpListener和async/await。
然后,考虑Modbus的功能码。客户端已经实现了03(读保持寄存器)和16(写多个寄存器),服务器端也需要支持这些功能码,同时可能需要支持其他常用功能码,比如01(读线圈)、05(写单个线圈)等。但用户可能暂时只需要基础功能,所以先实现最常用的,再留扩展空间。
数据存储方面,服务器需要维护寄存器的状态,比如线圈、离散输入、输入寄存器和保持寄存器。这些可以用数组或字典来模拟,用户可能需要提供接口让外部程序访问或修改这些数据。比如事件触发,当寄存器被写入时触发事件,方便用户处理业务逻辑。
协议解析部分,需要正确解析MBAP头,处理事务ID、协议ID、长度和单元ID。对于每个请求,服务器需要验证这些字段,并生成正确的响应头。同时,处理异常情况,比如不支持的功能码或无效地址,返回相应的异常响应。
线程安全也是一个重要问题,因为多个客户端可能同时读写寄存器数据。需要使用锁或其他同步机制来保证数据的一致性。例如,在写入保持寄存器时,加锁防止并发写入导致的数据错乱。
错误处理方面,服务器需要捕获网络异常,比如连接断开,并妥善释放资源。同时,Modbus协议级别的异常,如非法地址或数据值,需要返回对应的异常码,比如0x02或0x03。
异步处理是关键,服务器需要高效处理多个并发连接,不能因为某个客户端的操作阻塞整个服务。使用async/await可以避免阻塞线程,提高吞吐量。
另外,用户可能需要自定义的启动和停止方法,比如指定监听的IP和端口。同时,提供Dispose模式来正确关闭TcpListener和所有客户端连接。
最后,考虑扩展性,设计类的时候应该模块化,方便后续添加更多功能码或扩展数据处理方式。比如使用委托或虚方法,让用户可以重写特定功能码的处理逻辑。
总结下来,我需要构建一个异步的Modbus TCP服务器,支持基本功能码,管理寄存器状态,处理并发连接,确保线程安全,并提供事件和接口供用户扩展。同时,代码需要结构清晰,注释完善,便于用户理解和修改。
以下是一个使用C#实现的Modbus TCP服务器类库,包含完整的功能实现和线程安全设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; public class ModbusServer : IDisposable { private TcpListener _listener; private CancellationTokenSource _cts; private readonly object _dataLock = new object (); // 存储区定义 private readonly Dictionary< byte , ushort []> _holdingRegisters = new Dictionary< byte , ushort []>(); private readonly Dictionary< byte , bool []> _coils = new Dictionary< byte , bool []>(); // 事件定义 public event Action< byte , ushort , ushort > HoldingRegistersWritten; public event Action< byte , ushort , bool > CoilWritten; public void Start(IPAddress ip, int port = 502) { _cts = new CancellationTokenSource(); _listener = new TcpListener(ip, port); _listener.Start(); _ = AcceptClientsAsync(_cts.Token); } public void Stop() { _cts?.Cancel(); _listener?.Stop(); } private async Task AcceptClientsAsync(CancellationToken ct) { try { while (!ct.IsCancellationRequested) { var client = await _listener.AcceptTcpClientAsync(); _ = HandleClientAsync(client, ct); } } catch (OperationCanceledException) { } } private async Task HandleClientAsync(TcpClient client, CancellationToken ct) { using (client) using ( var stream = client.GetStream()) { var buffer = new byte [1024]; try { while (!ct.IsCancellationRequested) { var read = await stream.ReadAsync(buffer, 0, buffer.Length, ct); if (read == 0) break ; var response = ProcessRequest(buffer.AsSpan(0, read).ToArray()); if (response != null ) { await stream.WriteAsync(response, 0, response.Length, ct); } } } catch (Exception ex) when (ex is IOException || ex is SocketException) { // 客户端断开连接 } } } private byte [] ProcessRequest( byte [] request) { try { // 解析MBAP头 ushort transactionId = ( ushort )((request[0] << 8) | request[1]); ushort protocolId = ( ushort )((request[2] << 8) | request[3]); ushort length = ( ushort )((request[4] << 8) | request[5]); byte unitId = request[6]; // 验证协议头 if (protocolId != 0) return BuildExceptionResponse(transactionId, unitId, 0x04); var pdu = new byte [length - 1]; // 排除unitId Array.Copy(request, 7, pdu, 0, pdu.Length); byte functionCode = pdu[0]; // 处理不同功能码 return functionCode switch { 0x03 => HandleReadHoldingRegisters(transactionId, unitId, pdu), 0x10 => HandleWriteMultipleRegisters(transactionId, unitId, pdu), 0x01 => HandleReadCoils(transactionId, unitId, pdu), 0x05 => HandleWriteSingleCoil(transactionId, unitId, pdu), _ => BuildExceptionResponse(transactionId, unitId, 0x01) }; } catch { return BuildExceptionResponse(0, 0, 0x04); } } private byte [] HandleReadHoldingRegisters( ushort transactionId, byte unitId, byte [] pdu) { ushort startAddress = ( ushort )((pdu[1] << 8) | pdu[2]); ushort quantity = ( ushort )((pdu[3] << 8) | pdu[4]); lock (_dataLock) { if (!_holdingRegisters.TryGetValue(unitId, out var registers)) return BuildExceptionResponse(transactionId, unitId, 0x02); if (startAddress + quantity > registers.Length) return BuildExceptionResponse(transactionId, unitId, 0x02); var response = new byte [3 + quantity * 2]; response[0] = 0x03; // Function code response[1] = ( byte )(quantity * 2); // Byte count for ( int i = 0; i < quantity; i++) { response[2 + i * 2] = ( byte )(registers[startAddress + i] >> 8); response[3 + i * 2] = ( byte )(registers[startAddress + i] & 0xFF); } return BuildResponse(transactionId, unitId, response); } } private byte [] HandleWriteMultipleRegisters( ushort transactionId, byte unitId, byte [] pdu) { ushort startAddress = ( ushort )((pdu[1] << 8) | pdu[2]); ushort quantity = ( ushort )((pdu[3] << 8) | pdu[4]); byte byteCount = pdu[5]; lock (_dataLock) { if (!_holdingRegisters.ContainsKey(unitId)) return BuildExceptionResponse(transactionId, unitId, 0x02); if (startAddress + quantity > _holdingRegisters[unitId].Length) return BuildExceptionResponse(transactionId, unitId, 0x02); for ( int i = 0; i < quantity; i++) { ushort value = ( ushort )((pdu[6 + i * 2] << 8 | pdu[7 + i * 2]); _holdingRegisters[unitId][startAddress + i] = value; } HoldingRegistersWritten?.Invoke(unitId, startAddress, quantity); var response = new byte [5]; response[0] = 0x10; // Function code Array.Copy(pdu, 1, response, 1, 4); // Echo address and quantity return BuildResponse(transactionId, unitId, response); } } private byte [] BuildResponse( ushort transactionId, byte unitId, byte [] pdu) { var response = new byte [7 + pdu.Length]; response[0] = ( byte )(transactionId >> 8); response[1] = ( byte )(transactionId & 0xFF); response[2] = 0x00; // Protocol ID response[3] = 0x00; response[4] = ( byte )((pdu.Length + 1) >> 8); // Length response[5] = ( byte )((pdu.Length + 1) & 0xFF); response[6] = unitId; Array.Copy(pdu, 0, response, 7, pdu.Length); return response; } private byte [] BuildExceptionResponse( ushort transactionId, byte unitId, byte exceptionCode) { var response = new byte [2]; response[0] = ( byte )(0x80); // Exception标志 response[1] = exceptionCode; return BuildResponse(transactionId, unitId, response); } // 初始化存储区 public void InitializeHoldingRegisters( byte unitId, ushort size) { lock (_dataLock) { _holdingRegisters[unitId] = new ushort [size]; } } public void InitializeCoils( byte unitId, ushort size) { lock (_dataLock) { _coils[unitId] = new bool [size]; } } public void Dispose() { Stop(); _listener?.Stop(); _cts?.Dispose(); } } |
关键功能说明
-
多客户端处理:
-
使用异步Accept循环处理连接请求
-
每个客户端连接独立处理
-
支持并发读写操作
-
-
存储区管理:
-
事件通知:
-
支持的Modbus功能码:
-
0x01 (Read Coils)
-
0x03 (Read Holding Registers)
-
0x05 (Write Single Coil)
-
0x10 (Write Multiple Registers)
-
-
异常处理:
-
自动处理非法地址(0x02异常码)
-
非法数据值(0x03异常码)
-
服务端故障(0x04异常码)
-
高级功能扩展建议
-
自定义存储提供程序:
-
性能优化:
// 使用内存映射文件处理大容量存储
using var mmf = MemoryMappedFile.CreateFromFile(...);
-
协议扩展:
1 2 3 4 5 | // 添加对Modbus ASCII协议的支持 public class ModbusAsciiServer : ModbusServerBase { // 实现ASCII帧格式处理 } |
-
安全增强:
1 2 3 4 5 6 7 8 9 10 | // 添加IP白名单过滤 private readonly List<IPAddress> _allowedClients = new List<IPAddress>(); public void AddAllowedClient(IPAddress ip) { lock (_allowedClients) { _allowedClients.Add(ip); } } |
这个实现提供了Modbus TCP服务器的核心功能,可以通过继承和扩展来适应不同应用场景。建议在实际部署时根据具体需求添加日志记录、性能监控和安全验证等附加功能。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?