勇哥注:
modbus通讯协议本身是可靠的,不然也不会发明起至今快50年了,还在自动化行业中广泛使用。
然而当应用各种第三方modbus组件编程的时候,由于组件的实现者、组件的应用者没有遵循标准来实现和应用,
就会造成各种通讯异常。
这就有点像一台pc机,硬件厂商能保证自己的硬件没有问题,然而架不住使用者乱装软件,最终还是可能会死机蓝屏。
使用modbus组件的使用者想怎么用就怎么用,没有章法,这是此问题的原因。
有关ModbusTCP的介绍及使用,见勇哥下面的教程:
Modbus Poll和Modbus slave软件应用(二)
Modbus Poll和Modbus slave软件应用(一)
ModbusTCP是主从模式的通讯架构,即Master/Slave。
game4automation实现了Slave从站。
Slave从站会公布自己的ip及端口号,若干个Master会连接它。
下图显示的是game4automation的Modubs组件参数面板。
可以看到没有任何设置。
原因是它的缺省值就是:ip为本机,可以是127.0.0.1,或者是本机的地址。
端口号为502。
端口号不可以改变。
master端通常由C#端使用某种ModbusTCP通讯组件来进行编程。
请使用github上开源的并且使用量较大的组件,例如EasyModbus。
开源很重要,不然通讯失败你都不知道是什么原因。
另外,不要牛B哄哄总想着自己去写,这里面水很深,不是一件容易的事。
勇哥重点说一下Master端访问要注意的内容:
(1)modbus的四个存储区(1.输入继电器、2.可读可写继电器、3.输入寄存器、4.可读可写寄存器),
请在程序里使用6个通信通道进行通讯(存储区1一个,2两个,3一个,4两个)。
原因是你一定会在多线程进通讯操作,用一个通道进行多个区的并发操作,死定了。
(2)对某通道信道操作的时候,你最好设计成队列方式,然后这个队列统一交给一个线程逐条处理这些通信请求
这是因为任何一次通道操作,都是有交互握手的过程,如果这个过程没完成,你再来一个,就会报下面这样的错误。
它实际上就是通讯阻塞了。
具体来说,就是两种写入的通道(继电器写入,寄存器写入)应该以这种队列来完成。
然后队列的追加操作内容的函数你应该把它Lock起来,这样就让调用的线程间进行了排队,有了先后次序。
(3)对于读的通道,设计一个线程专门定时读。
读的内容是4个通道:输入继电器、输入寄存器、可读可写继电器、可读可写寄存器。
读到的内容进行缓存。
任何读通道数据的api都设计成从缓存内容中获取数据。
(4)4个区的6个通道是可以并发进行操作的,这个没有问题。
因此读通道与写通道是可以并发的,不用互锁。
(5)以上设计中,由于读的线程的周期不固定,并且读线程和应用缓存数据的逻辑不在同一个周期内,因此要特别留意死等模式的逻辑判定。
这种模式是在一个死循环方式读一组缓存数据是否满足条件,如果满足就跳出,否则继续循环。
因为读线程和应用缓存数据的逻辑不在同一个周期内的原因,有可能对于“动作很快”的情况下,死循环里只有一次机会读到缓存数据才会退出,然而不幸的是读和应用者的周期不同,错过了瞬间的正确值后,就再也读不到退出条件的值了。
然后结果就是死循环成立,永远退不出去
然后由于这个死循环位于一个函数内,调用者每次调用都会卡在这个死循环中,函数其实是在反复重入而不能正常退出的,所以时间一长就会有内存与堆栈方面的问题。
另外还会造成一个现场,就是主逻辑卡死,不能继续。
因为这种原因,你得确保你的循环跳出条件是有机会多个执行周期内总有机会命中。
像那种起步时两个状态,停止时两个状态,它们是交替的,这一种逻辑判断往往是不可靠的,因为一但速度快了后,这种状态就是瞬间的过程,错过了就没有了。
总结一下:
多个通道之间可以并发,单个通道间的任何读写,只能一个完成后,再进行下一条指令。如果想做到这一点,可以考虑用队列方式来进行。
2025.6.7勇哥注:
引入ai的一个问答,很好的补充一下上面的内容。
modbus tcp中,4区(保持继电器)它应该是可读可写的是吧? 我的C#程序用的是easyModbus开源组件。然后我这边是Master端。 我一个线程写入它的地址:0-9, 然后另一个线程读取它的地址10-20 这个时候会有问题吗?还是说我的写与读必需交替而不能同时?
在 Modbus TCP 协议中处理保持寄存器(4区)的读写操作,结合使用 EasyModbus 开源组件及 C# 实现主站(Master)时,关键结论如下:
保持寄存器特性
4区(保持寄存器)是可读可写的存储区,符合 Modbus 标准规范45。并发读写操作可行性
当写入地址范围(0-9)与读取地址范围(10-20)完全无重叠时,理论上可安全并发操作,原因如下:地址独立:操作的目标寄存器物理地址互不冲突,无数据竞争风险78。
协议支持:Modbus TCP 允许主站发起独立读写请求,服务器端(如 PLC)能正确处理非重叠区域的并发访问23。
线程安全要求
尽管协议和地址无冲突,仍需显式同步线程,原因在于:库实现限制:EasyModbus 的
ModbusClient
非线程安全类,多线程共享同一实例可能导致内部状态(如 Socket 连接、请求队列)紊乱78。资源竞争:未同步的并发调用可能引发
IOException
或数据包错乱(如写操作中断读响应解析)7。推荐的同步方案
建议采用以下任一方式确保稳定性:csharpCopy Codeprivate readonly object _modbusLock = new object(); // 声明锁对象// 写线程示例void WriteThread(){ lock (_modbusLock) { modbusClient.WriteMultipleRegisters(0, writeValues); // 写入0-9 } }// 读线程示例void ReadThread(){ lock (_modbusLock) { int[] values = modbusClient.ReadHoldingRegisters(10, 10); // 读取10-20 } }
此举确保同一时间仅一个线程访问
ModbusClient
,规避库内部竞争78。
总结建议
✅ 可读写性:4区保持寄存器支持读写操作45。
✅ 并发可行性:读写非重叠地址(0-9写 vs 10-20读)协议层面无冲突23。
⚠️ 强制同步:必须通过锁机制(如
lock
)保护ModbusClient
实例,解决库的线程安全问题78。➕ 优化建议:若频繁操作,可合并读写请求(如批量写后读),减少锁竞争提升效率8。
注:交替读写(如先写后读同一连接)非必需,重点在于地址隔离 + 实例同步。

