资讯动态

Modbus TCP通讯测试实战:从协议解析到稳定采集的完整指南

发布时间:2026/10/2 7:31:44 来源:尧图企业网站定制
简介这份资源是汇川AM402 PLC与C# Winform程序通过Modbus TCP通信的完整测试工程面向工控上位机开发初学者及需要对接汇川PLC的C#工程师帮助解决协议封装、Socket连接与数据读写等实际问题。压缩包共47个文件、约922KB以15个cs源码文件为核心配合sln与csproj工程文件、resx与resources界面资源、config配置及exe可执行程序另含pdb调试符号与suo等IDE状态文件结构完整可直接用Visual Studio打开运行。内容覆盖Modbus TCP功能码构造与解析、非阻塞Socket连接、异常重连与数据解析显示等关键环节并附带AM402_Test1 PLC程序便于对照调试。目前已有2254人学习下载适合作为上位机与PLC联调的参考范例快速理解通信流程与排错思路。1. 从 InproModebusTCP通讯测试.rar 说起一个压缩包背后要跑通的三件事如果你手里拿到一个叫InproModebusTCP通讯测试.rar的包第一反应大概率是「解压看看里面是什么」。但真正要把它跑起来你面对的其实是三件独立的事Modbus TCP 协议本身怎么走、上位机怎么当客户端去读写寄存器、以及现场设备PLC、仪表、网关的 IP 和端口怎么对上。这三件事任何一件没弄明白测试就会卡在「连不上」或者「连上了读回来全是 0」。Modbus TCP 是工业现场最常见的一种以太网通讯方式它把传统的 Modbus RTU 帧塞进 TCP 报文里默认端口 502走的是标准的 TCP 三次握手建立连接然后一问一答。所谓「通讯测试」本质就是验证你的客户端能不能正确发出功能码请求、设备能不能按协议返回数据。这个方向适合做自动化、工控上位机、设备集成的工程师也适合刚接触 Modbus 想找个能跑的样例练手的人。下面我按「协议先立住 → 环境搭起来 → 代码跑通 → 坑排掉」的顺序把这类测试包该怎么用讲清楚。2. Modbus TCP 协议先立住报文结构、功能码和端口那些事在动手解压之前先把协议本身搞清楚否则后面抓包看到一堆十六进制你也不知道哪段是哪段。Modbus TCP 的报文结构和 RTU 最大的区别是去掉了 CRC 校验换成了一个 7 字节的 MBAP 头Modbus Application Protocol Header后面才是真正的 PDU。2.1 MBAP 头 7 个字节分别是什么MBAP 头是理解 Modbus TCP 的钥匙它固定 7 个字节结构如下字段长度说明典型值事务标识符 Transaction ID2 字节客户端自增用来匹配请求和响应0x0001协议标识符 Protocol ID2 字节Modbus 固定为 00x0000长度 Length2 字节后面还有多少字节单元标识PDU0x0006单元标识 Unit ID1 字节从站地址TCP 下常填 1 或 0xFF0x01后面紧跟的就是 PDU第一个字节是功能码比如 0x03 读保持寄存器、0x04 读输入寄存器、0x06 写单个寄存器、0x10 写多个寄存器。举个例子读从站 1 的保持寄存器起始地址 0读 2 个寄存器完整报文是00 01 00 00 00 06 01 03 00 00 00 02 |事务ID|协议ID|长度 |单元|功能码|起始地址|数量|响应会带回同样的 Transaction ID功能码后面跟一个字节的字节数然后是数据。搞懂这个结构你抓包的时候就能一眼看出请求和响应是否配对。2.2 功能码和寄存器地址的对应关系新手最容易翻车的地方是地址。Modbus 有四种数据区每种对应不同的功能码和地址范围线圈 Coil可读可写功能码 01/05/15地址从 0 开始离散输入 Discrete Input只读功能码 02保持寄存器 Holding Register可读可写功能码 03/06/16地址从 0 开始输入寄存器 Input Register只读功能码 04注意一个高频坑很多设备手册上写的是「40001」这种 5 位地址而协议里实际发的是 0。40001 对应保持寄存器偏移 040002 对应偏移 1以此类推。你在代码里填地址时要么填 0要么填 40001 让库去转换取决于你用的库怎么定义。这个不统一是 Modbus 测试里最常见的「读回来全是 0」的原因之一。2.3 为什么默认端口是 502能不能改Modbus TCP 的官方分配端口是 502这是 IANA 注册的。但实际项目里很多网关和串口服务器会把 Modbus TCP 映射到别的端口比如 5020、8502甚至 10002。所以拿到一个测试包第一件事是确认目标设备的端口号而不是无脑填 502。端口填错的表现是 TCP 连接直接被拒绝Connection refused和协议层没关系用 telnet 或 nc 一试就知道。提示用telnet 192.168.1.10 502能连上说明 TCP 层通了连不上就是 IP 或端口问题先别怀疑协议。3. 把测试环境搭起来从解压到第一个能跑的客户端协议清楚之后就可以动手了。InproModebusTCP通讯测试.rar这类包通常包含一个上位机测试程序、可能的源码、以及一份说明文档。不管里面具体是什么你要跑通的核心是「一个能发 Modbus TCP 请求的客户端」加「一个能响应的服务端或真实设备」。3.1 解压后先看什么别急着双击 exe解压之后先别急着运行可执行文件。按这个顺序看看目录结构找readme、说明、doc之类的文件里面通常写了默认 IP、端口、从站地址找配置文件比如.ini、.json、.xml里面往往有连接参数如果有源码先看主程序里连接部分的代码确认用的是哪个库最后再运行程序用默认参数试连如果包里只有 exe 没有源码那你就把它当成一个黑匣子客户端重点放在「怎么配参数让它连上我的设备」上。如果带源码那更好可以直接改参数重新编译。3.2 没有真实设备时用 Python 起一个 Modbus TCP 服务端大多数时候你手边没有 PLC这时候可以自己起一个模拟服务端。用 Python 的pymodbus库几行就能搞定# 安装pip install pymodbus from pymodbus.server import StartTcpServer from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext # 初始化数据区从地址0开始给100个寄存器初值都设为0 store ModbusSlaveContext( hrModbusSequentialDataBlock(0, [0] * 100), # 保持寄存器 irModbusSequentialDataBlock(0, [0] * 100), # 输入寄存器 coModbusSequentialDataBlock(0, [0] * 100), # 线圈 diModbusSequentialDataBlock(0, [0] * 100), # 离散输入 ) # unit1 表示从站地址为1singleFalse 表示可以有多个从站 context ModbusServerContext(slavesstore, singleTrue) # 监听所有网卡的502端口 StartTcpServer(contextcontext, address(0.0.0.0, 502))这段代码的逻辑是先建四个数据块每个块从地址 0 开始给 100 个位置全部初始化为 0然后把这些块打包成一个从站上下文从站地址设为 1最后在 502 端口启动 TCP 服务。跑起来之后你的客户端连127.0.0.1:502、从站地址 1就能读到全 0 的寄存器。想验证写入可以先写再读。参数说明ModbusSequentialDataBlock(0, [0]*100)的第一个参数是起始地址第二个是初始值列表列表长度就是寄存器数量。singleTrue表示所有单元标识都映射到同一个上下文测试够用了。如果端口 502 被占用或没权限Linux 下 1024 以下端口需要 root改成 5020 之类的。3.3 用客户端读一次寄存器确认链路通了服务端起好之后写个最小客户端验证from pymodbus.client import ModbusTcpClient client ModbusTcpClient(127.0.0.1, port502) # 连接服务端 client.connect() # 读保持寄存器从站地址1起始地址0读2个 rr client.read_holding_registers(address0, count2, slave1) if not rr.isError(): print(读取成功:, rr.registers) # 应该输出 [0, 0] else: print(读取失败:, rr) client.close()逻辑很直白建客户端、连、读、判断、关。address0是协议地址不是 40001。count2表示读两个寄存器。slave1是从站地址要和服务端的 unit 对上。如果这里报错先看服务端有没有收到请求再看是不是地址或从站号填错了。注意pymodbus不同大版本 API 有差异2.x 和 3.x 的read_holding_registers参数名可能不同报unexpected keyword argument时先查你装的版本。4. 用真实设备跑通IP、端口、从站号、地址四个参数怎么对模拟服务端跑通只证明你的代码没问题真正难的是连真实设备。现场设备千奇百怪参数对不上就是连不上或者读回垃圾数据。这一章把四个关键参数逐个拆开。4.1 IP 和端口先 ping 再 telnet别跳过拿到设备第一步永远是确认网络层通不通。设备 IP 通常在铭牌上或者用厂商配置软件设。确认步骤把电脑网卡设成和设备同网段比如设备是 192.168.1.10电脑设 192.168.1.100ping 192.168.1.10通了说明 IP 层没问题telnet 192.168.1.10 502通了说明 TCP 端口开着如果 ping 不通检查网线、网段、设备是否上电。如果 ping 通但 telnet 不通说明端口不对或者设备没开 Modbus TCP 服务。这一步能过滤掉一大半「连不上」的问题别一上来就怀疑代码。4.2 从站号和单元标识TCP 下到底填几Modbus TCP 里Unit ID 这个字段在纯 TCP 设备上经常被忽略很多设备不管你填几都响应。但在网关场景下一个网关后面挂多个 RTU 从站这时候 Unit ID 就用来区分具体是哪个从站。常见做法是直连单个 TCP 设备Unit ID 填 1 或 0xFF 都行看设备手册通过网关连 RTU 设备Unit ID 填 RTU 从站的实际地址比如 1、2、3如果读回来报异常码 0x0BGateway Target Device Failed to Respond基本就是从站号填错了网关找不到对应的 RTU 设备。4.3 寄存器地址40001 和 0 的换算别搞混前面提过手册上的 40001 对应协议地址 0。但不同库的处理方式不一样库/工具地址填法说明pymodbus填 0直接用协议地址Modbus Poll可配有选项决定是否偏移部分组态软件填 40001软件内部自动减 1某些 PLC填 1从 1 开始计数所以当你读不到数据时先把地址加 1 或减 1 试试这是血泪经验。另外注意「起始地址」和「数量」是两个参数读 10 个寄存器是从起始地址开始连续读 10 个不是读到地址 10。4.4 数据类型读回来两个寄存器怎么拼成一个浮点数Modbus 寄存器是 16 位的但现场数据可能是 32 位整数或浮点数占两个寄存器。这时候就涉及字节序和字序问题。常见组合有大端字序 大端字节序ABCD小端字序 大端字节序CDAB大端字序 小端字节序BADC小端字序 小端字节序DCBA读回来一个浮点数不对先别怀疑设备把四种组合都试一遍基本能对上。用 Python 转换import struct # 假设读到两个寄存器 regs [0x4148, 0x0000] regs [0x4148, 0x0000] # ABCD高字在前字内大端 val_abcd struct.unpack(f, struct.pack(HH, regs[0], regs[1]))[0] # CDAB低字在前 val_cdab struct.unpack(f, struct.pack(HH, regs[1], regs[0]))[0] print(val_abcd, val_cdab) # 12.5 12.5示例这段代码用struct把两个 16 位整数按不同顺序打包成 4 字节再解成浮点数。实际调试时把四种都打印出来哪个是合理值就用哪个。5. 通讯测试避坑连不上、读回 0、断连的排查清单前面把正常流程走完了这一章专门讲翻车现场。以下 5 条是我在实际项目里反复遇到的每条按「现象 → 原因 → 解决」写。5.1 现象TCP 连接被拒绝程序报 Connection refused原因目标 IP 或端口不对或者设备根本没开 Modbus TCP 服务。也可能是防火墙拦了。解决先用telnet IP 端口确认。连不上就检查设备配置里 Modbus TCP 是否启用、端口是不是 502。Windows 防火墙有时候会拦出站临时关掉测试。如果是网关确认网关的 TCP 服务已开启。5.2 现象连上了但读寄存器返回异常码 0x02Illegal Data Address原因请求的地址超出了设备实际支持的寄存器范围。比如设备只有 100 个保持寄存器你从地址 200 开始读。解决查设备手册确认寄存器地址范围。先用一个已知存在的地址比如手册里明确写的某个参数地址测试通了再扩展。别一上来就读一大片。5.3 现象读回来全是 0但设备明明有数据原因地址偏移搞错了40001 vs 0或者读错了数据区该读输入寄存器却读了保持寄存器或者从站号不对读到了空设备。解决先确认功能码对不对再看地址。把地址 ±1 试一遍。用抓包工具看请求报文里的地址字段和设备手册对照。如果设备支持用厂商自带的调试软件先读一次确认能读到再回来调自己的代码。5.4 现象跑一段时间后连接断开报 Connection reset by peer原因设备侧有连接空闲超时长时间不发请求就被断开或者网络不稳定或者客户端没做重连。解决加心跳定期读一个寄存器保持连接活跃。客户端加自动重连逻辑捕获异常后重新 connect。如果是 lwIP 这类嵌入式协议栈注意 TCP 的 keepalive 配置默认可能很长。import time from pymodbus.client import ModbusTcpClient def read_with_retry(client, address, count, slave, retries3): for i in range(retries): try: rr client.read_holding_registers(addressaddress, countcount, slaveslave) if not rr.isError(): return rr.registers except Exception as e: print(f第{i1}次失败: {e}) client.close() time.sleep(1) client.connect() return None这段重试逻辑在异常时关闭连接、等待、重连再试。retries3是重试次数time.sleep(1)是重试间隔实际项目里可以按需调整。5.5 现象Wireshark 抓到 TCP 三次握手成功但 Modbus 请求发出去没响应原因请求报文格式错误比如 MBAP 头的长度字段填错或者 Transaction ID 没对上导致客户端丢弃响应。也可能是设备只接受特定 Unit ID。解决在 Wireshark 里过滤tcp.port 502看请求报文和响应报文。如果只有请求没有响应检查 MBAP 头。长度字段应该等于「Unit ID PDU」的字节数。用现成的库一般不会错手搓报文才容易出这个问题。确认设备要求的 Unit ID有些设备只认 0xFF 或 1。提示抓包时把请求和响应的 Transaction ID 对比一下对不上就是客户端匹配逻辑有问题不是设备没回。6. 进阶把测试脚本变成能长期跑的采集程序测试跑通只是第一步真正投入项目要的是能稳定跑几天不出问题的采集程序。这里说几个我踩过坑之后固定下来的做法。第一是连接管理。别每次读都新建连接Modbus TCP 建立连接有开销频繁建连断连设备也受不了。用一个长连接配合心跳和重连。心跳不用太频繁10 到 30 秒读一次就行读什么不重要保持连接活跃即可。第二是超时设置。默认超时可能很长设备掉线时程序会卡住。把 socket 超时设短一点比如 3 秒超时就重试。pymodbus 可以在建客户端时传timeout3。第三是数据校验。读回来的值要做范围检查比如温度不可能 60000 度出现明显异常值大概率是字节序或地址错了别直接入库。加一层合理性判断异常时告警而不是写脏数据。第四是日志。每次请求和响应都记下来出问题时能回溯。不用记全部数据记时间、地址、功能码、结果状态就够。日志按天切分别让一个文件无限增长。import logging from logging.handlers import TimedRotatingFileHandler # 按天切分日志保留7天 handler TimedRotatingFileHandler(modbus.log, whenD, backupCount7, encodingutf-8) handler.setFormatter(logging.Formatter(%(asctime)s %(levelname)s %(message)s)) logger logging.getLogger(modbus) logger.addHandler(handler) logger.setLevel(logging.INFO) # 采集循环里记录 logger.info(fread addr0 count2 result{registers})这段日志配置按天切文件保留 7 天编码指定 utf-8 避免中文乱码。采集循环里把关键结果记进去出问题时翻日志比重新抓包快得多。最后说个验证方法拿一个已知值的寄存器比如设备上某个固定参数反复读 1000 次统计成功率和耗时。成功率低于 99% 就说明链路或超时设置有问题别急着上线。我自己习惯是先在实验室跑一晚上第二天看日志有没有断连和异常没问题再上现场。这个习惯帮我省过好几次现场返工。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑