Modbus如何读取非连续地址?基于EasyModbus的批量高效读取咨询
非连续Modbus地址批量读取优化方案
核心思路
Modbus单请求的开销远大于读取少量冗余地址的开销,所以优先把分散的非连续地址聚合成尽可能少的连续地址块,减少请求总次数,再从返回结果中提取目标地址的值即可。
具体实现步骤
1. 非连续地址分组聚合
- 先把所有需要读取的地址去重、从小到大排序
- 遍历排序后的地址,将地址间隔小于预设阈值(推荐设置为3~8,可根据实际场景调整)的地址归为同一连续块,同时保证每个块的总长度不超过125个保持寄存器(符合Modbus TCP单帧最大260字节的限制)
- 如果两个目标地址间隔过大(超过阈值),单独拆分为新的分组,避免读取过多无用地址浪费带宽
举个实际场景的例子:目标地址为2,5,7,20,21,40,阈值设为5,那么会聚合为三个分组:[2-7](长度6)、[20-21](长度2)、[40-40](长度1),总请求次数从6次降到3次,效率提升非常明显。
2. 批量读取后提取目标值
每个连续块读取完成后,根据目标地址和块起始地址的偏移量,从返回的数组中提取对应的值即可,无用的冗余地址直接丢弃。
3. 可选优化:并行请求
如果Modbus服务器支持并发连接/并发请求,可以把所有聚合后的分组用异步并行的方式发起请求,进一步降低总耗时。
基于EasyModbus的代码示例
using EasyModbus; using System.Collections.Generic; using System.Linq; // 地址分组类 public class AddressBlock { public int StartAddress { get; set; } public int Length { get; set; } public List<int> TargetAddresses { get; set; } = new List<int>(); } public class ModbusReader { // 批量读取非连续保持寄存器 public Dictionary<int, int> ReadDiscreteHoldingRegisters(string ip, int port, List<int> targetAddresses, int gapThreshold = 5) { var result = new Dictionary<int, int>(); if (targetAddresses == null || !targetAddresses.Any()) return result; // 1. 地址去重排序 var sortedAddresses = targetAddresses.Distinct().OrderBy(x => x).ToList(); var addressBlocks = new List<AddressBlock>(); var currentBlock = new AddressBlock { StartAddress = sortedAddresses[0], TargetAddresses = { sortedAddresses[0] }, Length = 1 }; // 2. 聚合分组 foreach (var addr in sortedAddresses.Skip(1)) { var currentEnd = currentBlock.StartAddress + currentBlock.Length - 1; // 满足两个条件:间隔小于阈值,且总长度不超过125 if (addr - currentEnd <= gapThreshold && (addr - currentBlock.StartAddress + 1) <= 125) { currentBlock.Length = addr - currentBlock.StartAddress + 1; currentBlock.TargetAddresses.Add(addr); } else { addressBlocks.Add(currentBlock); currentBlock = new AddressBlock { StartAddress = addr, TargetAddresses = { addr }, Length = 1 }; } } addressBlocks.Add(currentBlock); // 3. 批量读取 var modbusClient = new ModbusClient(ip, port); modbusClient.Connect(); foreach (var block in addressBlocks) { var blockValues = modbusClient.ReadHoldingRegisters(block.StartAddress, block.Length); // 提取目标地址的值 foreach (var targetAddr in block.TargetAddresses) { var offset = targetAddr - block.StartAddress; result[targetAddr] = blockValues[offset]; } } modbusClient.Disconnect(); return result; } // 使用示例 static void Main(string[] args) { var reader = new ModbusReader(); // 要读的非连续地址示例 var targetAddrs = new List<int> { 2,5,7,20,21,40,100,102,105 }; var result = reader.ReadDiscreteHoldingRegisters("127.0.0.1", 502, targetAddrs); // 输出结果 foreach (var kv in result) { Console.WriteLine($"地址{kv.Key}的值:{kv.Value}"); } Console.Write("Press any key to continue . . . "); Console.ReadKey(true); } }
注意事项
- 阈值可以根据实际场景调整:如果地址普遍非常分散,调小阈值减少冗余读取;如果地址普遍间隔小,调大阈值减少请求次数
- 如果服务器对请求频率有限制,不要用太高的并行度,避免被服务器拒绝请求
- 可以增加失败重试逻辑,单个块读取失败时可以先重试,重试失败再拆分为更小的块或者单个地址读取,提升稳定性
内容的提问来源于stack exchange,提问作者Wihelm jie
相关产品推荐
相关产品推荐

