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();
    }
}

  

关键功能说明

  1. 多客户端处理

    • 使用异步Accept循环处理连接请求

    • 每个客户端连接独立处理

    • 支持并发读写操作

  2. 存储区管理

    csharp
    1
    2
    3
    4
    5
    // 初始化保持寄存器(40001-49999)
    server.InitializeHoldingRegisters(unitId: 1, size: 9999);
     
    // 初始化线圈(00001-09999)
    server.InitializeCoils(unitId: 1, size: 9999);

      

  3. 事件通知

    csharp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 注册保持寄存器写入事件
    server.HoldingRegistersWritten += (unitId, startAddress, quantity) =>
    {
        Console.WriteLine($"Unit {unitId} registers {startAddress}-{startAddress+quantity} modified");
    };
     
    // 注册线圈写入事件
    server.CoilWritten += (unitId, address, value) =>
    {
        Console.WriteLine($"Unit {unitId} coil {address} set to {value}");
    };

      

  4. 支持的Modbus功能码

    • 0x01 (Read Coils)

    • 0x03 (Read Holding Registers)

    • 0x05 (Write Single Coil)

    • 0x10 (Write Multiple Registers)

  5. 异常处理

    • 自动处理非法地址(0x02异常码)

    • 非法数据值(0x03异常码)

    • 服务端故障(0x04异常码)

高级功能扩展建议

  1. 自定义存储提供程序

csharp
1
2
3
4
5
6
7
8
9
public interface IModbusStorage
{
    ushort[] GetHoldingRegisters(byte unitId);
    bool[] GetCoils(byte unitId);
    void UpdateRegisters(byte unitId, ushort startAddress, ushort[] values);
    void UpdateCoils(byte unitId, ushort startAddress, bool[] values);
}
 
public class DatabaseStorage : IModbusStorage { ... }

  

  1. 性能优化

csharp
复制
// 使用内存映射文件处理大容量存储
using var mmf = MemoryMappedFile.CreateFromFile(...);
  1. 协议扩展

csharp
1
2
3
4
5
// 添加对Modbus ASCII协议的支持
public class ModbusAsciiServer : ModbusServerBase
{
    // 实现ASCII帧格式处理
}

  

  1. 安全增强

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

 

 

 

 

posted @   funiyi816  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示