news 2026/10/14 14:16:39

104规约报文解析全攻略:APCI/ASDU拆解与抓包分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
104规约报文解析全攻略:APCI/ASDU拆解与抓包分析

简介:面向电力自动化与工业通信领域的104规约报文解析工具,适用于调度自动化、变电站通信等场景下的报文解析、抓包分析与故障排查,兼顾入门与高级分析。压缩包共20个文件,约11.68MB,含104AnalysisTool.exe及无抓包版两个可执行程序,配套desc104.dll、desc61850.dll、descModbus.dll等协议解析库,另有真型实验室104主规约.csv、其他例子.csv等示例数据,以及辅控序号不对.pcap抓包文件与GW104S_Diga日志,可覆盖从基础解析到工程排错的完整链路。工具还附help.rtf帮助文档、readme.txt说明及COT.DAT配置文件,便于快速上手。已有437人学习下载,适合电力通信调试人员、自动化工程师及规约学习者参考。

1. 104 报文解析工具,先把调试台面上的黑匣子拆开

做变电站后台或远动调试的人,十有八九都经历过这种场景:主站打过来电话说“遥信不对”,你抱着抓包文件对着十六进制串一个个数字节,数到眼花还是没找到那一帧哪儿错了。104 规约(IEC 60870-5-104)报文解析工具,干的就是把这种“黑匣子”变成普通文本的活儿——抓包文件、手工粘贴的 hex、日志里截出的一段报文,丢进去就能拆成 APCI、ASDU、信息体地址、浮点值、品质位这种可读字段,顺带还能做帧统计和异常定位。它适合三类人:刚接触 104 规约的新手、在现场跟主站联调的老手、以及需要批量核对报文正确性的后台开发。这篇文章不吹功能,我直接按自己实际拆过的路径,把协议骨架、解析脚本、抓包分析、高频翻车点一次讲透。

2. 先看懂 104 的报文骨架:APCI 与 ASDU 怎么切分

2.1 三种帧型:I 帧、S 帧、U 帧的判定逻辑

104 规约的 APDU(应用规约数据单元)由 APCI 和 ASDU 组成。APCI 是固定的 4 字节控制域,ASDU 承载业务数据。拿到一串报文,第一步不是看内容,而是先判定这个帧是 I 帧、S 帧还是 U 帧——三种帧的控制域第一位字节 bit0、bit1 组合不同,后续解析路径也完全不同。

  • bit0 = 0:I 帧,带发送序号和接收序号,承载 ASDU 数据
  • bit0 = 1、bit1 = 0:S 帧,只带接收序号,用于确认对方报文,不承载 ASDU
  • bit0 = 1、bit1 = 1:U 帧,用于链路控制(TESTFR、STARTDT、STOPDT),不带序号

这是整个解析流程的入口判断。市面不少工具解析出乱码,往往就是这里漏了判断,把 S 帧或 U 帧当 I 帧往下拆。判定代码很简单,我一般直接写死在工具首层:

def judge_frame_type(ctrl_byte: int) -> str: """ 根据 APCI 第一个字节判定帧类型 bit0=0 -> I 帧; bit0=1 bit1=0 -> S 帧; bit0=1 bit1=1 -> U 帧 """ if ctrl_byte & 0x01 == 0: return "I" if ctrl_byte & 0x03 == 0x01: return "S" return "U"

判定逻辑的关键是 mask 的三级区分。第一层取 bit0,第二层取 bit0-bit1。U 帧的 TESTFR、STARTDT、STOPDT 三种功能再靠 bit2-bit7 区分,比如 STARTDT 激活是 0x07,确认是 0x0B。调试时最常遇到的是主站建链后先发 U 帧 STARTDT 激活,从站回 STARTDT 确认,之后才启动 I 帧数据流。解析工具如果把这些 U 帧也按 ASDU 去拆,后面的信息体全部偏移,所以判定步骤必须在任何字段解析之前完成。

2.2 ASDU 字段逐个拆:类型标识、VSQ、传送原因、公共地址、信息体

ASDU 是真正干活的部分,由类型标识、可变结构限定词(VSQ)、传送原因(COT)、公共地址、信息体组成。每个字段都有固定的字节长度和取值范围,这也是工具能“翻译”报文的基础。

  • 类型标识 1 字节:决定信息体格式,比如 0x0D(13)是带品质描述的短浮点遥测,0x01(1)是单点遥信,0x1E(30)是带时标的单点遥信
  • VSQ 1 字节:bit7 表示信息体地址是否连续,低 7 位表示信息体个数。0x81 表示连续、1 个信息体
  • COT 2 字节:传送原因,低字节取值,1 周期、2 背景、3 突发、20 响应总召唤,高字节是扩展位
  • 公共地址 2 字节:区分站号,常见 1 起
  • 信息体定长或变长:由类型标识决定,比如遥测浮点就是 3 字节信息体地址 + 4 字节浮点 + 1 字节品质描述

解析工具把每个字段单独标色高亮,你一眼就能看出“哪个字段超范围”。COT 值为 44 或 45 时表示未知类型标识,这通常是主站和从站版本不匹配;COT 为 20 则说明这帧是响应总召唤的现场值快照。我见过不少对点问题就是 COT 误读导致的——后台把突发报文(COT=3)当成周期报文(COT=1)去处理,逻辑就全乱了。

2.3 帧长度字段的陷阱:0x68 后面那个数到底指什么

104 帧的头部只有两个字节是固定的:0x68 启动字符和紧随其后的 APDU 长度。长度字段的语义是“从控制域第一个字节开始,到 ASDU 最后一个字节为止的字节数”,它不包含 0x68 和长度字段自身。也就是说 0x68 0x12 实际表示后面跟着 18 个字节,整帧共 20 字节。

这个语义是解析工具切帧的核心依据。写工具时必须用“先校验长度字段再切帧”的顺序,而不是搜索下一个 0x68 来分帧——因为 ASDU 里的数据字节完全可能等于 0x68。常见做法是先搜到 0x68,读取下一个字节得到帧长,校验 4 ≤ len ≤ 253 后直接从当前偏移处切出完整帧,然后跳过整帧继续找下一帧,绝不能按固定偏移扫 0x68。长度字段数值异常时还要区分是短包还是长包,这是工具报“非法帧”的唯一判据,逻辑写错就会把正常报文全切乱。

3. 手写一个遥测报文解析器:拿真实 hex 串一步步拆

3.1 从抓包文件里挑一帧遥测报文,先看原始十六进制

进入实战环节。我有一帧从抓包文件里挑出来的典型遥测报文,原始十六进制如下:

68 12 00 00 00 00 0D 01 03 00 01 00 01 40 00 CD CC CC 3D 00

我不直接让你背格式,按顺序逐字节拆一遍。0x68 是启动字符;0x12 是 APDU 长度 18,也就是从控制域开始到最后一个字节一共 18 字节;紧接着 4 字节 00 00 00 00 是控制域,bit0 为 0,这是 I 帧且收发序号都是 0。从第 7 个字节起是 ASDU:0x0D 是类型标识 13(带品质描述的短浮点遥测),0x01 是 VSQ 表示 1 个信息体且地址不连续,03 00 是传送原因 3(突发),01 00 是公共地址 1,01 40 00 是信息体地址 0x4001(遥测区起始地址),后面 5 字节 CD CC CC 3D 00 是浮点值和品质描述。

3.2 完整解析脚本:逐字段读取加边界注释

把上述逻辑写成可复用脚本,就是工具最核心的解析内核。注意这里用 struct 模块按小端序解包,这跟 104 规约的字节序约定必须一致,否则浮点值会全乱。

import struct def parse_m_me_nc(payload_hex: str) -> dict: """ 解析带品质描述的短浮点遥测帧(类型标识 13) 输入: 完整 104 帧的十六进制字符串, 不含空格 返回: 各字段解析结果 """ raw = bytes.fromhex(payload_hex) # 校验启动字符和长度字段 if raw[0] != 0x68: raise ValueError("启动字符不为 0x68") apdu_len = raw[1] if len(raw) != apdu_len + 2: raise ValueError(f"实际长度 {len(raw)} 与长度字段 {apdu_len} 不一致") # 前 4 字节为控制域, 跳过 asdu = raw[4:] ti = asdu[0] # 类型标识 vsq = asdu[1] # 可变结构限定词 cot = struct.unpack("<H", asdu[2:4])[0] # 传送原因 2 字节小端 com_addr = struct.unpack("<H", asdu[4:6])[0] # 公共地址 2 字节小端 # 信息体地址为 3 字节小端, 补一个 0x00 凑 4 字节解包 info_addr = struct.unpack("<I", asdu[6:9] + b"\x00")[0] value = struct.unpack("<f", asdu[9:13])[0] # IEEE 754 单精度小端 qds = asdu[13] # 品质描述 return { "ti": ti, "vsq": vsq, "cot": cot, "com_addr": com_addr, "info_addr": hex(info_addr), "value": round(value, 4), "qds": qds, } if __name__ == "__main__": result = parse_m_me_nc("6812000000000D0103000100014000CDCCCC3D00") print(result)

脚本的关键点在三处。一是长度字段校验用len(raw) != apdu_len + 2,这对应前面讲的长度字段不含起始字节和自身;二是信息体地址按 3 字节小端处理,必须补零凑 4 字节再unpack,直接按 2 字节取会丢掉高位;三是浮点用<f解析,也就是低字节在前。如果把<f写成>f,0.1 会变成 1.5625e-42 这类天文数字,这是我在实际调试时踩过的真实翻车现场。

3.3 用总召唤与时钟同步报文验证工具解析逻辑

工具写完了不能只看“能跑”,还得拿几种语义明确的报文做验证。总召唤报文(类型标识 100,COT=6)是主站下发的激活命令,没有信息体数据,解析结果里信息体部分应为空;时钟同步报文(类型标识 103)携带 6 字节时间戳,其中毫秒 2 字节、分钟 1 字节、小时 1 字节、日 1 字节、月 1 字节,年份的高 2 位藏在分钟字段的 bit7-bit6。

我一般用这两种报文当“标尺”——如果总召唤解析出信息体内容,说明帧偏移没切对;如果时钟同步的年份字段异常,说明位运算写错了。解析工具内置的专家模式就是拿这些已知答案做自动断言,报文一进去先跑一遍“体检”,跟标准答案比对,不一致的直接在界面上标红。这种设计比单纯译码强得多,因为它把“解析正确性”从人眼核对变成了机器判断。

4. 文件解析与抓包分析:从 pcap 到高级统计

4.1 pcap 文件怎么喂给工具:链路层偏移是关键

抓包解析是 104 调试的刚需——现场不可能每次都让你在后台软件里手动触发链路,更多时候是从交换机镜像口抓一个 pcap 回来慢慢回放。工具解析 pcap 文件时,第一步是跳过 24 字节的 pcap 全局头,之后每个数据包前有 16 字节的包头部,里面包含时间戳和包长度,紧接着才是链路层数据。

这里的坑是链路层长度不固定。以太网标准是 14 字节,但带 VLAN 标签时(802.1Q)是 18 字节,还有可能遇到带附加字段的变种。工具如果固定用 14 字节偏移去定位 IP 头部,碰到 VLAN 包就会直接错位,随后的 TCP 端口判断、104 帧定位全盘皆错。解决方法是解析 EtherType 字段:0x0800 后直接是 IPv4,0x8100 是 VLAN 标签,需要再跳 4 字节后才到 EtherType 0x0800。这一步必须动态判断,不能写死。

4.2 在 TCP 流里定位 104 帧:先扫 0x68 再按长度跳帧

TCP 是字节流,没有消息边界,所以从 TCP payload 里恢复 104 帧必须自带定界逻辑。我习惯的做法是三层递进:先确定 TCP 端口(常见的 2404),再遍历 payload 找 0x68,然后读下一字节校验长度,按长度跳过整帧,继续找下一帧。

def extract_104_frames(tcp_payload: bytes) -> list: """ 从 TCP payload 中提取完整 104 帧 返回帧列表, 每帧为 bytes 类型 """ frames = [] i = 0 while i < len(tcp_payload): if tcp_payload[i] != 0x68: i += 1 continue if i + 1 >= len(tcp_payload): break frame_len = tcp_payload[i + 1] if frame_len < 4 or frame_len > 253: i += 1 continue if i + 2 + frame_len <= len(tcp_payload): frames.append(tcp_payload[i:i + 2 + frame_len]) i += 2 + frame_len # 跳过完整帧 else: break # 剩余字节不足一帧, 等待后续 TCP 段 return frames

这段逻辑里最值钱的是“按长度跳帧而不是按偏移扫”,它保证即使 TCP payload 里混着热备份报文或其他协议数据,也能准确找回每一帧边界。提取出来的帧再交给前面写的parse_m_me_nc之类函数逐帧解码,就能看到完整的时间线回放。我的习惯是把 pcap 里所有帧按时间戳排列输出,再把同一信息体地址的连续变化连成折线,这样后台某个测点是否跳变异常,一眼就能扫出来。

4.3 高级分析:按类型标识统计、按传送原因聚合、定位频繁重发

工具的“高级分析”不是玄学,本质上是把解码结果做三个维度的聚合。第一个维度是按类型标识分组统计帧数,如果一帧里同时出现单点遥信和双点遥信异常,能直接看到哪些类型占比过高;第二个维度是按传送原因聚合,COT=3 的帧占比异常高,说明对侧在频繁上送变化报文,可能是死区参数设太小;第三个维度是重发检测,同一信息体地址在短时间窗口内反复出现相同值,基本可以判定链路存在重复帧转发。

这三个统计维度能快速缩小问题的排查范围。我调试过一个遥信频繁翻转的现场,后台日志全是对点记录,看不出规律;用类型统计一看,单点遥信帧占 92%,其中 70% 是同一信息体地址,再核对时标发现是现场开入信号抖动,跟规约解析完全无关。这就是高级分析的价值——把协议层的“是什么”翻译成业务层的“为什么”。

5. 六大 104 解析避坑点:现象、原因、对策

5.1 帧边界与地址解析:最容易翻车的四个地方

现象一:所有带浮点的遥测解析结果接近 0 或异常大。

原因:浮点字节序解析错误。104 规约里 IEEE 754 单精度是低字节在前,用struct.unpack(">f", ...)或者按十进制“人工算”都会得到完全错误的结果。解决:统一用<f解析,并在工具里内置一个已知值自检——拿 0.1 的浮点字节 CD CC CC 3D 做回归,解出来不是 0.1 就说明字节序反了。

现象二:公共地址解析出 256 的倍数或 0。

原因:公共地址是 2 字节小端序,低字节在前。有的协议栈实现把公共地址按单字节处理,导致站号大于 255 时高位丢了,解析结果周期性地差 256。解决:取asdu[4] + (asdu[5] << 8),并确认对端装置配置的站号范围。

现象三:pcap 文件里所有报文解析错位,但单独粘贴 hex 又是对的。

原因:链路层偏移处理没适配 VLAN 标签。前面提到 EtherType 为 0x8100 时要额外跳过 4 字节,固定偏移的工具在这里必然翻车。解决:解析前先读 EtherType,做分支判断。

现象四:长度字段校验失败,工具把一帧拆成两帧。

原因:有的厂商实现把 APDU 长度字段定义成“ASDU 长度 + 控制域长度”,有的定义成“ASDU 长度 + 控制域长度 - 1”,版本差异会导致长度字段差 1。这种帧用标准校验必然失败。解决:在校验时报“长度不一致”并给出“按当前长度切帧”和“按标准切帧”两种候选结果,让用户选择,而不是直接丢弃。

5.2 信息体与时标解析:隐蔽的位置偏移

现象五:带时标的单点遥信(类型 30)解析后年份不对,差了几十年。

原因:104 时标的年份只有 7 位(0-99),且高 2 位存放在分钟字段的高 2 位 bit7-bit6。粗心的解析器把分钟当作纯 6 位处理,年份位全被丢掉了。解决:year = ((minute_byte >> 6) & 0x03) + 2020(年份基准依系统约定),分钟实际值取minute_byte & 0x3F。

现象六:双点遥信(类型 3)解析出“中间状态”或“异常状态”。

原因:双点遥信的 DCO 字段只有低 2 位有效,00 表示中间状态、01 确定开、10 确定合、11 异常。高 6 位是填充位,但有的厂商在填充位里塞了其他信息,直接按整字节去判断会当成异常状态。解决:先dco = raw_dco & 0x03再判断。

这两个坑的共同点是“字段位宽比字节窄”,必须用 mask 操作取有效位,不能整字节取值。几乎所有 104 解析工具里出现的“状态异常”“时间不对”都是这类位运算没写到位造成的。

6. 把解析结果变成回归测试资产:一个我常用的收尾技巧

工具用得顺手之后,我建议你多做一件事:把每次调试现场遇到的典型报文整理成固定用例集,每个报文标注正确的解析结果,然后写一个自动断言脚本。这样以后工具升级、或者换个解析库再验证,几分钟就能跑完整套回归,不用再靠人眼去核对几百帧报文。

EXPECTED = {"cot": 3, "com_addr": 1, "info_addr": "0x4001", "value": 0.1, "qds": 0} def test_parse_m_me_nc(): result = parse_m_me_nc("6812000000000D0103000100014000CDCCCC3D00") for key, expected in EXPECTED.items(): actual = result.get(key) assert actual == expected, f"{key}: 实际 {actual}, 期望 {expected}" print("全部断言通过")

这个脚本把“解析结果对错”变成了“测试通过与否”,比盯着 hex 串数位可靠得多。我自己的习惯是建一个 cases 目录,每一个现场问题收敛后,把出问题的原始报文和最终确认的解析结果追加成一条新用例。从那以后,我每次改解析逻辑都会强制先跑一遍全量回归,三个月的现场报文集合跑通了我才敢说没问题,这个习惯帮我挡掉了至少三次改动引入的倒退。如果你也常跟 104 规约打交道,建议拿到工具后第一件事就是建自己的用例集。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 14:16:23

世界地图shp转中国为中心:投影与中央经线设置全攻略

简介&#xff1a;世界地图shapefile数据包以国家测绘地理信息局2019版标准地图为底图绘制&#xff0c;面向投稿C刊、制作科研图件的学者及GIS从业者&#xff0c;既提供符合审查要求的底图&#xff0c;也给出将世界地图转为中国为中心视角的可行方法。压缩包共177个文件&#xf…

作者头像 李华
网站建设 2026/10/14 14:16:15

解决dbgrid32.ocx丢失错误的专业指南

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/10/14 14:16:08

Python Flask+Django构建高校学术交流管理系统实践

1. 项目背景与核心价值 高校学术交流活动是科研工作者分享成果、碰撞思想的重要平台。传统线下报告管理模式存在信息孤岛、流程繁琐、数据统计困难等问题。这套基于Python FlaskDjango的学术交流管理系统&#xff0c;正是为解决这些痛点而生。 我在某高校信息化部门工作期间&a…

作者头像 李华
网站建设 2026/10/14 14:15:20

用模型给数据挑错:图像分类与目标检测中的标注质量排查实战

1. 这个实验到底在折腾什么先说结论&#xff1a;这个项目的核心想法特别朴素——让模型自己去找数据里的错。不是拿模型去跑分刷榜&#xff0c;而是反过来&#xff0c;把模型当成一个"质检员"&#xff0c;用它来判断训练数据里哪些标签可能标错了、哪些样本可能是脏数…

作者头像 李华
网站建设 2026/10/14 14:15:06

Python量化分析:股价与价值背离的预测模型构建

1. 项目背景与核心价值 在金融投资领域&#xff0c;股价与内在价值的背离现象一直是专业投资者关注的重点。传统的基本面分析和技术分析往往各自为战&#xff0c;而Python量化分析能够将两者有机结合&#xff0c;通过数据驱动的方式捕捉市场异常。这个项目正是要解决三个核心问…

作者头像 李华
网站建设 2026/10/14 14:15:04

PDF 转换工具,办公党整理的实用工具合集

重要提醒&#xff1a;合同、身份证这类涉及隐私的文件&#xff0c;尽量不要使用网页在线工具&#xff0c;文件会上传至第三方服务器&#xff0c;存在信息泄露隐患&#xff0c;优先选择本地软件处理。&#x1f5a5;️电脑本地软件&#xff0c;适合批量处理、扫描件识别WPS Offic…

作者头像 李华