
简介本资源是一份面向电力自动化领域开发者的IEC 104规约IEC 60870-5-104Java实现工程包聚焦SCADA系统中RTU/IED与调度主站间的网络通信开发需求适用于具备Java基础并从事电力监控、工业协议对接的中高级工程师。压缩包共106个文件含34个核心Java源码涵盖IEC104Client/Server框架、ASDU编解码、心跳管理及TCP传输控制逻辑、22个SISC配置文件用于规约参数与设备建模、13个sample示例含连接测试、遥信遥测模拟等典型场景以及XML配置、DOCX规范文档含广东电网104规约实施细则等辅助材料整体3.69MB结构完整、开箱即用。已有435人学习下载提供从协议原理到可运行代码的全链路支撑包含ASDU结构解析、TCU校验机制、异常重连策略及SSL安全扩展接口便于快速集成至实际项目或开展协议级调试验证。1. IEC 104规约不是“协议配置”而是电力远动系统里必须跑通的实时数据链路闭环你在调试主站与子站通信时ping得通、端口也开着但遥信变位死活不上送或者报文抓出来全是0x68开头却解析不出SOE时间戳又或者T1超时反复重发导致通道拥塞——这些都不是网络层问题而是IEC 104规约在应用层没对齐。它不是简单的“TCPASDU”拼接而是一套带状态机、超时控制、帧序号管理、类型标识映射的完整交互契约。电力调度自动化、配网终端FTU/DTU、新能源场站监控系统中只要涉及主站-厂站间遥控、遥调、遥测、遥信的实时交互就必须严格遵循IEC 60870-5-1042006或EN 60870-5-104:2006定义的控制域、可变结构限定词、传输原因、公共地址、信息体地址等字段语义。本文面向已具备TCP/IP和SCADA基础的工程师不讲标准文档翻译只拆解从建链、心跳、召唤、响应到异常恢复的全链路实操路径覆盖参数设置逻辑、报文字段定位、常见解析陷阱及现场验证方法。2. 用libiec60870在Linux下跑通IEC 104最小主站建链心跳总召唤三步闭环IEC 104本质是基于TCP的APDUApplication Protocol Data Unit传输但直接socket发原始字节极易出错。工业现场主流做法是采用经过电力行业长期验证的开源库其中libiec60870GitHub上mz-automation/libiec60870因其轻量、无依赖、支持C/C嵌入且API贴近标准定义成为调试阶段首选。它不封装业务逻辑只提供APDU构造/解析、连接管理、定时器回调等底层能力这恰恰适合需要看清每一帧含义的工程师。2.1 编译与链接静态库方式避免运行时依赖冲突# 克隆并编译注意必须指定CMAKE_BUILD_TYPEReleaseDebug模式下定时器精度不足 git clone https://github.com/mz-automation/libiec60870.git cd libiec60870 mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DBUILD_TESTSOFF -DBUILD_EXAMPLESOFF .. make -j$(nproc) sudo make install提示libiec60870默认安装到/usr/local/lib若系统使用/usr/lib路径需在/etc/ld.so.conf.d/下新增libiec60870.conf并执行sudo ldconfig否则ldd your_app会提示not found。2.2 主站初始化设置T1/T2/T3超时参数与公共地址映射IEC 104的可靠性高度依赖三个关键超时参数T1发送U格式如STARTDT后等待U格式确认的最大时间典型值15sT2发送I格式含数据后等待I格式确认的最大时间典型值10sT3空闲状态下发送测试帧TESTFR的最大间隔典型值20s这些值必须与子站配置严格一致否则触发重传风暴。libiec60870通过IEC60870_5_104_Master结构体设置#include iec60870/iec60870_5_104.h int main() { // 创建主站实例 IEC60870_5_104_Master master IEC60870_5_104_Master_create(); // 设置超时参数单位毫秒 IEC60870_5_104_Master_setT1(master, 15000); // T115s IEC60870_5_104_Master_setT2(master, 10000); // T210s IEC60870_5_104_Master_setT3(master, 20000); // T320s // 设置子站公共地址即ASDU中的COT字段前的地址通常为1~255 IEC60870_5_104_Master_setCommonAddress(master, 1); // 启动连接阻塞直到建链成功或超时 if (!IEC60870_5_104_Master_start(master, 192.168.10.100, 2404)) { fprintf(stderr, 连接失败\n); return -1; } printf(主站已连接至192.168.10.100:2404\n); return 0; }参数说明IEC60870_5_104_Master_setCommonAddress()设置的是子站地址即APCI中的公共地址不是主站自身地址。该值需与子站配置的“主站允许访问的地址列表”匹配。start()函数内部会自动发送STARTDT ACT0x68 04 07 00 00 00并等待子站返回STARTDT CON0x68 04 0B 00 00 00。若子站未响应libiec60870会在T1超时后重发最多3次可调。2.3 发送总召唤并解析响应从APDU到遥信点表的逐层解包建链成功后必须发送总召唤TypeID100获取全量遥信/遥测数据。libiec60870提供IEC60870_5_104_Master_sendInterrogationCommand()封装此过程// 发送总召唤QOI20即召唤所有类型数据 if (!IEC60870_5_104_Master_sendInterrogationCommand(master, 20)) { fprintf(stderr, 总召唤发送失败\n); } // 注册接收回调关键所有I格式报文在此处理 IEC60870_5_104_Master_setASDUReceivedHandler(master, asdu_received_handler, NULL); // 进入事件循环实际项目中应结合epoll/select while (IEC60870_5_104_Master_isRunning(master)) { IEC60870_5_104_Master_processEvents(master); usleep(10000); // 10ms轮询间隔 }回调函数解析ASDU核心逻辑void asdu_received_handler(void* parameter, IEC60870_5_104_Master master, Connection connection, ASDU asdu) { uint8_t type_id ASDU_getTypeID(asdu); if (type_id 1) { // 单点遥信M_SP_NA_1 int num_items ASDU_getNumberOfElements(asdu); for (int i 0; i num_items; i) { InformationObject io ASDU_getElement(asdu, i); uint32_t ioa InformationObject_getObjectAddress(io); // 信息体地址 uint8_t value M_SP_NA_1_getValue((M_SP_NA_1) io); // 0分1合 printf(遥信%d: 地址%u, 状态%u\n, i1, ioa, value); } } }注意ASDU_getTypeID()返回的是IEC 104标准定义的类型标识如1单点遥信3双点遥信13遥测值而非报文中的原始字节。libiec60870已做标准化映射开发者无需手动解析0x01、0x03等十六进制值。3. 抓包分析IEC 104报文Wireshark过滤字段精确定位K/W/T1/T2/T3验证当通信异常时Wireshark是唯一能穿透TCP层看到APDU细节的工具。但默认Wireshark不识别IEC 104需加载自定义解码器或使用过滤表达式人工定位。重点不是“看懂全部”而是快速定位控制域、类型标识、信息体地址、传输原因四个关键字段。3.1 Wireshark过滤与着色规则聚焦APDU起始与关键字段IEC 104 APDU固定以0x68开头启动字符后跟长度字段L。最简过滤表达式tcp.port 2404 tcp.len 0 tcp.payload matches :68:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}此表达式捕获所有含0x68且长度非零的TCP载荷。为提升效率建议创建着色规则规则名IEC104_APDUFiltertcp.port 2404 tcp.payload[0] 0x68Color蓝色背景这样所有IEC 104报文在包列表中高亮显示。3.2 手动解析APDU结构从68 L 控制域到ASDU的逐字节对照以典型总召唤响应报文为例Hex Dump0000 68 1b 00 00 00 00 64 01 06 00 01 00 01 00 00 00 h.......d....... 0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0020 00 00 00 00 ....字节偏移值Hex字段名含义说明068启动字符固定值标识APDU开始11b长度L0x1b 27表示APDU总长含启动字符2-500 00 00 00控制域APCI第1字节00固定格式第2字节00无测试第3字节00无负确认第4字节00无附加信息6-764 01类型标识TypeID0x0164 356→ 对应标准中M_ME_NA_1归一化值806可变结构限定词VSQbit70无序列bits6-06→ 本ASDU含6个信息体9-1000 01传输原因COT0x0100 256→ACTCON激活确认11-1200 01公共地址Common Address子站地址113-1400 00信息体地址IOA低16位实际地址需结合后续字节此处省略高位提示Wireshark中右键0x68→Decode As→选择Raw可强制按字节显示避免自动解析干扰。关键字段如TypeID、COT、IOA必须与主站代码中设置的值完全一致否则子站拒绝响应。3.3 K/W/T1/T2/T3参数在报文中的体现与超时验证方法K、W参数不显式出现在APDU中而是隐含在发送窗口与接收窗口机制里K主站最大未确认I帧数默认12决定S帧确认帧中接收序号的更新频率W子站最大未确认I帧数默认8决定其发送I帧的节奏验证方法在Wireshark中过滤tcp.port2404 and tcp.flags.ack1 and tcp.flags.push0观察S帧出现频次。若主站连续发送多个I帧如遥信变位遥测上送后子站未及时回S帧则W值过小导致子站缓冲区满后续I帧被丢弃。T1/T2/T3超时则通过时间戳差值验证记录STARTDT ACTU帧发送时刻T0记录STARTDT CONU帧接收时刻T1计算T1-T0若15sT1值则T1超时已触发重发Wireshark中可见重复U帧同理对I帧记录I帧发送时刻T2对应S帧接收时刻T3T3-T210s即T2超时。4. IEC 104与IEC 101规约的核心差异物理层、帧结构、应用场景的硬边界很多工程师误以为“104就是101走TCP”实际二者在帧结构设计、错误恢复机制、应用场景约束上存在不可互换的差异。混淆会导致主站无法正确解析子站报文或子站拒绝响应。4.1 物理层与链路层101依赖串行链路104强耦合TCP可靠性维度IEC 60870-5-101IEC 60870-5-104传输介质RS-232/485串口点对点或轮询TCP/IP网络支持多主站、广播、NAT穿透帧校验1字节CS异或校验依赖TCP校验和APDU层无额外校验连接管理无连接概念靠链路空闲超时维持显式建链STARTDT、断链STOPDT、心跳TESTFR地址空间单字节地址0~255双字节地址0~65535支持更大规模厂站提示101规约中0x10START和0x20STOP是链路服务命令而104中0x68是APDU启动符二者字节含义完全不同。试图用101解析器读104报文必然在0x68处报“非法起始符”。4.2 APCI与ASDU结构104增加启动字符与长度字段取消101的链路地址IEC 101的APCIApplication Protocol Control Information包含控制域4字节地址域1字节链路地址帧校验1字节IEC 104的APCI则变为启动字符0x681字节长度L1字节APDU总长-2控制域4字节无链路地址字段地址由TCP五元组隐含这意味着同一台子站若同时支持101和104其公共地址配置必须独立设置。101的地址写在串口配置里104的地址写在TCP服务端配置中二者数值可以相同但无关联。4.3 应用场景硬约束104不可用于低压台区101不适用广域网IEC 104强制要求端到端TCP连接因此在以下场景失效低压智能电表集中器-电表间为PLC/LoRa无TCP栈移动基站监控GPRS网络延迟抖动大T1/T2易超时无公网IP的分布式光伏需穿透NAT必须配合主站主动拨号IEC 101因无连接状态天然适应高误码率链路配电台区RS-485总线线缆老化导致偶发丢帧水电站光纤环网单点故障不影响其他节点老旧RTU设备仅支持串口固件无TCP协议栈注意“国网698规约”是另一套独立标准Q/GDW 1376.1-2013面向用电信息采集系统与104无继承关系。其报文结构、加密方式、心跳机制均不同不可混用。5. 现场排错黄金三步法从TCP连接日志、APDU统计、ASDU字段校验定位根因当IEC 104通信中断不要急于重启设备。按以下顺序检查90%问题可在5分钟内定位5.1 第一步确认TCP连接状态与基础参数一致性在主站服务器执行# 检查到子站的TCP连接是否ESTABLISHED ss -tnp | grep :2404 | grep 192.168.10.100 # 查看连接队列若Recv-Q持续0说明主站未及时读取socket缓冲区 ss -i sport :2404 # 验证T1/T2/T3是否与子站配置一致登录子站Web界面或串口配置工具 # 重点核对T1启动确认超时、T2数据确认超时、公共地址、类型标识支持列表5.2 第二步APDU级统计——区分是建链失败还是数据交互失败启用libiec60870内置统计需编译时开启-DENABLE_STATISTICSON// 在master创建后添加 IEC60870_5_104_Master_enableStatistics(master); // 定期打印统计如每60秒 IEC60870_5_104_Master_printStatistics(master, stdout);关键指标解读指标名正常范围异常含义sentUFrameCount建链期0若为0说明未发出STARTDT ACTreceivedUFrameCount≥1若为0子站未响应若1但无I帧子站未进入数据态sentIframeCount总召唤后0若为0主站未发召唤若0但receivedSFrameCount0T2超时receivedInvalidApduCount00表示子站发了非法APDU如长度超限、控制域错误5.3 第三步ASDU字段级校验——用Python脚本快速验证遥信点表映射编写轻量脚本解析pcap文件中的ASDU验证信息体地址IOA是否与现场点表一致# parse_iec104_pcap.py import dpkt import sys def parse_asdu(payload): if len(payload) 6 or payload[0] ! 0x68: return None length payload[1] if len(payload) 6 length: return None # 跳过APCI6字节定位ASDU起始 asdu_start 6 type_id (payload[asdu_start1] 8) | payload[asdu_start] vsq payload[asdu_start2] cot (payload[asdu_start4] 8) | payload[asdu_start3] ioa (payload[asdu_start7] 16) | (payload[asdu_start6] 8) | payload[asdu_start5] return {type_id: type_id, vsq: vsq, cot: cot, ioa: ioa} with open(sys.argv[1], rb) as f: pcap dpkt.pcap.Reader(f) for ts, buf in pcap: eth dpkt.ethernet.Ethernet(buf) if isinstance(eth.data, dpkt.ip.IP): ip eth.data if isinstance(ip.data, dpkt.tcp.TCP): tcp ip.data if tcp.dport 2404 or tcp.sport 2404: if tcp.data: result parse_asdu(tcp.data) if result and result[type_id] 1: # 单点遥信 print(f遥信IOA{result[ioa]}, COT{result[cot]})运行python parse_iec104_pcap.py capture.pcap输出示例遥信IOA1001, COT20COT20响应召唤若输出IOA为0或65535说明子站配置的点表地址未正确加载若COT恒为48未知原因说明子站固件版本不支持该召唤类型。最后技巧在子站侧开启“报文镜像”功能如有将原始APDU直接输出到串口或日志文件比抓包更精准。镜像内容通常为ASCII十六进制可用xxd -r转为二进制再用上述脚本解析彻底绕过TCP层干扰。本文还有配套的精品资源点击获取