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服务器的核心功能,可以通过继承和扩展来适应不同应用场景。建议在实际部署时根据具体需求添加日志记录、性能监控和安全验证等附加功能。

浙公网安备 33010602011771号