这篇文章适合对IEC104协议有所了解的开发者。文中概念性的介绍比较少,以干货为主;对IEC104协议了解较少,可以先参考这篇文章电力IEC104规约协议解读(含源码下载)_104协议-CSDN博客
其实网络通信协议很简单,就是两部分组成(网络通信+协议),IEC104也不例外,简单来说就是IEC104=Socket+协议,搞懂这个,你就能搞懂大部分的网络通信协议。
为了方便理解,这里把协议分成两部分:协议组成和工作流程。
那么,我们只需要搞懂以下三点就能轻松掌握(程序解析在最后)。
1. Socket
2. 协议组成
3. 流程
Socket
网络通信底层采用TCP/IP通信方式,不管你用什么编程语言,对于网络通信编程,Socket是绕不开的。
【IO密集型用异步,计算密集型用并行】
对于Socket通信编程,如果是一对多IO密集型,最好使用异步而不是多线程;如果是一对一,那就无所谓了。
private void ContinueReadBuffer()
{
try
{
_stream.BeginRead(
_receiveBuffer,
_receiveBufferOffset,
_receiveBuffer.Length - _receiveBufferOffset,
HandleDataReceived,
_stream);
}
catch (Exception ex)
{
HandleReceiveOperationException(ex);
throw;
}
}
源码中采用了异步回调的方式BeginRead和EndRead相结合,异步可以更高效的利用CPU系统资源,软件不卡顿,用户体验也更好。
在一对多的情况下,怎么能最大化利用系统资源做到高并发通信,答案肯定不是多线程,看了源码你就知道了。
协议组成
需要搞懂这几个名词:APCI、ASDU、APDU、U帧、S帧、I帧。
APCI:控制信息部分,类似于帧头,由1个byte 的起始字节0x68 + 1 byte的长度 + 4个byte控制位域(CF)组成。
ASDU:存储数据部分。
APDU:等于APCI+ASDU,长度等于APCI+ASDU-2,减去起始字节和APDU长度字节。
U帧:控制报文帧(只包括APCI部分),主要有启动帧、停止帧、测试帧。
S帧:监视帧(只包括APCI部分),接收序号。
I帧:信息传输帧(包括APCI+ASDU部分)。
-
发送序号和接收序列号是保证数据完整性的条件。
-
类型标识定义发送数据的格式。
遥测常用类型标识
09—带品质描述的测量值,每个遥测值占3个字节
0d—带品质描述的浮点值,每个遥测值占5个字节
遥信常用类型标识
01—不带时标的单点遥信,每个遥信占1个字节
-
可变结构体定义发送数据信息是有序还是无序,有序即一个信息体地址,元素的对应地址会在此信息地址基础上依次加1。无序即1个地址对应一个元素。
-
传输原因定义记录传送的原因以及上行下行方向等。
更详细的解释请参考
电力IEC104规约协议解读(含源码下载)_104协议-CSDN博客
流程
1. 由客户端(主站)向服务端(从站)发起TCP连接。
2. 连接成功后,向服务端发送链路启动帧。
3. 服务端收到启动帧以后,向客户端回应启动确认帧。
4. 客户端收到启动确认帧,向服务端发送总召唤命令帧。
5. 服务端收到总召唤命令数据请求后,发送总召唤命令数据响应帧,然后继续发送总召唤命令数据。总召唤命令数据发送完成后,发送总召唤命令数据结束帧。
6. 客户端在收到总召唤命令数据结束帧后,发送对时请求帧。
7. 服务器收到对时请求帧后,发送对时响应帧。
8. 由服务器主动向客户端发送变化数据帧。同时,收到客户端发送的控制类命令,回复相应的操作结果。
9. 客户端等到下一个数据总召唤命令数据周期,重复第5步之后的流程。
源码中是针对逆变器进行数据采集,每5分钟发送一次总召,没有涉及对时操作;总召时间间隔可根据需要调整。
程序解析
采用的C#编程语言,Windows Form窗体程序
启动后的窗体
Visual studio开发工具,Common文件夹存放公用类,IEC104文件夹存放104协议逻辑实体,Tools为工具,ZZH是Socket相关类,Form1是主窗体。
主程序流程图
loopTimer定时任务
controlTimer定时任务
void HandleData(int typeId, InformationObject[] infoObjs)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < infoObjs.Length; i++)
{
//浮点遥测值
if (typeId == 13)
{
int firstAddress = infoObjs[i].GetInformationObjectAddress(); // 信息地址起始位
int len = infoObjs[i].GetInformationElements().Length / 2;
for (int j = 0; j < len; j++)
{
int address = firstAddress + j;
if (address < 16385 || address > 17000)
{
Console.WriteLine("遥测信息地址超出定义范围");
}
var value = infoObjs[i].GetInformationElements()[j, 0];
var kv = "k:" + address + ",v:" + value + "\r\n";
sb.Append(kv);
Config104.InsertHash(address, ((EShortFloat)value).GetValue());
}
}
//单点遥信
else if (typeId == 1)
{
int firstAddress = infoObjs[i].GetInformationObjectAddress(); // 信息地址起始位
int len = infoObjs[i].GetInformationElements().Length; // 所有信息值长度
for (int j = 0; j < len; j++)
{
int address = firstAddress + j;
if (address > 500 || address < 0)
{
Console.WriteLine("遥信信息超出定义范围:" + address);
}
var value = infoObjs[i].GetInformationElements()[j, 0];
var kv = "k:" + address + ",v:" + value + "\r\n";
sb.Append(kv);
Config104.InsertHash(address, ((ESinglePointWithQuality)value).IsOn());
}
}
}
if(checkBox1.Checked)
WriteLog.WriteLogs(sb.ToString());
}
上面代码是数据解析方法,由于没有数据库操作,解析之后的数据存放在Hashtable中,如果想扩展数据库操作,可以在这里修改或直接从Hashtable中获取。
Gitee源码地址:https://gitee.com/zhangzhenhuaO/iec104.git
也可以加作者微(xiaoyiyz)获取源码