“connection refused”通常因设备未启用modbus tcp服务或502端口被防火墙拦截;需用telnet/test-netconnection验证端口可达,确认设备手册中modbus tcp是否需手动开启,并用wireshark或qmodmaster进一步排查。

Modbus TCP 客户端怎么连上设备就报“Connection refused”
绝大多数情况不是代码写错了,而是设备没开 Modbus TCP 服务,或者防火墙/网络策略拦了 502 端口。别急着查 ModbusClient.Connect() 的返回值——它只管 TCP 连接成功与否,不验证 Modbus 协议层是否就绪。
实操建议:
- 先用
telnet 192.168.1.100 502或Test-NetConnection 192.168.1.100 -Port 502(PowerShell)确认端口可达 - 检查设备手册,确认 Modbus TCP 功能是否需手动启用(比如某些 PLC 默认只开 Modbus RTU over RS485)
- Wireshark 抓包看有没有
TCP SYN响应;若无响应,问题在网路层;若有但后续没MBAP头,说明设备没启动 Modbus TCP 栈 - 用开源工具如
QModMaster或Simply Modbus先连通,排除硬件和配置问题
读保持寄存器时 ReadHoldingRegisters(0, 10) 返回全 0 或抛异常
常见原因是地址偏移理解错:Modbus 协议里“寄存器地址”是 1-based(如文档写“40001”),而多数 C# 库(如 NModbus、LibModbus.NET)的 ReadHoldingRegisters 参数是 0-based 起始地址。传 0 对应的是 40001,传 1 才是 40002——这点极易踩坑。
实操建议:
- 明确设备文档中地址格式:若标“40001~40010”,则调用时用
ReadHoldingRegisters(0, 10);若标“Address 100”,则用ReadHoldingRegisters(99, n) - 检查从站 ID(slave ID)是否匹配设备设置,默认常为
1,但有些网关或仿真器要求设为255或其他值 - 注意超时设置:
client.Transport.Retries = 2和client.Transport.ReadTimeout = 1500很关键,工业现场延迟波动大,缺省 1s 常不够 - 返回全 0 也可能是设备未供电或寄存器未初始化,用设备配套软件确认该地址有真实数据
为什么多线程并发读多个地址会丢数据或报“Invalid response frame”
Modbus TCP 本身无会话状态,但多数 C# 实现(尤其是 NModbus 旧版)的 ModbusIpMaster 实例**不是线程安全的**。多个线程共用一个 client 实例发请求,TCP 流会混帧,导致解析失败。
实操建议:
- 每个线程/任务使用独立的
ModbusIpMaster实例(复用TcpClient不划算,不如每次新建) - 更稳妥做法是用同步队列 + 单 worker 线程轮询,避免并发竞争——尤其当你要读几十个地址且频率高时
- 若坚持并发,确保库版本 ≥ 3.0.77(
NModbus后续版本加了lock保护内部事务 ID 和缓冲区,但仍有风险) - 抓包看是否出现两个请求的 MBAP Transaction ID 相同,这是典型非线程安全表现
WriteSingleRegister 成功但设备没反应
成功只代表 Modbus 帧被设备接收并返回了正常响应(功能码 0x06),不代表写入生效。很多设备要求写入前先停机、或需配合特定控制字、或写入地址本身是只读的“伪寄存器”。
实操建议:
- 确认目标地址是否支持写操作——查设备寄存器映射表,带“R/W”标识的才可写,“R”或“-”表示只读
- 有些设备写控制位(如启停)需要“上升沿触发”,即先写 0 再写 1,或写一个脉冲值(如 100),而非持续写 1
- 检查写入值范围:比如写频率寄存器,单位可能是 0.01Hz,传
5000才是 50Hz,传50就是 0.5Hz - 用 Wireshark 对比正常设备软件写的帧和你代码写的帧,重点比对功能码、地址、值字节序(big-endian vs little-endian)
Modbus 看似简单,真正难的是和具体设备“对齐语义”:地址偏移、字节序、使能条件、响应延时、重试逻辑——这些没法靠通用库自动适配,得一条条对着设备手册抠。










