news 2026/10/20 18:38:48

IEC104主站Java实现:微电网通信协议栈与调试指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IEC104主站Java实现:微电网通信协议栈与调试指南

简介:本资源是一套基于Java实现的IEC104协议主站客户端程序,属于微电网管理系统的一部分,面向从事电力自动化、配电监控及微电网系统开发的工程师与学习者。程序围绕IEC104这一基于TCP/IP的电力行业通信协议展开,涵盖U帧、S帧、I帧三种帧格式的解析与应答式数据传输逻辑,可实现遥信、遥测上行采集与遥控、遥调下行控制,帮助读者理解主站客户端与从站服务端之间的通信机制。压缩包共105个文件,约3.59MB,包含30个class、23个java源码、22个png图示、10个json配置、9个jar依赖及properties、xml等工程文件,源码与依赖齐全,便于直接导入调试。目前已有734人学习下载,适合作为微电网通信模块开发、协议解析学习与工程二次开发的参考实例。

1. IEC104_microgrid:一个Java实现的IEC104主站客户端,微电网管理系统的通信底座

如果你在电力自动化或新能源行业待过,大概率听过IEC 60870-5-104这个规约。它跑在TCP之上,用来做变电站和调度主站之间的远动通信。微电网管理系统里,主站需要实时采集逆变器、储能PCS、电表等设备的数据,同时下发功率调度指令——这套通信链路的核心,就是一个稳定的IEC104主站客户端。IEC104_microgrid这个项目,用Java把主站侧的协议栈完整实现了一遍,包括APCI帧解析、ASDU报文组装、总召唤、遥测遥信上送处理、遥控遥调下发等。它适合两类人:一是做微电网EMS/SCADA的Java后端,需要把104协议集成进自己的系统;二是想学电力规约怎么落地到代码里的开发者,拿它当参考实现比啃规约文档快得多。下面我从项目结构、核心代码、运行方式到二次开发,把它拆开讲一遍。

2. 项目结构与技术选型:为什么用Java做104主站

2.1 整体模块划分

这个项目的代码组织遵循典型的通信中间件分层思路,从上到下大致分为四层:

  • 应用层:对外暴露的API接口,业务系统通过它来发送总召唤、遥控、遥调命令,以及注册数据上送的回调。
  • 协议层:IEC104的核心实现,包括APDU拆包/组包、ASDU类型识别与分发、时序控制(t1/t2/t3定时器)、序号管理(发送序号VS和接收序号VR)。
  • 传输层:基于Netty或原生Socket的TCP客户端,负责与RTU/网关建立长连接、断线重连、心跳维持。
  • 数据层:遥测(YC)、遥信(YX)、遥控(YK)、遥调(YT)的数据模型和缓存,通常用ConcurrentHashMap按信息对象地址(IOA)索引。

常见做法是把协议层做成无状态的编解码器,传输层只管字节流收发,应用层通过回调或Future拿到结果。这样换传输框架(比如从Netty换成Mina)时,协议层代码不用动。

2.2 技术栈与依赖

项目基于Java 8+,构建工具用Maven。核心依赖通常包括:

依赖用途
Netty 4.xTCP长连接、编解码链、心跳
SLF4J + Logback日志,规约调试时关键
FastJSON / Jackson如果需要把采集数据转JSON上送
JUnit协议编解码的单元测试

选Netty而不是原生Socket,主要因为104主站需要处理粘包/拆包、空闲检测、断线重连这些事,Netty的LengthFieldBasedFrameDecoder和IdleStateHandler能省不少代码。当然,如果项目规模很小,用Socket加一个读线程也能跑,但维护成本会高一些。

2.3 目录结构参考

一个典型的目录长这样:

iec104-microgrid/ ├── src/main/java/ │ ├── com/microgrid/iec104/ │ │ ├── frame/ # APCI帧结构:I帧、S帧、U帧 │ │ ├── asdu/ # ASDU类型定义与编解码 │ │ ├── session/ # 会话管理、序号控制、定时器 │ │ ├── transport/ # Netty客户端、重连策略 │ │ ├── model/ # 遥测遥信遥控数据模型 │ │ └── api/ # 对外接口 │ └── resources/ │ └── logback.xml ├── src/test/java/ # 编解码测试 └── pom.xml

这个结构的好处是,你拿到源码后能快速定位:要改协议细节去frame和asdu,要改连接行为去transport,要加新功能在api层扩展。

3. 核心协议实现:从APCI帧到ASDU报文的完整链路

3.1 APCI帧的三种类型与编解码

IEC104的APDU由APCI(控制域)和ASDU(数据单元)组成。APCI固定6字节:1字节启动字符0x68,1字节长度,4字节控制域。控制域决定了帧类型:

  • I帧:信息传输帧,控制域第1位为0,包含发送序号VS和接收序号VR。
  • S帧:确认帧,控制域第1位为1、第2位为0,只含接收序号VR。
  • U帧:控制帧,控制域第1位为1、第2位为1,用于启动、停止、测试。

下面是一个I帧的编码示例:

// I帧编码:控制域4字节 = VS左移1位 | VR左移1位 public byte[] encodeIFrame(int vs, int vr, byte[] asduBytes) { int ctrl1 = (vs & 0x7FFF) << 1; // 发送序号,bit0固定0 int ctrl2 = (vr & 0x7FFF) << 1; // 接收序号,bit0固定0 ByteBuffer buf = ByteBuffer.allocate(6 + asduBytes.length); buf.put((byte) 0x68); // 启动字符 buf.put((byte) asduBytes.length); // APDU长度(不含启动字符和长度本身) buf.put((byte) (ctrl1 & 0xFF)); // 控制域第1字节 buf.put((byte) ((ctrl1 >> 8) & 0xFF)); // 控制域第2字节 buf.put((byte) (ctrl2 & 0xFF)); // 控制域第3字节 buf.put((byte) ((ctrl2 >> 8) & 0xFF)); // 控制域第4字节 buf.put(asduBytes); return buf.array(); }

这里的关键参数是vs和vr,它们各占15位,范围0~32767,超过后回绕到0。发送每一条I帧后VS加1,收到对端I帧后VR加1。如果序号对不上,说明链路出了问题,需要触发链路复位。实际调试时,序号错乱是最常见的翻车点之一,后面避坑章节会细说。

3.2 ASDU类型识别与分发

ASDU是104的载荷,类型标识(TypeID)决定它是什么报文。微电网场景下常用的类型有:

TypeID含义方向
100总召唤命令主站→从站
101总召唤确认从站→主站
103时钟同步主站→从站
9归一化遥测值从站→主站
13浮点遥测值从站→主站
1单点遥信从站→主站
45单点遥控命令主站→从站
49归一化遥调命令主站→从站

分发逻辑通常用一个Map把TypeID映射到处理器:

// ASDU分发器:根据类型标识路由到对应处理器 public class AsduDispatcher { private final Map<Integer, AsduHandler> handlers = new HashMap<>(); public void register(int typeId, AsduHandler handler) { handlers.put(typeId, handler); } public void dispatch(AsduFrame frame) { AsduHandler handler = handlers.get(frame.getTypeId()); if (handler == null) { log.warn("未注册的ASDU类型: {}", frame.getTypeId()); return; } try { handler.handle(frame); } catch (Exception e) { log.error("ASDU处理异常, typeId={}", frame.getTypeId(), e); } } }

AsduFrame里封装了类型标识、可变结构限定词、传送原因、公共地址、信息对象地址列表和具体值。处理器注册在会话初始化时完成,比如把TypeID=9注册为遥测处理器,收到后更新本地缓存并触发业务回调。

3.3 总召唤流程与时序控制

总召唤是主站上线后的第一个动作,用来获取从站的全部数据。流程是:主站发总召唤命令(TypeID=100,传送原因=6激活),从站回确认(TypeID=101),然后从站依次上送遥信、遥测数据,最后发一个总召唤结束(TypeID=101,传送原因=10激活终止)。

时序控制靠三个定时器:

  • t1:发送I帧后等待确认的超时,默认15秒。超时未收到S帧或I帧确认,断开连接。
  • t2:收到I帧后最迟发送S帧确认的时间,默认10秒,必须小于t1。
  • t3:链路空闲时发送测试帧的间隔,默认20秒。
// 简化的t1定时器:发送I帧后启动,收到确认后取消 public void startT1Timer() { t1Future = scheduler.schedule(() -> { log.error("t1超时,链路可能断开,触发重连"); session.close(); reconnect(); }, t1Millis, TimeUnit.MILLISECONDS); } public void onAckReceived() { if (t1Future != null) { t1Future.cancel(false); t1Future = null; } }

这三个定时器的值在规约里有推荐范围,但实际项目中要根据网络质量调整。t1设太短,网络抖动就断链;设太长,真断了半天才发现。我一般会在配置里暴露出来,现场调试时用。

4. 运行与调试:把主站客户端跑起来

4.1 环境准备与配置

跑这个项目需要JDK 8以上、Maven 3.6+。如果只是本地验证协议逻辑,不需要真实的RTU设备,可以写一个模拟从站的测试类,或者用开源的104模拟器。配置项通常放在application.properties或YAML里:

iec104.master.host=192.168.1.100 iec104.master.port=2404 iec104.master.commonAddress=1 iec104.master.t1=15000 iec104.master.t2=10000 iec104.master.t3=20000 iec104.master.reconnectInterval=5000

commonAddress是公共地址,也叫ASDU地址,用来区分不同从站。微电网里如果有多台设备,每台的公共地址不能重复。

4.2 启动流程与关键日志

启动类通常长这样:

public class Iec104MasterApplication { public static void main(String[] args) { Iec104Config config = Iec104Config.load("application.properties"); Iec104Master master = new Iec104Master(config); // 注册数据上送回调 master.registerTelemetryListener((ioa, value, quality) -> { log.info("遥测上送: IOA={}, value={}, quality={}", ioa, value, quality); // 业务处理:写入时序库、判断越限等 }); master.registerTelemetryListener((ioa, value, quality) -> { log.info("遥信上送: IOA={}, value={}", ioa, value); }); master.start(); // 建立TCP连接,发送STARTDT,启动总召唤 } }

启动后日志里应该能看到:TCP连接建立、发送U帧STARTDT、收到STARTDT确认、发送总召唤命令、收到总召唤确认、遥测遥信数据陆续上送、总召唤结束。如果卡在某一步,对照日志定位。

4.3 用模拟器验证

没有真实设备时,可以用Python写一个简易的104从站模拟器来验证主站行为。核心是监听2404端口,收到总召唤后回确认并构造几条遥测数据:

# 简易104从站模拟:仅用于验证主站总召唤流程 import socket, struct def build_asdu(type_id, cause, common_addr, ioa, value): # 构造ASDU:类型标识+可变结构限定词+传送原因+公共地址+信息对象地址+值 asdu = struct.pack('BBBHB', type_id, 1, cause, common_addr, ioa) asdu += struct.pack('f', value) # 浮点遥测 return asdu s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 2404)) s.listen(1) conn, addr = s.accept() print('主站已连接:', addr) # 后续处理STARTDT、总召唤、数据上送...

这个模拟器不用写完整,能回总召唤确认和几条数据就够了。主站收到数据后打印日志,说明协议链路通了。

5. 避坑与常见问题:调试104主站时踩过的坑

5.1 序号错乱导致链路复位

现象:运行一段时间后,日志里出现“接收序号不连续”或“发送序号确认异常”,然后链路断开重连。

原因:VS和VR是15位循环序号,每发一个I帧VS加1,每收一个I帧VR加1。如果代码里在重发或异常分支里多加了或漏加了序号,就会错位。另一个常见原因是S帧确认没有及时发出,对端t1超时后断开。

解决:把序号管理集中到一个类里,所有增减操作走同一个方法,加日志打印每次变化。收到I帧后立即更新VR并检查是否需要发S帧,不要拖到业务处理完再发。

5.2 总召唤数据不完整

现象:总召唤后只收到部分遥测,或者总召唤结束帧一直不来。

原因:从站上送数据是分帧的,一帧ASDU里可能包含多个信息对象。如果解析时只取了第一个IOA,后面的就丢了。另外,总召唤结束的判断要看传送原因是否为10(激活终止),不能只看TypeID。

解决:解析ASDU时遍历所有信息对象,每个都回调。总召唤结束单独处理,收到后标记本轮总召唤完成。如果长时间没收到结束帧,可以设一个超时,超时后重新发起总召唤。

5.3 遥控命令没有返回到位信息

现象:下发遥控命令后,从站回了激活确认,但一直没有激活终止或遥控返校信息。

原因:104遥控是“选择-执行”两阶段,先发选择命令(传送原因=6),收到确认后再发执行命令(传送原因=6),最后从站回激活终止(传送原因=10)。如果代码里把选择和执行合并成一步,或者没等确认就发执行,从站会拒绝。

解决:把遥控流程拆成状态机:选择→等待选择确认→执行→等待执行确认→等待终止。每个状态设超时,超时后回滚。

5.4 网络抖动导致频繁断连

现象:现场网络不稳定时,主站频繁重连,数据采集断断续续。

原因:t1设得太短,或者重连间隔太短,导致还没恢复就再次发起连接。

解决:t1适当放大到20~30秒,重连间隔用指数退避,第一次5秒,第二次10秒,最多到60秒。同时开启TCP的keepalive,让操作系统帮忙检测死连接。

5.5 公共地址配错导致数据对不上

现象:连接正常,总召唤也有响应,但遥测值全是0或者明显不对。

原因:公共地址(ASDU地址)配错了,主站发的总召唤指向了错误的从站,或者从站上送的数据公共地址和主站预期的不一致,被过滤掉了。

解决:抓包看ASDU里的公共地址字段,和从站配置核对。微电网里如果通过网关汇聚多台设备,网关可能会做地址映射,要确认映射关系。

6. 二次开发:扩展点与改造思路

拿到这个项目后,二次开发通常集中在几个方向:接入新的设备类型、对接自己的数据平台、增加协议扩展。

扩展ASDU类型:如果设备用了非标准类型标识,在AsduDispatcher里注册新的处理器即可。处理器实现AsduHandler接口,在handle方法里解析自定义载荷。注意保持编解码的无状态性,不要在处理器里持有会话状态。

对接时序数据库:遥测数据上送频率可能很高,直接写库会成瓶颈。我一般会在回调里先写内存队列,再用批量线程写入InfluxDB或TDengine。队列用Disruptor或LinkedBlockingQueue都行,关键是批量提交,比如每500条或每1秒刷一次。

增加WebSocket推送:如果前端需要实时看数据,可以在数据回调里加一个WebSocket广播。用Netty的ChannelGroup管理所有前端连接,收到遥测后遍历发送。注意做背压,前端消费慢的时候别把内存撑爆。

协议一致性测试:改完协议层代码后,一定要跑一遍编解码的单元测试。我习惯用抓包工具录一段真实的104报文,存成二进制文件,测试时读进来解码再编码,对比字节是否一致。这个后悔药能省掉很多现场调试时间。

配置热更新:现场调试时经常要改t1/t2/t3或公共地址,如果每次都要重启就太慢了。可以把配置放在一个volatile字段里,加一个管理接口或者监听配置中心的变化,运行时动态生效。但序号和会话状态不能热更新,改了必须重连。

性能考量:单主站连接下,104的吞吐量通常不是瓶颈,几千个点位的总召唤也就几秒。但如果一个主站要连几十个从站,就要考虑用连接池或异步IO。Netty的EventLoopGroup线程数要调,默认的CPU核数×2在连接数多的时候可能不够。

从那以后我每次改完协议层代码,都强制走一遍“单元测试→模拟器联调→抓包对比”三步,不再直接连现场设备试。希望帮到你。

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

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

多模态Agent实时性架构:全双工与级联管线的800ms工程博弈

1. 这不是“加个语音模块”就能解决的问题&#xff1a;为什么800ms成了多模态Agent的生死线你有没有试过对着一个号称“实时”的多模态Agent说话&#xff0c;话音刚落&#xff0c;它却沉默了将近一秒钟才开始回应&#xff1f;那种微妙的卡顿感&#xff0c;不是系统卡死&#xf…

作者头像 李华
网站建设 2026/10/20 18:37:57

LLM网关:Rust构建的协议转换与流量治理中枢

1. 这不是API代理&#xff0c;而是一套面向LLM服务的流量治理中枢“我用Rust做了一个Codex和Claude的网关管理器”——这句话在技术社区刷屏时&#xff0c;很多人第一反应是&#xff1a;“又一个转发请求的反向代理&#xff1f;”但真正看过代码、跑过压测、在生产环境里调过日…

作者头像 李华
网站建设 2026/10/20 18:37:45

从技能拆解到能力映射:构建可迭代的个人技能体系实战指南

1. 从“skills”这个词说起&#xff1a;为什么它突然成了高频词“skills”这个词最近被反复提起&#xff0c;不是没有原因的。我在几个技术社区和职场社群里潜水观察了大半年&#xff0c;发现一个很明显的趋势&#xff1a;以前大家聊的是“你用过什么框架”“你做过什么项目”&…

作者头像 李华
网站建设 2026/10/20 18:36:36

OctaFuse网关2.14.0:图像语音接入、策略化折扣与声明式配置实战指南

1. 项目概述&#xff1a;这不是一个普通网关升级&#xff0c;而是一次面向真实业务场景的“连接力重构”OctaFuse Gateway 2.14.0 这个版本号背后&#xff0c;藏着三类人最关心的问题&#xff1a;前端业务方在问“新接入的图像和语音流&#xff0c;能不能直接塞进我们现有的工单…

作者头像 李华
网站建设 2026/10/20 18:36:05

AI服务器赛道爆发:千亿市场背后的技术拆解与产业链机会

1. 赛道爆发的底层逻辑&#xff1a;为什么服务器突然成了香饽饽 1.1 从"卖铁皮箱子"到"卖算力入口"的认知转变 早些年提起服务器&#xff0c;大部分人的第一反应就是机房里面那一排排嗡嗡作响的铁皮箱子&#xff0c;跟"性感"两个字完全不沾边。…

作者头像 李华
网站建设 2026/10/20 18:35:39

大数据安全ROI怎么算?一套可落地的投入产出测算框架

"你们安全部去年花了一百多万&#xff0c;到底防住了什么&#xff1f;给我一个数。"这是某公司财务总监在一次年度预算会上抛给安全负责人的原话。搁几年前&#xff0c;这种问题会被定义为"不懂安全"&#xff0c;但现在我越来越理解对方的立场&#xff1a;…

作者头像 李华