本文还有配套的精品资源点击获取简介电力自动化领域常用的IEC 60870-5-104协议调试与教学工具完整提供主站IEC104NAMaster和从站IEC104NASlave两个独立VC6工程支持遥控、遥信、遥测等核心功能模拟。主站能发送典型控制命令并接收响应从站可解析上行报文、反馈设备状态并按设定周期上报模拟遥测值与遥信变位。源码结构清晰包含全部.cpp/.h文件、资源脚本.rc、工程配置.dsw/.dsp、预编译头StdAfx.h/cpp、ReadMe说明文档以及Debug/Release编译输出目录。所有代码无第三方依赖兼容原生Visual C 6.0开发环境开箱即用适合现场调试、协议原理学习、IED设备联调验证及教学演示。压缩包内含冲突标记文件.hoist-conflict-*属版本合并残留不影响主工程编译运行。1. 项目概述为什么在2024年还要用VC6跑IEC104仿真你可能第一眼看到“VC6”就皱眉——这玩意儿连Windows 10都得开兼容模式才能装更别说Win11了。但如果你干过变电站综自系统调试、配网终端DTU/FTU联调、或者给老电厂做DCS接口改造你就知道不是我们不想用Qt或.NET而是现场那台运行着WinXP SP3的后台监控机它只认VC6编译出来的EXE。这套IEC104主从站双模仿真工具包就是为这种“真实世界”量身定做的——它不炫技不堆新特性只做一件事在最原始、最受限、也最真实的工业现场环境里稳稳当当地把IEC 60870-5-104协议的握手、心跳、遥信变位、遥控执行、遥测上送这些动作一五一十地跑通、看懂、调明白。关键词里写的“IEC104仿真、VC6源码、主从站模拟”其实背后藏着三层硬需求第一层是协议教学——学生或新人工程师需要一个能单步调试、能看到每一字节报文如何组装、如何校验、如何应答的“透明盒子”第二层是设备联调——厂家送来一台新的RTU后台系统说“收不到遥信”到底是RTU没发还是发错了APDU结构还是ASDU类型填反了这时候你不能靠抓包软件猜得有个能和它“对讲”的另一端第三层是故障复现与验证——某次现场出现“遥控超时”是通道延时大还是从站未按规范返回确认帧还是主站解析逻辑有缺陷这套工具让你把问题锁定在协议栈层面而不是在“是不是网线松了”这种低级环节反复折腾。我做过不下二十个变电站的通信调试最深的体会是协议文档写得再清楚不如亲眼看见一个完整的ASDU Type 1单点信息从从站发出、被主站正确识别并触发界面变位的过程来得直观。而VC6的价值恰恰在于它的“笨重”——没有MFC的自动内存管理没有STL容器的抽象封装所有socket收发、字节序转换、APDU长度计算、控制域标志位操作都赤裸裸地写在.cpp文件里。你调试时F11跟进去看到的是pBuf[2] (BYTE)(m_nAppLen 0xFF);这种直白到粗暴的代码而不是一个黑盒函数的返回值。这对理解IEC104底层机制比任何高级框架都有效。它不是过时的技术而是一把精准的解剖刀——专用于切开协议表象直抵通信本质。2. 整体架构与设计思路为什么是两个独立工程而不是一个带切换的UI拿到这个资源包第一眼你会注意到它不是“一个程序通过菜单切换主/从模式”而是实实在在的两个独立VC6工程——IEC104NAMaster.dsw和IEC104NASlave.dsw。有人可能会觉得“这样不方便”但这是经过无数次现场踩坑后做出的刻意选择。让我拆解一下背后的逻辑。2.1 主站与从站的本质差异决定了它们必须物理隔离IEC104协议里主站Control Station和从站Controlled Station的角色定义是刚性的。主站发起连接、发送控制命令如遥控、请求数据如总召从站被动响应、主动上送变化数据如遥信变位、遥测越限。它们的状态机完全不同主站要维护连接状态、命令超时重发、等待确认帧从站要监听TCP端口、解析不同类型的APDU、管理本地数据对象如开关量状态表、模拟量缓存区、按周期生成自发报文。如果强行塞进一个工程UI层就得做大量状态判断“当前是主站模式那禁用‘接收报文’按钮如果是从站模式那隐藏‘发送遥控’菜单”而底层协议栈更会陷入混乱——比如一个socket句柄既要connect()又要listen()这在Winsock里本身就是非法操作。所以这两个工程是职责分离的典范IEC104NAMaster工程里核心是IEC104NAMaster.cpp和Master104.h它们专注构建APDU、填充ASDU、处理超时定时器IEC104NASlave工程里Slave104.cpp和IEC104Slave.h则负责TCP监听、报文解析、数据对象映射、周期上报调度。它们之间唯一的“耦合”就是TCP/IP网络层——主站连从站的IP和端口仅此而已。这种松耦合让调试变得极其清晰你想验证遥控流程就只启动主站和从站两个EXE用Wireshark抓包看交互你想单独测试从站的数据上报逻辑甚至可以把主站关掉只看从站自己发的自发报文是否符合规范。2.2 VC6工程结构的“复古”合理性再看目录里的那些文件.dswWorkspace、.dspProject、.clwClassWizard、.apsApplication Studio Project Settings。这看似是历史包袱实则是稳定性的基石。VC6的工程文件是纯文本格式你可以用记事本打开.dsp里面清清楚楚写着# Begin Source File SOURCE.\IEC104NAMaster.cpp # End Source File # Begin Source File SOURCE.\StdAfx.cpp # End Source File这意味着什么意味着没有任何神秘的二进制配置、没有IDE自动生成的不可见元数据、没有版本升级导致的工程文件损坏风险。我在某省调中心遇到过一次事故他们用VS2019打开一个旧的VS2010工程结果.vcxproj文件被自动升级某些自定义的预编译宏丢失导致整个通信模块编译失败而备份的.dsp文件却完好无损半小时内就恢复了调试。这套工具包保留.dsw/.dsp不是守旧而是把“可预测性”放在第一位。至于那些.hoist-conflict-*文件摘要里说得很清楚是Git合并冲突的残留。实际编译时你完全可以安全地删掉它们——它们只是文本编辑器在解决冲突时留下的临时标记不会被VC6读取。真正参与编译的只有干净的.cpp、.h、.rc和工程配置文件。这种“极简依赖”的设计确保了你在一台刚装好VC6SP6补丁的裸机上双击.dsw点击“重建全部”就能得到两个可运行的EXE中间不需要安装任何SDK、Runtime或第三方库。对于需要快速部署到客户现场笔记本上的工程师来说这就是最大的效率。3. 核心细节解析报文构造、状态机与数据对象映射现在我们深入到代码层面看看这套工具包是如何把枯燥的IEC104标准变成一行行可调试、可修改的C代码的。重点不在“它实现了什么”而在“它为什么这样实现”。3.1 主站侧APDU组装与遥控命令的“三步走”以遥控Single Command, ASDU Type 45为例主站的发送逻辑在IEC104NAMasterDlg.cpp的某个按钮响应函数里比如OnBtnSendYK()。它不是简单地拼一个字符串而是严格遵循IEC 60870-5-104的分层结构控制域Control Field构造这是APDU的“头部身份证”。代码里会看到类似这样的片段cpp BYTE ctrlField[4]; ctrlField[0] 0x68; // 启动字符 ctrlField[1] 0x0E; // APDU长度固定为14字节含后续所有 ctrlField[2] 0x00; // 控制域启动标志、PRM1主站发、FUNC0未传输 ctrlField[3] 0x00; // 控制域续序列号、计数器等关键点在于ctrlField[2]的设置。0x00表示这是一个I格式帧带ASDU且是主站发出的。如果你把这里错写成0x80表示S格式帧只含控制信息从站就会直接丢弃因为它期待的是带数据的I帧。这个细节在Wireshark里看一眼就能验证正确的遥控帧其控制域第二个字节一定是0x00或0x01取决于方向绝不会是0x80。ASDU应用服务数据单元填充这才是遥控命令的“正文”。Slave104.h里定义了struct ASDU_Type45它包含TypeID 45单点遥控VariableStruct 0x80可变结构限定词表示1个信息体CauseOfTransmission 6激活即发送遥控命令ASDUAddr 0x0001从站地址通常为1IOA 0x000001信息对象地址即你要控制的开关编号Value 1合闸或0分闸QOS 0x80品质描述词含“可选”、“非确认”等标志这些字段不是随便填的。比如CauseOfTransmission 6是标准规定的“激活”如果填成5“激活终止”从站会认为这是命令取消而不是执行。代码里会用位运算精确设置QOS字节确保第7位可选为1第6位非确认为0因为遥控是需要确认的。校验与发送最后一步是计算APDU的CRC校验虽然IEC104本身不强制要求但很多现场设备会校验。工具包里有一个CalcCRC16()函数它遍历从启动字符到ASDU末尾的所有字节用标准的CRC-16算法计算。算出的2字节校验码会被追加到APDU末尾。然后send(m_hSocket, pBuf, nTotalLen, 0)才真正把数据发出去。这三步缺一不可漏掉任何一步从站收到的都是乱码或非法帧。我见过太多人只关注ASDU内容却忘了控制域的PRM位没设对结果主站以为发成功了但从站根本没收到。3.2 从站侧状态机驱动的报文解析与数据对象映射从站的核心是Slave104.cpp里的ParseAPDU()函数。它不是一个简单的switch(TypeID)而是一个基于状态机的解析器。为什么因为IEC104的APDU是变长的ASDU内部还有嵌套结构比如遥测值可能是2字节整数也可能是4字节浮点数必须边读边判。它的状态流转大概是*State_Start: 等待0x68启动字符。没等到丢弃所有数据重新同步。*State_Length: 读取第二个字节得到APDU总长度L。如果L太大比如255或太小4判定为错误进入State_Error。*State_Control: 读取接下来的4字节控制域。检查PRM位必须为0表示从站接收、FUNC位是否为0即I帧。如果不是直接返回错误。*State_ASDU: 进入ASDU解析。先读TypeID根据TypeID查一个预定义的“ASDU模板表”。比如TypeID1单点信息对应模板{len1, ioa_len3}TypeID30归一化值对应{len4, ioa_len3}。然后按模板长度逐字节读取信息体地址IOA、信息元素Value、品质描述词QDS。*State_Finish: 解析完成触发相应回调比如OnRecvSingleCommand(ASDU_Type45* pCmd)。这个状态机的设计保证了从站对“畸形报文”的鲁棒性。比如主站误发了一个长度为0的APDU从站不会崩溃而是卡在State_Length等待下一个0x68来重新同步。这比用一堆if-else堆出来的解析逻辑更能应对真实现场中各种网络抖动、设备bug导致的异常报文。至于“数据对象映射”这是从站的灵魂。IEC104Slave.h里定义了几个全局数组// 遥信状态表100个点 BOOL g_bDIStatus[100] {0}; // 0分闸, 1合闸 // 遥测值表100个点 float g_fAIValue[100] {0.0f}; // 遥控命令接收缓冲区 struct YK_CMD g_stYKCmd;当解析到TypeID45的遥控命令时OnRecvSingleCommand()就会根据pCmd-IOA找到对应的索引更新g_bDIStatus[index]然后立即调用SendConfirm(pCmd)发送确认帧。而周期上报比如每5秒发一次遥信全数据的逻辑则是在一个CWnd::OnTimer()定时器里遍历g_bDIStatus数组把所有点的状态打包成TypeID1的ASDU塞进APDU发出去。这种“内存变量 ↔ 协议报文”的直接映射让学习者一眼就能看懂原来“遥信点1合闸”在协议里就是IOA0x000001, Value1这几个字节。4. 实操过程从零开始编译、调试与联调的完整链路现在让我们把理论付诸实践。假设你手头有一台装好VC6SP6的Windows机器推荐XP或Win7兼容模式以及一份刚解压的工具包。下面是你将经历的真实操作链路每一步我都标出了关键检查点和常见陷阱。4.1 编译前的环境准备与工程清理第一步永远是清理战场。双击IEC104NAMaster.dswVC6会加载工作区。此时你很可能看到“找不到StdAfx.h”或“无法打开resource.h”的错误。别慌这不是代码问题而是VC6的“路径记忆”在作祟。解决方案是在VC6菜单栏依次点击Tools→Options→Directories选项卡。在Show directories for:下拉框中选择Include files。点击右侧Add按钮添加你解压后的整个文件夹路径比如D:\IEC104Sim\。确保这个路径在列表的最顶端。因为VC6会按顺序搜索头文件如果系统路径如C:\Program Files\Microsoft Visual Studio\VC98\Include排在前面它会优先找到系统里的windows.h而忽略你项目里的resource.h。同样在Library files目录下添加D:\IEC104Sim\如果项目用了自定义lib但本工具包没有。最关键的一步关闭VC6手动删除项目根目录下的Debug和Release文件夹以及所有.ncb、.opt文件它们是VC6的智能感知和选项缓存经常损坏。然后再重新打开.dsw。提示.hoist-conflict-*文件可以放心删除它们是Git合并时的临时产物VC6完全无视它们。保留它们只会让你的项目目录看起来很混乱。4.2 主站工程编译与基础功能验证打开IEC104NAMaster.dsw后VC6左侧的Workspace窗口会显示IEC104NAMaster工程。右键点击它选择Settings...。在General选项卡里确认Microsoft Foundation Classes设置为Use MFC in a Shared DLL这是VC6默认也是本项目所用。然后点击OK。现在按F7开始编译。如果一切顺利你会在输出窗口看到Compiling... Compiling resources... Linking... IEC104NAMaster.exe - 0 error(s), 0 warning(s)编译成功后按CtrlF5运行。你会看到一个经典的VC6风格对话框上面有IP地址输入框、端口号默认2404、连接按钮、以及一堆“发送遥信”、“发送遥测”、“发送遥控”的按钮。首次验证建立TCP连接1. 先不要点任何发送按钮。在IP框里填127.0.0.1本机回环端口填2404IEC104标准端口。2. 点击Connect。如果连接成功界面上的Status标签应该变成Connected并且Connect按钮变为Disconnect。3.关键检查此时立刻打开Windows任务管理器切换到“性能”选项卡观察“网络使用率”。如果连接成功你应该能看到一个微小的、持续的流量几KB/s这是主站和从站之间的心跳报文S帧在交互。如果没有流量说明连接根本没建立回去检查IP、端口、防火墙Win10/11的防火墙默认会阻止VC6程序联网需要手动放行。4.3 从站工程编译与双向联调主站编译成功后关闭它然后双击IEC104NASlave.dsw重复上面的环境设置和编译步骤。编译完成后运行IEC104NASlave.exe。从站的界面比主站更简洁只有一个Start Listen按钮和一个日志框。点击它你会看到日志里打印[INFO] Listening on port 2404... [INFO] Client connected from 127.0.0.1:54321这说明从站已启动监听并且主站已经成功连上它。终极验证遥控命令的端到端闭环1. 在主站界面确保Status是Connected。2. 在从站界面找到“遥控命令”区域通常有一个下拉框让你选择“点号”比如0001和“状态”ON或OFF。3. 在主站界面点击Send Remote Control按钮。4.观察现象* 主站日志框应立刻打印[SENT] ASDU Type 45, IOA0001, Value1。* 从站日志框应紧接着打印[RECV] ASDU Type 45, IOA0001, Value1然后是[EXEC] Remote control executed for IOA 0001。* 主站界面的某个状态指示灯比如一个红色方块应该由灰变绿表示合闸成功。* 如果你用Wireshark抓包过滤tcp.port 2404你会看到一来一回的两帧第一帧是主站发的I帧Type 45第二帧是从站回的I帧Type 46遥控确认其中CauseOfTransmission 7激活确认。注意如果从站收到了命令但没执行或者主站没收到确认请立刻检查Slave104.cpp里的OnRecvSingleCommand()函数。最常见的错误是IOA地址映射错了比如代码里写的是g_bDIStatus[pCmd-IOA] pCmd-Value;但pCmd-IOA是一个3字节的整数0x000001而数组索引只能是0~99所以必须做index pCmd-IOA 0xFF;这样的掩码操作。这个细节正是现场调试中最容易卡住的地方。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”在电力自动化现场没有“理论上应该可以”只有“实际上到底行不行”。下面这些是我和同行们在无数个深夜、在几十个变电站机房里用键盘敲出来、用万用表量出来的经验。它们不会出现在任何官方文档里但能帮你节省至少80%的调试时间。5.1 “连接不上”的十大原因与速查表现象最可能原因排查指令/操作解决方案主站点“Connect”没反应VC6程序被防火墙拦截netsh advfirewall show allprofiles在防火墙设置中为IEC104NAMaster.exe添加入站和出站规则主站显示“Connected”但从站日志无连接记录主站IP填错如填了localhost而非127.0.0.1ping localhost和ping 127.0.0.1统一使用127.0.0.1避免DNS解析问题从站日志显示连接但主站状态仍是“Disconnected”从站未启动监听或监听端口被占用netstat -ano \| findstr :2404杀掉占用2404端口的进程taskkill /PID PID /F连接瞬间断开主站和从站的“心跳间隔”设置不一致查看主站配置里的Test FRQ和从站的T0参数将两者都设为30秒IEC104标准建议值连接成功但发遥控无响应从站的g_bDIStatus数组越界访问导致程序崩溃在VC6中按CtrlAltBreak中断查看调用栈在OnRecvSingleCommand()开头加if(pCmd-IOA 100) return;5.2 报文解析失败的“隐形杀手”最让人抓狂的问题不是报文发不出去而是发出去了对方也收到了但就是“看不懂”。Wireshark里看到一串十六进制却不知道哪一位是TypeID哪一位是IOA。这时请记住这三个“必查项”字节序Endianness陷阱IEC104规定所有多字节整数如IOA、ASDUAddr在网络上传输时必须是大端序Big-Endian即高位字节在前。但x86 CPU是小端序Little-Endian。所以当你在代码里写int ioa 0x000001;然后直接memcpy(pBuf10, ioa, 3);结果是错的正确的做法是cpp // 错误直接拷贝小端序 memcpy(pBuf10, ioa, 3); // 正确手动转大端序 pBuf[10] (ioa 16) 0xFF; pBuf[11] (ioa 8) 0xFF; pBuf[12] ioa 0xFF;这个错误会导致从站解析出的IOA是0x010000即65536远超你的数组范围从而静默失败。APDU长度字段的“幽灵字节”APDU的第二个字节L表示的是APDU的总长度减去启动字符0x68的剩余字节数。也就是说如果L14那么整个APDU是15字节1个启动字符 14个后续字节。很多初学者会误以为L就是总长度导致在计算校验码时少算了一个字节结果CRC校验失败从站直接丢弃。“静默丢包”的品质描述词QDS在遥信报文TypeID1中QDS字节的第2位Bit 1是“块传送”标志。如果这个位被意外置1而从站的固件又恰好不支持块传送它就会把整个ASDU当成非法报文既不处理也不回复错误就像什么都没发生一样。解决方案是在构造QDS时务必用 ~0x02清除这一位确保它是0。5.3 实战避坑心得来自一线的“小抄”“不要相信默认端口”虽然IEC104标准端口是2404但现场90%的设备尤其是国产RTU会改成其他端口比如10001、8888甚至20000以上。调试前务必向设备厂家索要《通信规约配置手册》找到“TCP Server Port”这一项而不是盲目填2404。“日志比界面更重要”工具包的对话框界面上那些闪烁的指示灯和按钮只是给你一个心理安慰。真正的真相藏在Edit控件日志框里。我养成了一个习惯每次调试都先把主站和从站的日志框拖到屏幕两侧眼睛盯着它们而不是看界面上的按钮颜色。因为日志会告诉你“收到了Type 1但IOA0x0000FF超出范围”而界面只会显示一个灰色方块让你一头雾水。“重启是最强的调试器”当一切看起来都对但就是不通时不要纠结于某一行代码。请关闭主站、关闭从站、关闭Wireshark、关闭VC6然后重启电脑。很多诡异的网络状态如TIME_WAIT端口未释放、Winsock栈损坏只有重启才能彻底清除。这听起来很原始但在工业现场它比写一百行调试代码都管用。最后再分享一个小技巧如果你想快速验证一个新设备是否真的支持IEC104不用写任何代码。直接用本工具包的从站把它当成一个“协议探针”。把从站的监听端口设为设备要连接的端口比如10001然后让设备作为主站去连它。如果设备能成功建立连接并开始发送心跳S帧那就证明它的TCP/IP栈和基本连接逻辑是OK的如果连不上问题一定出在设备的网络配置IP、子网掩码、网关上而不是协议层面。这个方法帮我在三个项目里把原本需要两天的网络排查缩短到了十分钟。本文还有配套的精品资源点击获取简介电力自动化领域常用的IEC 60870-5-104协议调试与教学工具完整提供主站IEC104NAMaster和从站IEC104NASlave两个独立VC6工程支持遥控、遥信、遥测等核心功能模拟。主站能发送典型控制命令并接收响应从站可解析上行报文、反馈设备状态并按设定周期上报模拟遥测值与遥信变位。源码结构清晰包含全部.cpp/.h文件、资源脚本.rc、工程配置.dsw/.dsp、预编译头StdAfx.h/cpp、ReadMe说明文档以及Debug/Release编译输出目录。所有代码无第三方依赖兼容原生Visual C 6.0开发环境开箱即用适合现场调试、协议原理学习、IED设备联调验证及教学演示。压缩包内含冲突标记文件.hoist-conflict-*属版本合并残留不影响主工程编译运行。本文还有配套的精品资源点击获取
VC6环境下可直接编译的IEC104主从站双模仿真工具包
发布时间:2026/6/3 1:52:47
本文还有配套的精品资源点击获取简介电力自动化领域常用的IEC 60870-5-104协议调试与教学工具完整提供主站IEC104NAMaster和从站IEC104NASlave两个独立VC6工程支持遥控、遥信、遥测等核心功能模拟。主站能发送典型控制命令并接收响应从站可解析上行报文、反馈设备状态并按设定周期上报模拟遥测值与遥信变位。源码结构清晰包含全部.cpp/.h文件、资源脚本.rc、工程配置.dsw/.dsp、预编译头StdAfx.h/cpp、ReadMe说明文档以及Debug/Release编译输出目录。所有代码无第三方依赖兼容原生Visual C 6.0开发环境开箱即用适合现场调试、协议原理学习、IED设备联调验证及教学演示。压缩包内含冲突标记文件.hoist-conflict-*属版本合并残留不影响主工程编译运行。1. 项目概述为什么在2024年还要用VC6跑IEC104仿真你可能第一眼看到“VC6”就皱眉——这玩意儿连Windows 10都得开兼容模式才能装更别说Win11了。但如果你干过变电站综自系统调试、配网终端DTU/FTU联调、或者给老电厂做DCS接口改造你就知道不是我们不想用Qt或.NET而是现场那台运行着WinXP SP3的后台监控机它只认VC6编译出来的EXE。这套IEC104主从站双模仿真工具包就是为这种“真实世界”量身定做的——它不炫技不堆新特性只做一件事在最原始、最受限、也最真实的工业现场环境里稳稳当当地把IEC 60870-5-104协议的握手、心跳、遥信变位、遥控执行、遥测上送这些动作一五一十地跑通、看懂、调明白。关键词里写的“IEC104仿真、VC6源码、主从站模拟”其实背后藏着三层硬需求第一层是协议教学——学生或新人工程师需要一个能单步调试、能看到每一字节报文如何组装、如何校验、如何应答的“透明盒子”第二层是设备联调——厂家送来一台新的RTU后台系统说“收不到遥信”到底是RTU没发还是发错了APDU结构还是ASDU类型填反了这时候你不能靠抓包软件猜得有个能和它“对讲”的另一端第三层是故障复现与验证——某次现场出现“遥控超时”是通道延时大还是从站未按规范返回确认帧还是主站解析逻辑有缺陷这套工具让你把问题锁定在协议栈层面而不是在“是不是网线松了”这种低级环节反复折腾。我做过不下二十个变电站的通信调试最深的体会是协议文档写得再清楚不如亲眼看见一个完整的ASDU Type 1单点信息从从站发出、被主站正确识别并触发界面变位的过程来得直观。而VC6的价值恰恰在于它的“笨重”——没有MFC的自动内存管理没有STL容器的抽象封装所有socket收发、字节序转换、APDU长度计算、控制域标志位操作都赤裸裸地写在.cpp文件里。你调试时F11跟进去看到的是pBuf[2] (BYTE)(m_nAppLen 0xFF);这种直白到粗暴的代码而不是一个黑盒函数的返回值。这对理解IEC104底层机制比任何高级框架都有效。它不是过时的技术而是一把精准的解剖刀——专用于切开协议表象直抵通信本质。2. 整体架构与设计思路为什么是两个独立工程而不是一个带切换的UI拿到这个资源包第一眼你会注意到它不是“一个程序通过菜单切换主/从模式”而是实实在在的两个独立VC6工程——IEC104NAMaster.dsw和IEC104NASlave.dsw。有人可能会觉得“这样不方便”但这是经过无数次现场踩坑后做出的刻意选择。让我拆解一下背后的逻辑。2.1 主站与从站的本质差异决定了它们必须物理隔离IEC104协议里主站Control Station和从站Controlled Station的角色定义是刚性的。主站发起连接、发送控制命令如遥控、请求数据如总召从站被动响应、主动上送变化数据如遥信变位、遥测越限。它们的状态机完全不同主站要维护连接状态、命令超时重发、等待确认帧从站要监听TCP端口、解析不同类型的APDU、管理本地数据对象如开关量状态表、模拟量缓存区、按周期生成自发报文。如果强行塞进一个工程UI层就得做大量状态判断“当前是主站模式那禁用‘接收报文’按钮如果是从站模式那隐藏‘发送遥控’菜单”而底层协议栈更会陷入混乱——比如一个socket句柄既要connect()又要listen()这在Winsock里本身就是非法操作。所以这两个工程是职责分离的典范IEC104NAMaster工程里核心是IEC104NAMaster.cpp和Master104.h它们专注构建APDU、填充ASDU、处理超时定时器IEC104NASlave工程里Slave104.cpp和IEC104Slave.h则负责TCP监听、报文解析、数据对象映射、周期上报调度。它们之间唯一的“耦合”就是TCP/IP网络层——主站连从站的IP和端口仅此而已。这种松耦合让调试变得极其清晰你想验证遥控流程就只启动主站和从站两个EXE用Wireshark抓包看交互你想单独测试从站的数据上报逻辑甚至可以把主站关掉只看从站自己发的自发报文是否符合规范。2.2 VC6工程结构的“复古”合理性再看目录里的那些文件.dswWorkspace、.dspProject、.clwClassWizard、.apsApplication Studio Project Settings。这看似是历史包袱实则是稳定性的基石。VC6的工程文件是纯文本格式你可以用记事本打开.dsp里面清清楚楚写着# Begin Source File SOURCE.\IEC104NAMaster.cpp # End Source File # Begin Source File SOURCE.\StdAfx.cpp # End Source File这意味着什么意味着没有任何神秘的二进制配置、没有IDE自动生成的不可见元数据、没有版本升级导致的工程文件损坏风险。我在某省调中心遇到过一次事故他们用VS2019打开一个旧的VS2010工程结果.vcxproj文件被自动升级某些自定义的预编译宏丢失导致整个通信模块编译失败而备份的.dsp文件却完好无损半小时内就恢复了调试。这套工具包保留.dsw/.dsp不是守旧而是把“可预测性”放在第一位。至于那些.hoist-conflict-*文件摘要里说得很清楚是Git合并冲突的残留。实际编译时你完全可以安全地删掉它们——它们只是文本编辑器在解决冲突时留下的临时标记不会被VC6读取。真正参与编译的只有干净的.cpp、.h、.rc和工程配置文件。这种“极简依赖”的设计确保了你在一台刚装好VC6SP6补丁的裸机上双击.dsw点击“重建全部”就能得到两个可运行的EXE中间不需要安装任何SDK、Runtime或第三方库。对于需要快速部署到客户现场笔记本上的工程师来说这就是最大的效率。3. 核心细节解析报文构造、状态机与数据对象映射现在我们深入到代码层面看看这套工具包是如何把枯燥的IEC104标准变成一行行可调试、可修改的C代码的。重点不在“它实现了什么”而在“它为什么这样实现”。3.1 主站侧APDU组装与遥控命令的“三步走”以遥控Single Command, ASDU Type 45为例主站的发送逻辑在IEC104NAMasterDlg.cpp的某个按钮响应函数里比如OnBtnSendYK()。它不是简单地拼一个字符串而是严格遵循IEC 60870-5-104的分层结构控制域Control Field构造这是APDU的“头部身份证”。代码里会看到类似这样的片段cpp BYTE ctrlField[4]; ctrlField[0] 0x68; // 启动字符 ctrlField[1] 0x0E; // APDU长度固定为14字节含后续所有 ctrlField[2] 0x00; // 控制域启动标志、PRM1主站发、FUNC0未传输 ctrlField[3] 0x00; // 控制域续序列号、计数器等关键点在于ctrlField[2]的设置。0x00表示这是一个I格式帧带ASDU且是主站发出的。如果你把这里错写成0x80表示S格式帧只含控制信息从站就会直接丢弃因为它期待的是带数据的I帧。这个细节在Wireshark里看一眼就能验证正确的遥控帧其控制域第二个字节一定是0x00或0x01取决于方向绝不会是0x80。ASDU应用服务数据单元填充这才是遥控命令的“正文”。Slave104.h里定义了struct ASDU_Type45它包含TypeID 45单点遥控VariableStruct 0x80可变结构限定词表示1个信息体CauseOfTransmission 6激活即发送遥控命令ASDUAddr 0x0001从站地址通常为1IOA 0x000001信息对象地址即你要控制的开关编号Value 1合闸或0分闸QOS 0x80品质描述词含“可选”、“非确认”等标志这些字段不是随便填的。比如CauseOfTransmission 6是标准规定的“激活”如果填成5“激活终止”从站会认为这是命令取消而不是执行。代码里会用位运算精确设置QOS字节确保第7位可选为1第6位非确认为0因为遥控是需要确认的。校验与发送最后一步是计算APDU的CRC校验虽然IEC104本身不强制要求但很多现场设备会校验。工具包里有一个CalcCRC16()函数它遍历从启动字符到ASDU末尾的所有字节用标准的CRC-16算法计算。算出的2字节校验码会被追加到APDU末尾。然后send(m_hSocket, pBuf, nTotalLen, 0)才真正把数据发出去。这三步缺一不可漏掉任何一步从站收到的都是乱码或非法帧。我见过太多人只关注ASDU内容却忘了控制域的PRM位没设对结果主站以为发成功了但从站根本没收到。3.2 从站侧状态机驱动的报文解析与数据对象映射从站的核心是Slave104.cpp里的ParseAPDU()函数。它不是一个简单的switch(TypeID)而是一个基于状态机的解析器。为什么因为IEC104的APDU是变长的ASDU内部还有嵌套结构比如遥测值可能是2字节整数也可能是4字节浮点数必须边读边判。它的状态流转大概是*State_Start: 等待0x68启动字符。没等到丢弃所有数据重新同步。*State_Length: 读取第二个字节得到APDU总长度L。如果L太大比如255或太小4判定为错误进入State_Error。*State_Control: 读取接下来的4字节控制域。检查PRM位必须为0表示从站接收、FUNC位是否为0即I帧。如果不是直接返回错误。*State_ASDU: 进入ASDU解析。先读TypeID根据TypeID查一个预定义的“ASDU模板表”。比如TypeID1单点信息对应模板{len1, ioa_len3}TypeID30归一化值对应{len4, ioa_len3}。然后按模板长度逐字节读取信息体地址IOA、信息元素Value、品质描述词QDS。*State_Finish: 解析完成触发相应回调比如OnRecvSingleCommand(ASDU_Type45* pCmd)。这个状态机的设计保证了从站对“畸形报文”的鲁棒性。比如主站误发了一个长度为0的APDU从站不会崩溃而是卡在State_Length等待下一个0x68来重新同步。这比用一堆if-else堆出来的解析逻辑更能应对真实现场中各种网络抖动、设备bug导致的异常报文。至于“数据对象映射”这是从站的灵魂。IEC104Slave.h里定义了几个全局数组// 遥信状态表100个点 BOOL g_bDIStatus[100] {0}; // 0分闸, 1合闸 // 遥测值表100个点 float g_fAIValue[100] {0.0f}; // 遥控命令接收缓冲区 struct YK_CMD g_stYKCmd;当解析到TypeID45的遥控命令时OnRecvSingleCommand()就会根据pCmd-IOA找到对应的索引更新g_bDIStatus[index]然后立即调用SendConfirm(pCmd)发送确认帧。而周期上报比如每5秒发一次遥信全数据的逻辑则是在一个CWnd::OnTimer()定时器里遍历g_bDIStatus数组把所有点的状态打包成TypeID1的ASDU塞进APDU发出去。这种“内存变量 ↔ 协议报文”的直接映射让学习者一眼就能看懂原来“遥信点1合闸”在协议里就是IOA0x000001, Value1这几个字节。4. 实操过程从零开始编译、调试与联调的完整链路现在让我们把理论付诸实践。假设你手头有一台装好VC6SP6的Windows机器推荐XP或Win7兼容模式以及一份刚解压的工具包。下面是你将经历的真实操作链路每一步我都标出了关键检查点和常见陷阱。4.1 编译前的环境准备与工程清理第一步永远是清理战场。双击IEC104NAMaster.dswVC6会加载工作区。此时你很可能看到“找不到StdAfx.h”或“无法打开resource.h”的错误。别慌这不是代码问题而是VC6的“路径记忆”在作祟。解决方案是在VC6菜单栏依次点击Tools→Options→Directories选项卡。在Show directories for:下拉框中选择Include files。点击右侧Add按钮添加你解压后的整个文件夹路径比如D:\IEC104Sim\。确保这个路径在列表的最顶端。因为VC6会按顺序搜索头文件如果系统路径如C:\Program Files\Microsoft Visual Studio\VC98\Include排在前面它会优先找到系统里的windows.h而忽略你项目里的resource.h。同样在Library files目录下添加D:\IEC104Sim\如果项目用了自定义lib但本工具包没有。最关键的一步关闭VC6手动删除项目根目录下的Debug和Release文件夹以及所有.ncb、.opt文件它们是VC6的智能感知和选项缓存经常损坏。然后再重新打开.dsw。提示.hoist-conflict-*文件可以放心删除它们是Git合并时的临时产物VC6完全无视它们。保留它们只会让你的项目目录看起来很混乱。4.2 主站工程编译与基础功能验证打开IEC104NAMaster.dsw后VC6左侧的Workspace窗口会显示IEC104NAMaster工程。右键点击它选择Settings...。在General选项卡里确认Microsoft Foundation Classes设置为Use MFC in a Shared DLL这是VC6默认也是本项目所用。然后点击OK。现在按F7开始编译。如果一切顺利你会在输出窗口看到Compiling... Compiling resources... Linking... IEC104NAMaster.exe - 0 error(s), 0 warning(s)编译成功后按CtrlF5运行。你会看到一个经典的VC6风格对话框上面有IP地址输入框、端口号默认2404、连接按钮、以及一堆“发送遥信”、“发送遥测”、“发送遥控”的按钮。首次验证建立TCP连接1. 先不要点任何发送按钮。在IP框里填127.0.0.1本机回环端口填2404IEC104标准端口。2. 点击Connect。如果连接成功界面上的Status标签应该变成Connected并且Connect按钮变为Disconnect。3.关键检查此时立刻打开Windows任务管理器切换到“性能”选项卡观察“网络使用率”。如果连接成功你应该能看到一个微小的、持续的流量几KB/s这是主站和从站之间的心跳报文S帧在交互。如果没有流量说明连接根本没建立回去检查IP、端口、防火墙Win10/11的防火墙默认会阻止VC6程序联网需要手动放行。4.3 从站工程编译与双向联调主站编译成功后关闭它然后双击IEC104NASlave.dsw重复上面的环境设置和编译步骤。编译完成后运行IEC104NASlave.exe。从站的界面比主站更简洁只有一个Start Listen按钮和一个日志框。点击它你会看到日志里打印[INFO] Listening on port 2404... [INFO] Client connected from 127.0.0.1:54321这说明从站已启动监听并且主站已经成功连上它。终极验证遥控命令的端到端闭环1. 在主站界面确保Status是Connected。2. 在从站界面找到“遥控命令”区域通常有一个下拉框让你选择“点号”比如0001和“状态”ON或OFF。3. 在主站界面点击Send Remote Control按钮。4.观察现象* 主站日志框应立刻打印[SENT] ASDU Type 45, IOA0001, Value1。* 从站日志框应紧接着打印[RECV] ASDU Type 45, IOA0001, Value1然后是[EXEC] Remote control executed for IOA 0001。* 主站界面的某个状态指示灯比如一个红色方块应该由灰变绿表示合闸成功。* 如果你用Wireshark抓包过滤tcp.port 2404你会看到一来一回的两帧第一帧是主站发的I帧Type 45第二帧是从站回的I帧Type 46遥控确认其中CauseOfTransmission 7激活确认。注意如果从站收到了命令但没执行或者主站没收到确认请立刻检查Slave104.cpp里的OnRecvSingleCommand()函数。最常见的错误是IOA地址映射错了比如代码里写的是g_bDIStatus[pCmd-IOA] pCmd-Value;但pCmd-IOA是一个3字节的整数0x000001而数组索引只能是0~99所以必须做index pCmd-IOA 0xFF;这样的掩码操作。这个细节正是现场调试中最容易卡住的地方。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”在电力自动化现场没有“理论上应该可以”只有“实际上到底行不行”。下面这些是我和同行们在无数个深夜、在几十个变电站机房里用键盘敲出来、用万用表量出来的经验。它们不会出现在任何官方文档里但能帮你节省至少80%的调试时间。5.1 “连接不上”的十大原因与速查表现象最可能原因排查指令/操作解决方案主站点“Connect”没反应VC6程序被防火墙拦截netsh advfirewall show allprofiles在防火墙设置中为IEC104NAMaster.exe添加入站和出站规则主站显示“Connected”但从站日志无连接记录主站IP填错如填了localhost而非127.0.0.1ping localhost和ping 127.0.0.1统一使用127.0.0.1避免DNS解析问题从站日志显示连接但主站状态仍是“Disconnected”从站未启动监听或监听端口被占用netstat -ano \| findstr :2404杀掉占用2404端口的进程taskkill /PID PID /F连接瞬间断开主站和从站的“心跳间隔”设置不一致查看主站配置里的Test FRQ和从站的T0参数将两者都设为30秒IEC104标准建议值连接成功但发遥控无响应从站的g_bDIStatus数组越界访问导致程序崩溃在VC6中按CtrlAltBreak中断查看调用栈在OnRecvSingleCommand()开头加if(pCmd-IOA 100) return;5.2 报文解析失败的“隐形杀手”最让人抓狂的问题不是报文发不出去而是发出去了对方也收到了但就是“看不懂”。Wireshark里看到一串十六进制却不知道哪一位是TypeID哪一位是IOA。这时请记住这三个“必查项”字节序Endianness陷阱IEC104规定所有多字节整数如IOA、ASDUAddr在网络上传输时必须是大端序Big-Endian即高位字节在前。但x86 CPU是小端序Little-Endian。所以当你在代码里写int ioa 0x000001;然后直接memcpy(pBuf10, ioa, 3);结果是错的正确的做法是cpp // 错误直接拷贝小端序 memcpy(pBuf10, ioa, 3); // 正确手动转大端序 pBuf[10] (ioa 16) 0xFF; pBuf[11] (ioa 8) 0xFF; pBuf[12] ioa 0xFF;这个错误会导致从站解析出的IOA是0x010000即65536远超你的数组范围从而静默失败。APDU长度字段的“幽灵字节”APDU的第二个字节L表示的是APDU的总长度减去启动字符0x68的剩余字节数。也就是说如果L14那么整个APDU是15字节1个启动字符 14个后续字节。很多初学者会误以为L就是总长度导致在计算校验码时少算了一个字节结果CRC校验失败从站直接丢弃。“静默丢包”的品质描述词QDS在遥信报文TypeID1中QDS字节的第2位Bit 1是“块传送”标志。如果这个位被意外置1而从站的固件又恰好不支持块传送它就会把整个ASDU当成非法报文既不处理也不回复错误就像什么都没发生一样。解决方案是在构造QDS时务必用 ~0x02清除这一位确保它是0。5.3 实战避坑心得来自一线的“小抄”“不要相信默认端口”虽然IEC104标准端口是2404但现场90%的设备尤其是国产RTU会改成其他端口比如10001、8888甚至20000以上。调试前务必向设备厂家索要《通信规约配置手册》找到“TCP Server Port”这一项而不是盲目填2404。“日志比界面更重要”工具包的对话框界面上那些闪烁的指示灯和按钮只是给你一个心理安慰。真正的真相藏在Edit控件日志框里。我养成了一个习惯每次调试都先把主站和从站的日志框拖到屏幕两侧眼睛盯着它们而不是看界面上的按钮颜色。因为日志会告诉你“收到了Type 1但IOA0x0000FF超出范围”而界面只会显示一个灰色方块让你一头雾水。“重启是最强的调试器”当一切看起来都对但就是不通时不要纠结于某一行代码。请关闭主站、关闭从站、关闭Wireshark、关闭VC6然后重启电脑。很多诡异的网络状态如TIME_WAIT端口未释放、Winsock栈损坏只有重启才能彻底清除。这听起来很原始但在工业现场它比写一百行调试代码都管用。最后再分享一个小技巧如果你想快速验证一个新设备是否真的支持IEC104不用写任何代码。直接用本工具包的从站把它当成一个“协议探针”。把从站的监听端口设为设备要连接的端口比如10001然后让设备作为主站去连它。如果设备能成功建立连接并开始发送心跳S帧那就证明它的TCP/IP栈和基本连接逻辑是OK的如果连不上问题一定出在设备的网络配置IP、子网掩码、网关上而不是协议层面。这个方法帮我在三个项目里把原本需要两天的网络排查缩短到了十分钟。本文还有配套的精品资源点击获取简介电力自动化领域常用的IEC 60870-5-104协议调试与教学工具完整提供主站IEC104NAMaster和从站IEC104NASlave两个独立VC6工程支持遥控、遥信、遥测等核心功能模拟。主站能发送典型控制命令并接收响应从站可解析上行报文、反馈设备状态并按设定周期上报模拟遥测值与遥信变位。源码结构清晰包含全部.cpp/.h文件、资源脚本.rc、工程配置.dsw/.dsp、预编译头StdAfx.h/cpp、ReadMe说明文档以及Debug/Release编译输出目录。所有代码无第三方依赖兼容原生Visual C 6.0开发环境开箱即用适合现场调试、协议原理学习、IED设备联调验证及教学演示。压缩包内含冲突标记文件.hoist-conflict-*属版本合并残留不影响主工程编译运行。本文还有配套的精品资源点击获取