资讯动态

电力规约101/104报文解析实战:从抓包到排障

发布时间:2026/9/2 19:17:20 来源:尧图企业网站定制
简介面向电力规约调试与电力自动化开发人员的101/104报文解析工具包集成了报文解析、浮点数/十六进制转换、客户端/服务端模拟等多类实用工具。包内含IEC8705报文翻译工具与报文解析器可对IEC 101/104规约报文进行翻译和逐字段拆解另有Peugeot、PMA及104主站调试模拟工具等客户端/服务端模拟软件便于开发调试时收发数据验证均为亲测可用软件。资源共136个文件以exe、dll、csv、xml、chm等为主压缩包约6.8MB其中csv文件对应规约信息库chm为帮助文档dll为运行库整体目录清晰、即下即用。目前已有7993人学习下载适合电力系统通信调试人员、自动化工程师及规约入门学习者参考使用。 半夜在变电站调试主站电话突然打过来“104链路还是断的你抓一下报文看看。”我把笔记本串到交换机镜像口打开Wireshark抓了30秒盯着满屏十六进制一头雾水——干电力自动化的人对这一幕应该都不陌生。说得好听叫报文解析说得直白点就是从一堆字节里翻译出“哪路开关跳了”“哪条线路电流超限”这种能看懂的人话。我断断续续写了三年101、104电力规约报文解析工具从最初只会用现成软件看十六进制串到后来自己写脚本把pcap文件批量转成可读表格踩了不少坑。这篇就把这些经验一次性倒出来从规约原理、报文结构到工具选型、实战解析和排障思路尽量讲清楚。1. 先搞明白101和104到底在传输什么东西1.1 一条数据从开关到主站要走多少路电力系统的调度自动化本质上是把变电站里的开关位置、电气量、电能数据从现场设备一层层送到调度主站。现场负责采集的装置叫RTU或者测控装置它们把一次设备的模拟量和开关量变成数字信号再通过远动通道发给主站。101和104就是这条通道上使用的“通用语言”全称分别是IEC 60870-5-101和IEC 60870-5-104。101规约诞生得早主要跑在串口上RS-232、RS-485这类专线通道波特率一般9600甚至更低。104规约是后来在101的基础上改出来的把传输层换成了TCP/IP默认端口2404可以直接复用电力调度数据网。两者在应用层上的数据格式高度一致区别主要集中在链路层和传输方式上。所以很多解析工具会把101和104做在一起因为只要把ASDU应用服务数据单元这一层解析逻辑写好再分别处理链路层和APCI外壳就能同时覆盖两个规约。这也是这个工具项目最大的价值点一套解析核心兼容两种规约。1.2 101和104最大的区别不在“报文”在“通道”只看报文内容的话101和104的信息元素结构几乎一样真正的区别在于对比项101规约104规约传输通道串口/专线TCP/IP网络默认端口无2404链路确认机制依赖链路层确认APCI控制域确认适用场景老旧变电站、窄带通道调度数据网、新建站点传输速率低通常9600bps以下高取决于网络带宽多客户端支持困难天然支持老一代变电站用101串口专线优点是物理隔离、抗干扰缺点也明显布线麻烦、速率上不去、调试要带串口线。104能跑在现成的网络里一个前置机可以同时接几十个厂站带宽大远程维护也方便。现在新建站点基本都是104为主但存量101站点数量依然庞大所以这两个规约在很长一段时间内会共存。1.3 解析工具真正的工作内容解析工具做的事情就是把物理通道上收到的原始字节流按照规约定义逐层拆开第一层从字节流里找到帧边界101靠启动字符和结束字符104靠启动字符加长度字段。第二层解析控制信息判断这是数据传输帧、确认帧还是链路控制帧。第三层解析ASDU把类型标识、传送原因、公共地址、信息体地址、信息元素值全部取出来。第四层把信息元素值还原成物理量比如将原始值乘以系数变成实际电流值。很多刚接触规约的人以为解析就是“把一个报文对应到一份说明文档”实际上真正的难点在于字节流是连续的不可能保证一个TCP包恰好装一个完整APDU必须自己处理粘包、拆包和半包。这个问题我后面会专门讲。2. 拆帧看门道104报文的APCI、ASDU和控制域2.1 用总召唤报文认识68开头的一串字节先看一个最常见的总召唤激活确认报文十六进制长这样68 0E 08 00 00 00 64 01 06 00 01 00 00 00 00 00看不懂很正常我逐段拆68启动字符固定值说明这是一个104 APDU。0EAPDU长度14字节注意这个长度是从控制域第一个字节开始算到报文结束不包含68和0E本身。08 00 00 00控制域四个字节。这里是I帧第一个字节最低位为0发送序号N(S)和接收序号N(R)都包含在里面。64类型标识十进制100代表总召唤命令C_IC_NA_1。01可变结构限定词VSQbit7为0表示逐个寻址低7位表示有1个信息体。06 00传送原因两字节这里0x0006表示“激活”。01 00公共地址两字节这里是0x0001代表站地址1。00 00 00信息体地址三字节总召唤的地址固定为0。00召唤限定词QCC0表示无附加限定。看到没有一个看似普通的报文里面藏着七层信息。解析工具的核心工作就是把这七层信息全部提取出来并按类型标识去查对应的信息体结构。2.2 I帧、S帧、U帧控制域里的链路心跳104规约的控制域有4个字节里面区分三种帧类型这是新手最容易搞混的地方I帧信息帧第一个字节最低位为0格式为“0 N(S) 0 N(R)”用于携带ASDU数据。N(S)是发送序号N(R)是期望接收的序号。S帧监视帧第一个字节最低位为1、第二位为0格式为“01 N(R)”用于确认收到的数据不携带ASDU。U帧未编号帧前两位都是1格式为“11 功能码 0 N(R)”用于链路控制比如STARTDT激活链路、STOPDT停止链路、TESTFR测试链路。U帧里的TESTFR测试帧本质上是链路的心跳。主站和从站每隔一段时间互发测试帧确认链路活着。如果你的解析工具把U帧也当I帧去解析ASDU一定会报错。正确的做法是先判断帧类型再决定是否继续解析ASDU。2.3 类型标识和传送原因解析器词典的核心ASDU里的类型标识决定了信息体的格式也是解析器里最核心的分发表。我在工具里维护了一张映射表类型标识名称含义1M_SP_NA_1单点遥信1字节信息体3M_DP_NA_1双点遥信1字节信息体9M_ME_NA_1归一化遥测2字节11M_ME_NB_1标度化遥测2字节13M_ME_NC_1短浮点遥测4字节45C_SC_NA_1单点遥控命令100C_IC_NA_1总召唤命令传送原因同样关键在104里占2个字节。常见值有1周期、2背景扫描、3突发变位、6激活、7激活确认、8停止激活、9停止激活确认、20响应站召唤。实际调试中看到传送原因是3基本可以判断这个报文是“开关变位”的主动上送看到20说明是总召唤的应答数据。在工具里把传送原因翻译成中文短语比直接看数字高效得多。3. 解析工具选型自研、开源库还是现成抓包软件3.1 三条技术路线的客观对比很多同行问我工具是怎么实现的到底该自己写还是用现成的。我整理了一张对比表对比维度纯自研脚本开源协议栈现成抓包/分析软件上手难度高需要精通规约中需要读库文档低装上就能用灵活性最高想怎么解析都行较高可二次开发低只能看已有功能性能取决于实现质量通常优化较好一般够用场景契合批量分析、定制需求集成到自己的系统现场快速排障典型代表Python/Java自写j60870、lib60870Wireshark、厂家调试软件坦白讲没有一条路线是“绝对正确”的。我见过只用Wireshark就能把问题定位得很准的老工程师也见过把自家解析器写到上千行的团队。关键是搞清楚自己手里的任务是什么。3.2 我推荐的组合打法如果是现场排查链路问题我的首选永远是Wireshark因为它免费、通用、抓包能力强。只要端口是2404Wireshark会自动识别并解析104报文。如果端口特殊右键Decode As手动指定IEC 60870-5-104解码器即可几十秒就能看到解析结果。如果需要批量分析历史报文、做回归测试、或者要处理厂家自定义的ASDU类型这时候Wireshark的通用dissector就有点不够用了。我会用Python写一个针对性的解析脚本用dpkt或scapy读pcap文件按自己的需要输出表格。这样可以完全控制解析逻辑遇到扩展类型也能随时加代码。至于开源协议栈比如Java的j60870、C的lib60870我倾向于在需要“和现场设备联调并自动交互”时才用比如要模拟主站做遥控实验、或者要搭建一个虚拟从站做测试。它的优势是帮你处理好了序列号、确认、链路状态机你只需要关注业务数据。4. 实操把Wireshark抓的104报文翻译成人话4.1 先解决“抓得到包但解析不了”的尴尬在真正写脚本之前先强调一个问题Wireshark抓包时如果只看到TCP包看不到104的解析结果绝大多数情况是端口不是2404或者TCP流没有成功重组。104的APDU可能被TCP拆成多个段也可能多个APDU粘在一个TCP包里Wireshark在流重组不完整时就会放弃解析。解决办法有两个方向调整Wireshark的协议解析设置强制把对应端口识别为104。用“Follow TCP Stream”把整个TCP载荷导出再交给自己的脚本处理。第二种做法在写脚本时更可靠因为你拿到的是完整的应用层数据不用关心TCP层面的分片和重传。4.2 一个可以直接改的Python解析脚本下面这个脚本的思路是用dpkt读取pcap筛选TCP端口2404再按APDU边界切分和解析。我会故意保持精简方便你在此基础上扩展import dpkt, datetime def parse_apdu(buf): if len(buf) 6 or buf[0] ! 0x68: return None apdu_len buf[1] if len(buf) apdu_len 2: return None, None, None # 不完整等下一次拼包 body buf[2:2 apdu_len] control body[0:4] if control[0] 0x01 0: frame_type I帧 elif control[0] 0x03 0x01: frame_type S帧 else: frame_type U帧 asdu body[4:] return frame_type, asdu, body def main(pcap_path): with open(pcap_path, rb) as f: pcap dpkt.pcap.Reader(f) for ts, pkt in pcap: eth dpkt.ethernet.Ethernet(pkt) if not isinstance(eth.data, dpkt.ip.IP): continue ip eth.data if not isinstance(ip.data, dpkt.tcp.TCP): continue tcp ip.data if tcp.dport ! 2404 and tcp.sport ! 2404: continue app_data tcp.data frame_type, asdu, body parse_apdu(app_data) if not frame_type: continue print(datetime.datetime.utcfromtimestamp(ts), frame_type, app_data.hex())这个脚本没有处理跨TCP包重组适合先看单包独立的情况。真正要用的生产脚本我建议维护一个session级缓冲区每次收到TCP数据先追加到缓存然后根据长度字段循环切APDU直到缓存里的数据不足一个完整APDU为止。4.3 解析结果如何验证工具写完最怕的是解析错了还不自知。我的习惯是用一个从站模拟器先构造已知数据再用自己的解析器去读逐字段核对。举个例子我用模拟器发一个单点遥信地址为100值为“合”抓到的ASDU十六进制是01 01 03 00 01 00 64 00 00 01按ASDU拆解01是单点遥信01是1个信息体03 00是突发传送原因01 00是公共地址164 00 00是信息体地址100最后01是开关值“合”。解析器输出结果应该和模拟器设置的完全一致。如果对不上就要优先检查信息体地址的字节序是否反过来、类型标识是否识别错位。5. 五个高频翻车现场地址、字节序、时标和扩展类型5.1 为什么报文抓了却看不到104端口识别问题有一次我在现场配的是专用网络端口不是标准的2404Wireshark打开抓包文件后满屏都是TCP看不到任何规约解析结果。当时年轻以为报文没抓到重新抓了好几遍浪费了半小时。后来才意识到是端口识别问题。只要在Wireshark里选中其中一个TCP包右键选择解码方式手动指定为IEC 60870-5-104整条TCP流的解析立刻就出来了。这也提醒我商用抓包工具再智能也架不住自定义端口。做工具的人一定要把“端口可配置”当成默认功能别写死。5.2 遥测值读数差100倍字节序、缩放系数和无效值104的ASDU里16位遥测值常见的是归一化值NVA或标度化值SVA默认低位在前。也就是说十六进制0x01 0x00实际值是1而不是256。如果解析器按高位在前去读数值直接差256倍。更隐蔽的是归一化值的缩放问题。NVA是一个-1到1之间的小数实际电流值需要乘以一个系数才能得到比如系数是1000A那么原始值0x4000十进制16384对应的实际值不是16384而是16384 / 32767 * 1000约500A。很多工具只输出原始值不换算物理量不是不能看而是看的人心里必须清楚这个坑。还有一种常见情况是无效值。很多装置在数据异常时会上送0x8000表示测量无效。解析工具应该把这类特殊值单独标记出来否则调试人员会把一个巨大或异常的数值当成真实故障去处理。5.3 地址对不上就全白搭链路地址、公共地址和信息体地址规约里地址分了好几层我最常看到的就是把链路地址、公共地址和信息体地址混为一谈。链路地址在101规约里用于标识从站104里没有这个概念。公共地址在ASDU里用于区分站主站就是根据它判断数据是哪个厂站上送的。信息体地址在信息体标识里用于区分具体测点比如某条线路的电流地址是1001电压地址是1002。有一次联调主站一直说收不到数据查来查去发现是公共地址配错了现场装置配置的是2主站侧写的是1主站直接把整帧丢弃。解析工具的价值在这里就体现出来了看到“公共地址2”马上能反查配置文件而不是在两边命令行里用tcpdump盲猜。5.4 七个字节的时标CP56Time2a毫秒和时区都在里面101和104里的带时标报文会用到CP56Time2a格式占7个字节结构如下第1、2字节毫秒数低位在前这是最容易搞错的。第3字节分钟包含无效位IV。第4字节小时包含夏令时标志SU。第5字节日包含星期几。第6字节月包含年扩展位。第7字节年从2000年起算。我曾经写过一个解析时标的工具一开始直接按大端读毫秒结果所有事件时间都比实际时间晚好几秒排查很久才发现要把毫秒的低8位放前面。后来我养成了一个习惯凡是涉及多字节数值统一先按规约里的字节序定义写死一个测试用例再套进正式代码。5.5 扩展ASDU类型厂家自定义的“方言”104规约规定类型标识取值范围是0到255标准定义了一部分但很多厂家会在自己的装置里加入私有类型。这类数据不是瞎编的一般在厂家的通信规约文档里会有明确说明。我在解析工具里维护了一个“扩展类型表”格式是类型标识 - (名称, 解析函数)。遇到标准类型不认识的先查扩展表查不到就原样输出十六进制并标记为“未知类型”。这样既不会漏数据也不会误解析。一定要记住宁可标记为未知也不要猜测字段含义否则会把错误数据写进数据库后期特别难清洗。6. 做了三年解析器最后想说的三句话第一句解析工具本质上是规约理解的载体代码只是把理解固化下来。不要急着写代码先花时间把IEC 60870-5-101和104的标准文档翻一遍尤其是ASDU那张表。标准文档确实枯燥但每一帧报文的含义都在里面写得很清楚。第二句永远不要盲信工具输出也不要盲信自己写的代码。解析结果最好的验证方式是现场对照比如把装置面板上的遥信状态和解析工具显示的值逐一比对。我见过一个工具把遥信值的bit位解析反了导致主站画面显示全错最后是靠现场检修发现“断路器实际是合的画面上却是分”。第三句工具要面向“快速定位问题”来设计而不是面向“解析所有报文”来设计。加一个类型标识的中文名称映射、把传送原因翻译成短语、把异常帧用单独颜色标记出来这些看似不起眼的小功能在半夜被电话叫起来处理故障时会救你的命。最后分享一个我自己的习惯写完解析逻辑后我会刻意拿几段采集到的历史故障报文做回归测试把解析结果留档。下次再遇到类似问题直接对比差异往往几分钟就能定位根因。这种积累比任何技巧都管用。本文还有配套的精品资源点击获取

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

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

免费获取报价