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服务器类库,包含完整的功能实现和线程安全设计:
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(...);
-
协议扩展:
// 添加对Modbus ASCII协议的支持 public class ModbusAsciiServer : ModbusServerBase { // 实现ASCII帧格式处理 }
-
安全增强:
// 添加IP白名单过滤 private readonly List<IPAddress> _allowedClients = new List<IPAddress>(); public void AddAllowedClient(IPAddress ip) { lock (_allowedClients) { _allowedClients.Add(ip); } }
这个实现提供了Modbus TCP服务器的核心功能,可以通过继承和扩展来适应不同应用场景。建议在实际部署时根据具体需求添加日志记录、性能监控和安全验证等附加功能。