简介:这是一款面向电力自动化与工业通信领域开发者的IEC 104协议专用测试工具,适用于协议调试、设备联调及规约功能验证等场景,特别适合熟悉C#开发或具备电力系统基础知识的工程师与技术爱好者使用。资源包共13个文件,含核心可执行程序(.exe)、界面与通信支持库(.dll)、配置参数模板(.xlsx)、协议配置文件(.xml)、日志记录(.log)及初始化设置(.ini),整体体积仅2.17MB,轻量易部署。已有6659人学习下载,反映出其在协议测试小众工具中的实用认可度。用户可直接运行软件进行IEC 104客户端报文收发、遥测遥信实时解析、遥控指令模拟、SOE事件触发与对时测试;支持在线修改规约参数、系数配置、语音报警提醒,并提供参数导入导出与表格批量赋值功能,显著提升现场调试效率。
1. IEC 104 测试工具:不是“点开就用”的压缩包,而是调试远动通道前必须亲手拆解的黑匣子
你下载了iec104测试工具.zip,双击解压后看到iec104_test.exe、config.ini和几个.dll文件——别急着双击运行。这不是一个图形界面点几下就能验证主站/子站通信的“傻瓜工具”,而是一套面向继电保护工程师、远动调试员和SCADA系统集成商的协议级诊断套件。它真正解决的,是现场最痛的三个问题:IEC 60870-5-104 报文在 TCP 层握手成功但应用层无响应、ASDU 类型 1(单点遥信)反复超时却查不到原因、以及加密隧道(如 TLS 1.2 封装)下报文被截断却无法定位是加密层还是协议层出错。这个工具的核心价值,不在于“测通”,而在于把 IEC 104 协议栈从 TCP 连接、APCI 帧解析、ASDU 编码、可变结构限定词(VSQ)校验到原因码(COT)语义映射,全部暴露为可观察、可干预、可重放的调试节点。如果你刚接手一个新能源场站远动联调项目,或者正在排查某次调度主站下发遥控失败的根因,那么这个 ZIP 包里藏着的,不是软件,而是你手边最硬的“协议显微镜”。
2. 从 ZIP 解压到协议握手:四步完成最小闭环测试环境搭建
2.1 解压与目录结构识别:先看懂文件名背后的协议分层逻辑
解压iec104测试工具.zip后,典型目录结构如下(不同版本略有差异,但核心文件一致):
├── iec104_test.exe # 主程序(32/64位Windows GUI) ├── config.ini # 全局配置(IP、端口、ASDU类型、超时阈值) ├── lib/ # 协议依赖库 │ ├── libiec104.dll # 核心协议栈(含APCI帧构造/解析) │ └── libtls_wrapper.dll # 可选TLS封装模块(仅当启用加密时加载) ├── logs/ # 自动记录原始报文(十六进制+ASCII双视图) ├── scripts/ # 预置测试脚本(Python格式,用于自动化重放) │ ├── test_asdu1.py # 单点遥信触发脚本 │ └── test_cot6.py # 激活确认(COT=6)压力测试 └── templates/ # 报文模板(JSON格式,定义ASDU字段映射) └── asdu_type_45.json # 遥控命令(类型45)字段占位符提示:
libiec104.dll是整个工具的协议心脏。它不依赖 .NET Framework,而是直接调用 Windows Sockets API + 自研 ASN.1 编解码器。这意味着你无需安装任何运行时环境,但必须确保 Windows 系统已启用 TLS 1.2 支持(Win7 SP1+ 默认开启,WinXP 已彻底不支持)。
2.2 配置文件config.ini的关键参数解析:为什么改错一行就收不到报文
config.ini不是简单填 IP 和端口。它控制着协议栈的行为边界。以下是必须手动校准的 5 个参数(其他参数建议保持默认):
| 参数名 | 示例值 | 作用说明 | 修改风险 |
|---|---|---|---|
ServerIP=192.168.1.100 | 主站IP(若本机做子站则填0.0.0.0) | 定义 TCP 连接目标地址 | 填错导致连接拒绝(Connection refused) |
ServerPort=2404 | 标准 IEC 104 端口(非2404需确认主站监听) | TCP 监听/连接端口 | 主站未监听该端口时,工具显示“连接超时”而非“拒绝” |
ASDUType=1 | 测试用 ASDU 类型(1=单点遥信,45=遥控) | 决定生成报文的 ASDU 结构 | 类型不匹配主站预期时,主站静默丢弃(无错误响应) |
TimeoutMS=3000 | 应用层超时(毫秒) | 控制 COT=6(激活确认)等待窗口 | 设过短(<1000ms)导致误判超时;设过长(>10000ms)拖慢调试节奏 |
UseTLS=false | 是否启用 TLS 加密(true/false) | 控制是否加载libtls_wrapper.dll | true 时若主站未配证书或版本不匹配,TCP 握手成功但 TLS 握手失败(日志显示 "SSL handshake failed") |
实操建议:首次测试务必设UseTLS=false,ASDUType=1,TimeoutMS=5000。等基础报文收发稳定后再逐步开启 TLS 或切换 ASDU 类型。
2.3 启动 GUI 并建立 TCP 连接:观察三次握手与 APCI 帧的实时映射
双击iec104_test.exe启动 GUI 后,界面分为三区:
- 左上:连接控制面板(IP/端口输入框 + “Connect”按钮)
- 右上:报文监视窗(实时滚动 HEX+ASCII 报文流)
- 底部:状态栏(显示
TCP: Connected,APCI: Ready,ASDU: Idle等状态)
关键操作步骤:
- 输入主站 IP 和端口(如
192.168.1.100:2404),点击Connect - 观察状态栏:若显示
TCP: Connected→ 成功建立 TCP 连接;若卡在TCP: Connecting...→ 检查防火墙或主站监听状态 - 此时报文监视窗应自动输出两条 APCI 帧(HEX 格式):
这是 IEC 104 的标准链路启动流程。如果只看到第一条(STARTDT ACT)而无第二条(STARTDT CON),说明主站未响应链路激活请求——此时问题一定在主站侧(如链路未使能、IP 白名单未加),而非工具本身。68 04 07 00 00 00 // STARTDT ACT (启动激活) 68 04 0B 00 00 00 // STARTDT CON (启动确认)
血泪经验:很多工程师在此处反复重启工具,却忽略主站日志。正确做法是:在主站 SCADA 系统中查看“远动通道日志”,搜索关键词
STARTDT,确认是否收到并返回了STARTDT CON。工具只是“镜子”,照不出主站配置缺陷。
3. 报文构造与发送:用 JSON 模板精准控制 ASDU 字段,绕过 GUI 的玄学限制
3.1 为什么 GUI 发送按钮常“失灵”?——GUI 仅支持预置 ASDU 类型,无法动态构造
GUI 界面的“Send ASDU”按钮背后,实际调用的是templates/asdu_type_X.json中定义的字段模板。例如asdu_type_1.json内容为:
{ "type": 1, "vsq": { "sq": false, "num": 1 }, "cot": 20, "commonAddr": 1, "infoAddr": [1001], "data": [ { "value": 1, "quality": { "invalid": false, "reserved": false, "substituted": false, "blocked": false, "overflow": false, "elv": false } } ] }问题在于:GUI 无法修改infoAddr数组长度、无法动态设置cot(原因码)、无法构造带序列号(SQ=true)的批量遥信。一旦你需要测试COT=5(自发上送)或ASDU=30(带时标遥测),GUI 就彻底失效。
3.2 用 Python 脚本重放报文:绕过 GUI,直控协议栈 DLL
工具自带scripts/test_asdu1.py提供了调用libiec104.dll的 Python 接口。以下是最小可运行脚本(需安装ctypes):
import ctypes import json # 加载协议库(路径需按实际调整) iec104_lib = ctypes.CDLL("./lib/libiec104.dll") # 定义函数原型(关键!否则调用崩溃) iec104_lib.asdu_encode.argtypes = [ctypes.c_char_p, ctypes.c_int] iec104_lib.asdu_encode.restype = ctypes.c_char_p # 构造自定义 ASDU(此处改为 COT=5,infoAddr=[1001,1002]) asdu_json = { "type": 1, "vsq": {"sq": False, "num": 2}, "cot": 5, # 自发上送,非主站召唤 "commonAddr": 1, "infoAddr": [1001, 1002], "data": [ {"value": 1, "quality": {"invalid": False}}, {"value": 0, "quality": {"invalid": False}} ] } # 编码为字节流 json_str = json.dumps(asdu_json).encode('utf-8') encoded = iec104_lib.asdu_encode(json_str, len(json_str)) # 输出十六进制(用于比对) print("Encoded ASDU HEX:", encoded.decode('utf-8'))代码逻辑说明:
asdu_encode()函数接收 JSON 字符串,返回符合 IEC 104 标准的二进制 ASDU 字节流(HEX 字符串)cot: 5表示“自发上送”,主站收到后不会回复 COT=7(激活终止),而是直接入库——这是测试遥信抖动的关键场景infoAddr设为[1001,1002]且vsq.num=2,生成带两个信息体的单个 ASDU,避免主站因信息体数量不匹配而丢弃
参数说明:
vsq.sq=false表示非序列化传输(每个信息体独立编码);vsq.num=2表示本 ASDU 包含 2 个信息体。若设sq=true,则num表示序列长度,所有信息体共用一个地址(infoAddr只能为单值)。
3.3 手动拼接完整 IEC 104 帧:从 ASDU 到 APCI 的逐层封装
IEC 104 报文 = APCI 头 + ASDU。libiec104.dll仅生成 ASDU,APCI 封装需手动完成。完整帧结构如下:
| 字段 | 长度 | 值 | 说明 |
|---|---|---|---|
| Start | 1 byte | 0x68 | 固定起始符 |
| APDU Length | 1 byte | 0x06 + ASDU_Length | APCI 长度 = 6字节固定头 + ASDU 长度 |
| Control Field | 4 bytes | 0x08 0x00 0x00 0x00 | U 帧(无编号)或 S 帧(监控)或 I 帧(数据) |
| ASDU | N bytes | asdu_encode()输出 | 实际应用数据 |
实战示例:将上一步生成的 ASDU(假设 HEX 为01 01 00 01 00 01 01,长度7)封装为 I 帧:
# APCI I 帧头(发送序号=0,接收序号=0) apci_head = bytes([0x68, 0x0D, 0x00, 0x00, 0x00, 0x00]) # 0x0D = 6(APCI头) + 7(ASDU长) # ASDU(从上一步获取) asdu_bytes = bytes.fromhex("01 01 00 01 00 01 01") # 完整帧 full_frame = apci_head + asdu_bytes print("Full IEC104 Frame HEX:", full_frame.hex().upper()) # 输出: 680D0000000001010001000101为什么必须手动拼接?
因为 GUI 和脚本默认使用 U 帧(0x68 04 07 00 00 00)启动链路,而真实业务数据必须用 I 帧(带序号)。手动拼接让你完全掌控帧类型、序号、ASDU 内容——这是复现现场偶发丢包问题的唯一方法。
4. 报文解析与故障定位:从 HEX 日志反推主站行为,避开“网络正常但业务不通”的陷阱
4.1 日志文件logs/的双视图解读法:HEX 与 ASCII 必须交叉验证
工具自动将所有收发报文写入logs/20240520_142315.log,每行格式为:
[2024-05-20 14:23:15.123] TX: 68 04 07 00 00 00 [2024-05-20 14:23:15.124] RX: 68 04 0B 00 00 00 [2024-05-20 14:23:16.201] TX: 68 0D 00 00 00 00 01 01 00 01 00 01 01 [2024-05-20 14:23:16.202] RX: 68 04 00 00 00 00关键技巧:
TX行是工具发出的报文,RX行是主站返回的报文68 04 00 00 00 00是S 帧(监控帧),表示主站已收到上一帧(I 帧),并告知其接收序号为 0- 如果
TX发了 I 帧,但RX长时间(>TimeoutMS)无响应,且后续出现68 04 00 00 00 00,说明主站收到了但未按预期生成 COT=7 响应——问题在主站应用层逻辑(如遥信地址未配置、数据库写入失败)
注意:不要只看 HEX,右侧 ASCII 视图(日志中隐含)能快速识别可读字段。例如
RX: 68 0E ... 41 42 43对应 ASCIIABC,可能是主站返回的自定义告警文本(非标准 IEC 104,属厂商扩展)。
4.2 用 Wireshark 抓包对比:验证工具报文是否被系统拦截
有时工具显示TX成功,但主站收不到。此时必须抓包验证:
- 在工具运行前,用 Wireshark 过滤
tcp.port == 2404 - 启动工具并发送报文
- 观察 Wireshark 是否捕获到对应 HEX 帧
典型翻车场景:
现象:工具日志有
TX: 68...,Wireshark 无任何 2404 端口流量原因:Windows 防火墙阻止了
iec104_test.exe的出站连接(尤其 Win10/11 默认启用)解决:在防火墙高级设置中,为
iec104_test.exe添加出站规则,或临时关闭防火墙测试现象:Wireshark 捕获到
68 04 07 00 00 00,但主站日志无记录原因:主站所在服务器启用了 TCP SYN Cookie,而工具未实现完整的三次握手(某些精简版工具会跳过 ACK)
解决:改用
scripts/test_asdu1.py脚本,它调用系统 socket API,兼容性更好
4.3 ASDU 解析失败的三大信号:从 HEX 直接判断主站兼容性
当RX报文出现以下 HEX 特征,基本可判定主站协议栈存在兼容性问题:
| HEX 特征 | 含义 | 主站可能问题 |
|---|---|---|
68 ?? ?? ?? ?? ?? 00 00 00 00 ... | ASDU 长度字段为0x00 | 主站未正确填充 ASDU 长度,工具解析时崩溃(常见于老旧 RTU) |
68 04 00 00 00 00后紧跟68 04 00 00 00 00 | 连续多个 S 帧无 I 帧 | 主站链路保活机制异常,持续发送空监控帧(需检查主站心跳配置) |
68 06 01 00 00 00 00 00 | APCI 长度=6,但无 ASDU | 主站返回了 U 帧(如 TESTFR ACT),但工具误认为是 I 帧解析失败(需升级工具 DLL) |
避坑验证法:将可疑RXHEX 复制到在线 IEC 104 解析器(如 iec104decoder.com),选择“Strict Mode”解析。若解析失败,说明主站报文不符合标准——此时不应调试工具,而应联系主站厂商提供合规报文样本。
5. 常见问题排查:5 条血泪踩坑记录,覆盖 90% 现场翻车场景
5.1 现象:点击 Connect 后状态栏卡在TCP: Connecting...,30 秒后报“连接超时”
原因:工具默认使用SOCK_STREAM连接,但主站监听在SOCK_SEQPACKET(有序包套接字)或 UDP 端口(极少数私有协议变种)
解决:
- 用
netstat -ano | findstr :2404在主站服务器确认监听协议(TCP还是UDP) - 若主站用 UDP,此工具完全不支持,需换用
scapy手写 IEC 104 UDP 封装脚本 - 若主站 TCP 监听但工具连不上,检查主站
iptables是否放行2404端口(Linux)或 Windows 防火墙入站规则
5.2 现象:TCP 连接成功,APCI 帧收发正常,但发送 ASDU 后主站无任何响应(RX 为空)
原因:主站配置了“链路地址过滤”,只接受commonAddr为特定值的 ASDU(如只认commonAddr=100)
解决:
- 修改
config.ini中CommonAddr=100(默认为1) - 或在 JSON 模板中显式设置
"commonAddr": 100 - 验证:用 Wireshark 抓包,确认发出的 ASDU 中
commonAddr字段值是否匹配主站要求(IEC 104 标准规定该字段为 2 字节,大端序)
5.3 现象:发送遥控命令(ASDU=45)后,主站返回COT=46(未执行),但现场设备实际已动作
原因:主站将COT=46(未执行)误当作最终结果,而忽略后续COT=5(自发上送)的状态更新
解决:
- 在
config.ini中添加WaitForCOT5=true(部分版本支持) - 或改用脚本连续发送:先发
COT=6(激活),再立即发COT=5(状态上送),强制主站关联两次报文 - 根本方案:要求主站厂商修复 COT 映射逻辑,
COT=46应仅表示“命令已接收”,非“执行失败”
5.4 现象:启用UseTLS=true后,TCP 连接成功,但日志显示SSL handshake failed
原因:主站使用 TLS 1.3,而工具内置libtls_wrapper.dll仅支持 TLS 1.2
解决:
- 主站侧降级 TLS 至 1.2(推荐)
- 或替换
libtls_wrapper.dll:从 OpenSSL 1.1.1t 编译新版 DLL(需 VS2019+ CMake),替换时确保libcrypto-1_1.dll和libssl-1_1.dll版本匹配 - 紧急绕过:用
stunnel做 TLS 代理,工具连127.0.0.1:2405,stunnel转发至主站192.168.1.100:2404并处理 TLS
5.5 现象:同一台电脑,昨天能连,今天突然TCP: Connection refused
原因:Windows 更新后重置了localhost解析,或主站进程崩溃后端口未释放
解决:
- 运行
netstat -ano | findstr :2404,若 PID 存在但无对应进程,用taskkill /F /PID XXXX强杀 - 检查
hosts文件(C:\Windows\System32\drivers\etc\hosts)是否新增了127.0.0.1 iec104-test类条目,导致 DNS 解析异常 - 终极验证:用
telnet 192.168.1.100 2404测试端口连通性,若 telnet 通则必为工具问题;不通则为主站或网络问题
6. 进阶技巧:用报文重放功能复现偶发故障,把“玄学问题”变成可验证 Bug
6.1 从日志提取故障报文:精准定位“一次性的丢包”
现场常遇到“主站偶尔收不到某次遥控”,但无法复现。此时需用工具的报文重放功能:
- 在
logs/中找到故障时段日志(如20240520_142315.log) - 复制故障前后的完整 TX/RX 交互(至少包含 STARTDT、ASDU、S 帧)
- 新建
replay_fault.json,按以下结构组织:
{ "sequence": [ { "direction": "TX", "hex": "680407000000" }, { "direction": "RX", "hex": "68040B000000" }, { "direction": "TX", "hex": "680D0000000001010001000101" } ] }- 运行重放脚本:
python scripts/replay.py replay_fault.json --interval 1000--interval 1000表示每帧间隔 1 秒,模拟真实时序
为什么有效:
- 真实故障常由主站 CPU 突增、数据库锁表、或网络抖动引起,单次重放无法触发
- 用
--loop 100参数循环 100 次,配合--jitter 50(±50ms 随机延迟),可高概率复现时序敏感型 Bug
6.2 构造边界报文:测试主站对非法字段的容错能力
IEC 104 标准允许infoAddr最大为 65535,但某些主站解析库用int16存储,导致infoAddr=65536时溢出为0。用脚本构造越界报文:
# 构造 infoAddr = 65536(超出 uint16 范围) asdu_json = { "type": 1, "vsq": {"sq": False, "num": 1}, "cot": 20, "commonAddr": 1, "infoAddr": [65536], # 关键:故意越界 "data": [{"value": 1}] }预期结果:
- 合规主站应拒绝此报文(返回
COT=47:未知类型) - 有缺陷主站可能崩溃、重启,或静默丢弃——这正是你需要提交给厂商的 Bug 证据
6.3 与主站日志对齐:用时间戳建立报文因果链
工具日志时间精度为毫秒,主站日志常为秒级。为精准对齐:
- 在工具发送前,记录系统时间:
import time start_time = time.time() * 1000 # 毫秒级 send_asdu() # 发送报文 print(f"[{start_time:.0f}] TX ASDU sent") - 主站日志中搜索该毫秒时间戳附近的
IEC104关键词 - 若主站日志无记录,但工具
TX日志存在 → 问题在传输层(网络丢包) - 若主站日志有记录但无响应 → 问题在主站应用层(如地址未配置、权限不足)
我干这行八年,最深的教训是:永远不要相信“网络通了就等于协议通了”。IEC 104 的坑,90% 在协议栈的灰色地带——那些标准没写死、厂商各搞一套的地方。这个 ZIP 包的价值,不是帮你省时间,而是给你一把刀,亲手剖开那些“应该工作”的黑匣子,直到看见血肉。
希望帮到你。
本文还有配套的精品资源,点击获取