
简介这是一份用C语言实现的IEC 60870-5-104协议源代码库适合电力自动化、工业通信领域的开发者及协议学习者使用。通过阅读代码可掌握ASDU与APDU报文结构、M/C/I等类型定义、连接管理、报文编码及错误处理机制也可作为基于TCP/IP实现设备通信的工程参考。压缩包共111个文件以39个C源码、32个头文件为主搭配Makefile、说明文档、TLS证书等便于在Linux环境编译调试与二次开发。资源包仅877KB结构紧凑核心模块包括CS101链路层、CS104主站/从站、连接管理及TLS支持。当前已有429人学习下载适合希望深入理解IEC104协议细节、从事电力系统数据交互开发的读者作为学习与实践素材。 看到“IEC104源代码仅供自己学习使用”这句标注第一反应就是这哥们儿跟我当年一样手里攥着一套电力规约源码打算找个周末把它啃明白。写过这句话的人都知道它不是为了免责更多是提醒自己先把协议搞透别拿生产环境开玩笑。如果你也是刚拿到这样一套IEC104源代码想读懂它、跑通它、甚至改成自己能用的东西那这篇东西就是给你准备的。我会从协议基础、代码结构、调试环境、常见坑点四个方向往下聊最后给出一条从“读懂”到“改造”的学习路线。内容不烧脑但每一步都是我实际踩过、验证过的做法可以直接照着操作。1. 在碰代码之前先把IEC104的底子打好1.1 IEC104到底是怎么回事为什么值得读它的源码IEC104全称是IEC 60870-5-104属于电力远动系统中主站和子站之间的通信规约底层基于TCP/IP传输。它解决的核心问题很朴素把变电站、光伏电站、储能站里的遥测、遥信、遥控、遥调数据用一种双方都认得的格式搬到调度主站去。现在分布式能源接入越来越多104规约仍然是电力自动化领域绕不开的“通用语言”。读它的源代码你不会只学到104本身。一套完整的IEC104实现里包含TCP长连接上的协议状态机设计、ASDU这类二进制报文的编解码套路、收发序号的滑动窗口确认机制还有和硬件点表对接的工程经验。这些技能放在工业通信、物联网网关开发里都通用。源码里那些看似不起眼的模块恰恰是平时文档里最容易一带而过、但实际工程里最关键的部分。1.2 必须记住的几个“硬概念”端口、帧、ASDU我见过不少人拿到代码就往上冲结果被APCI、ASDU、启动字符这些词砸晕。读源码之前先把下面几个概念焊死在脑子里后续看代码会顺畅很多。IEC104固定使用TCP 2404端口规约规定是给远动设备的。所有报文都叫APDU应用规约数据单元结构上分两块APCI应用规约控制信息 ASDU应用服务数据单元。APCI的前两个字节固定是0x68和后面的长度。控制域四个字节用来区分帧类型帧类型判定条件作用I帧控制域第1字节bit00承载应用数据带发送序号N(S)和接收序号N(R)S帧第1字节bit01且bit10只带接收序号N(R)做确认用U帧第1字节bit01且bit11启停传输、测试链路不承载ASDUU帧里最常见的是STARTDT启动数据传输0x07 00 00 00、STOPDT停止传输0x13 00 00 00、TESTFR测试帧0x83 00 00 00。连接的建立流程基本固定先TCP握手然后主站发STARTDT激活从站回0x0B 00 00 00确认之后才能传ASDU数据。ASDU部分才是业务核心。打开一个ASDU依次是类型标识、结构限定词、传送原因、公共地址、信息对象地址、信息元素。类型标识决定这条报文是干什么的几个必须要背下来的类型标识名称用途1M_SP_NA_1单点遥信3M_DP_NA_1双点遥信9M_ME_NA_1归一化遥测13M_ME_NC_1短浮点遥测用得非常多45C_SC_NA_1单点遥控100C_IC_NA_1总召唤103C_CS_NA_1时钟同步传送原因COT里6表示激活7表示激活确认20表示响应站召唤3表示突发。总召唤的流程就是主站发类型100、传送原因6的请求从站先回一个传送原因7的确认然后把全站数据用传送原因20的报文上送。这些概念拿一张纸抄下来贴在显示器边上读代码时随时对照。2. 拿到源代码之后怎么下手读2.1 先看目录和入口确认代码属于哪一层一套典型的IEC104源码不管主站还是从站模块划分通常都很接近。拿到代码第一件事不是打开文件就看而是先看目录结构把文件按职责归类。我常用的判断方式是这样的名字带socket、tcp、link的基本是链路层负责TCP连接、收发、重连。名字带apci、frame、codec的是编解码核心处理APCI和ASDU的打包、拆包。名字带asdu、object的处理具体信息对象比如遥测、遥信怎么解析。名字带station、process、rtu、map的是业务映射层把协议报文和实际采集点号对应起来。入口文件名通常是main.c、server.c或client.c里面就是程序启动和主循环。从站代码的入口逻辑差不多是固定的初始化监听socket绑定2404端口进入循环accept连接每个连接要么起一个线程要么放事件循环里然后进入一个类似process_frame()的函数处理收到的每一帧。如果你打开入口文件看到这几行代码的路数那说明这套源码结构比较规范可以放心往下读。2.2 跟一条报文从网口到业务回调我推荐的阅读方式不是从第一行读到最后一个文件而是“跟一条真实的报文走完整条链路”。最容易跟的就是总召唤。下面这段代码是一个典型的ASDU解析函数我简化了字段拆解的过程static int parse_asdu(const uint8_t *buf, int len, asdu_t *asdu) { asdu-type_id buf[0]; // 类型标识 asdu-vsq buf[1]; // 结构限定词 asdu-cot (buf[3] 8) | buf[2]; // 传送原因低字节在前 asdu-common_addr (buf[5] 8) | buf[4]; // 公共地址低字节在前 asdu-ioa (buf[8] 16) | (buf[7] 8) | buf[6]; // 信息对象地址3字节低在前 // 后面再根据type_id走不同的信息元素解析分支 return 0; }注意这段代码里我特意注释了“低字节在前”。IEC104里传送原因、公共地址这两个多字节字段在APCI之后都是低字节在前信息对象地址也是3字节低字节在前。因为底层走TCP本身是字节流不存在网络字节序转换的问题所以很多人想当然地用大端去解结果解出来的地址完全不对。这是新手最容易踩的坑后面排查章节我还会再说。跟报文的具体路径是这样的从recv()收到原始字节流开始第一步检查buf[0]是不是0x68不是就直接丢弃或者等下一个字节第二步看控制域的bit0和bit1判断是I帧、S帧还是U帧如果是I帧取出收发序号接着把后面的数据丢给ASDU解析函数。解析完成后根据类型标识分发到不同的业务回调比如类型100就触发全站点表扫描类型13就更新某个遥测变量的值。关键点在于整个链条里的函数调用关系就是协议规范的代码化表达。你跟着走一遍把每个函数的输入输出记下来比单看任何注释都管用。3. 边跑边读搭一套能抓包的调试环境3.1 工具选型模拟器、抓包、代码阅读干读源码很容易读着读着就困了。我的经验是先把环境跑起来让代码“动”给你看。工具不需要多三样就够。模拟主站/从站主站侧可以用开源IEC104客户端网上有很多GitHub搜IEC104 client就能找到。也有人用IEC104 Client Simulator这类商业工具注意官方有试用版搜破解码这种事就别干了一个是不安全另一个是完全没必要开源工具已经足够学习用了。抓包工具Wireshark这是必需品。它自带IEC104协议解析器能把APDU的每个字段自动拆开和代码里打印的报文一对比哪里对不上立刻就能看出来。代码阅读VS Code配个clangd或者直接用Source Insight都行。我不建议一开始就上高级调试器先用“加打印看报文”的方式把逻辑读通再碰断点。我自己的做法是在源码里关键函数入口加一个打印函数把收到的原始hex和解析后的结构体都打出来然后和Wireshark抓到的包做对照。这样做的好处是你不仅知道代码“干什么”还能现场看到它“怎么干”。3.2 抓包对比源码把每一帧拆开看操作流程特别简单我顺手写一下本机跑起从站程序监听2404端口。用模拟主站连接触发一次总召唤。Wireshark抓loopback回环接口过滤条件填tcp.port 2404。双击一条总召唤报文看Wireshark解析出的APDU结构。在源码的组帧函数里把拼出来的原始数据也打印出来逐字节对照。我自己实测的时候对照过一帧短浮点遥测上送报文代码里组出来的hex是68 1D 02 00 06 00 0D 01 03 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00。前两个字节68 1D是启动字符和长度02 00表示发送序号为106 00表示接收序号为30D 01是类型13和结构限定词03 00是传送原因3突发接着是公共地址和信息对象地址最后跟着4字节浮点数据和品质位。这一行hex把整个ASDU的结构全部串起来了。当你能做到“看到hex就猜到代码下一步做什么”的程度这套源码对你来说就不再是黑盒了。4. 源码阅读阶段最容易踩的坑4.1 能连上但激活不了多半是U帧解析问题现象很典型TCP三次握手成功了主站也发07 00 00 00过去了从站就是没反应。我排查过的案例里大部分问题出在从站收到U帧后没有正确判断控制域。U帧的判断逻辑是控制域第一字节等于0x07、0x0B、0x13、0x23、0x83、0xA3这类值其中低4位是功能码高两位是确认位。有些源码写判断的时候只看了一个字节把0x07和0x0B当成普通数据处理自然会走到错误分支。排查方法很简单在接收函数入口打印第一帧的控制字段看它走的是U帧分支还是I帧分支。如果没走U帧分支那问题就在帧类型判断的位运算写错了。另一个常见原因是从站要求的公共地址和主站发的不一致导致从站在ASDU解析阶段就把帧吞了。这种情况TCP连接是好的也不会回任何错误帧看起来很像“激活不了”实际上数据已经丢了。4.2 总召唤不回数据或回错数据总召唤不回数据先不要怀疑协议栈。优先级最高的排查顺序是类型标识是否是100、传送原因是否是6、公共地址是否匹配、点表是否为空。我见过一个案例从站程序里点表初始化没做地址映射全是0总召唤请求发过来之后扫描函数遍历了0个点自然什么都没有。回错数据则是另一个极端把遥测信息对象地址搞错了。IEC104的信息对象地址是3字节低字节在前很多源码注释里会写“按高位在前发送”结果编码的时候还真的把地址翻转过来了。遇到这种情况直接用Wireshark抓包对比主站发过来的请求看信息对象地址的字节顺序基本一眼就能定位。4.3 I帧序号对不上数据被对方丢弃I帧的发送序号和接收序号是IEC104保证数据可靠传输的核心机制。主站和从站各自维护一对序号发送方每发一个I帧发送序号加1接收方通过S帧或带确认的I帧把接收序号带回来。如果某台设备发过来的序号和预期不一致接收方会丢弃这一帧甚至有可能发送复位指令。排查这类问题最简单的是在收发函数里加一行日志打印控制域前两个字节的序号。经常遇到的情况是主站重启了从站没重启两边状态不一致从站还等着一个永远等不到的序号。这时候可以用STOPDT再重新STARTDT或者干脆重新建立TCP连接。你在源码里看到那些处理序号不一致的复位逻辑实际工程里就是这样被逼出来的。4.4 时标和品质位最容易被忽略的“隐性字段”很多调试问题不是通信断了而是数据“看起来不对”。比如主站画面上某条遥测值显示成无效但实际上数值明明在变化。这时候八成是品质描述符的问题。IEC104的信息元素里品质位的bit0到bit3分别表示IV(无效)、NT(不新)、SB(被取代)、BL(被封锁)。如果从站代码里给遥测数据的品质位填了一个无效标志主站展示时就会把这条数据置为无效哪怕值是对的。时标字段也有讲究。CP56Time2a是7字节毫秒占前两字节然后是分钟、小时、日、月年其中星期几藏在“日”字节的高3位里。我见过有源码在组时标的时候把星期几填错位置结果主站收到的日期是对的星期显示错。这类问题不影响通信但很容易让联调人员怀疑人生。排查时记得把Wireshark解析出的时标和主站界面对比别只盯着通信层看。5. 从“读懂”到“改造”学习路线的最后一步5.1 读完从站代码下一步怎么走我的建议是读懂一套从站代码之后不要急着找下一套源码而是试着把它改成主站。主站和从站的区别不仅是主动连接和被动监听还在于主站要维护多个子站的连接状态、定时轮询、处理超时重发。这个过程会逼你把协议状态机的每个分支都走一遍比读十遍代码都有效。进阶一点的玩法是把这套代码移植到嵌入式板子上跑。相关热词里出现的RK3506这类平台就很典型。104协议本身的资源消耗不大它依赖的只有TCP协议栈、定时器和简单的内存管理交叉编译到ARM平台并不复杂但需要处理一些琐碎的差异比如字节对齐、大小端、time_t位数等。移植一遍你对这套代码的掌控度会上升一个台阶。5.2 个人实操中最重要的几点体会最后聊几句实在的。我在读IEC104源码这件事上最大的体会是协议栈代码读三遍不如自己抓两包。第一遍读代码你会觉得每个函数都懂但串不起来第二遍边跑边读才开始建立链路的概念第三遍带着问题去读比如“为什么不回数据”“序号为什么不对”效率是最高的。还有一个细节想提醒你手头有测试工具的时候先把一帧报文的hex打印和Wireshark解析对着看确定完全一致后再往上加业务逻辑。很多人在这一步偷懒结果后面所有问题都得回头查底层编码反而更慢。这套“抓包前置”的思路让我后来排查问题的时间缩短了一大半。学104源码的乐趣就在于它是一种很典型的“看十遍不如动手一遍”的知识只要你愿意跑起来很快就能从读代码的人变成改代码的人。本文还有配套的精品资源点击获取