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)组成。
APDU:等于APCI+ASDU,长度等于APCI+ASDU-2,减去起始字节和APDU长度字节。
U帧:控制报文帧(只包括APCI部分),主要有启动帧、停止帧、测试帧。
S帧:监视帧(只包括APCI部分),接收序号。
I帧:信息传输帧(包括APCI+ASDU部分)。
发送序号和接收序列号是保证数据完整性的条件。
类型标识定义发送数据的格式。
遥测常用类型标识
09—带品质描述的测量值,每个遥测值占3个字节
0d—带品质描述的浮点值,每个遥测值占5个字节
01—不带时标的单点遥信,每个遥信占1个字节
可变结构体定义发送数据信息是有序还是无序,有序即一个信息体地址,元素的对应地址会在此信息地址基础上依次加1。无序即1个地址对应一个元素。
传输原因定义记录传送的原因以及上行下行方向等。
流程
1. 由客户端(主站)向服务端(从站)发起TCP连接。
2. 连接成功后,向服务端发送链路启动帧。
3. 服务端收到启动帧以后,向客户端回应启动确认帧。
4. 客户端收到启动确认帧,向服务端发送总召唤命令帧。
5. 服务端收到总召唤命令数据请求后,发送总召唤命令数据响应帧,然后继续发送总召唤命令数据。总召唤命令数据发送完成后,发送总召唤命令数据结束帧。
6. 客户端在收到总召唤命令数据结束帧后,发送对时请求帧。
7. 服务器收到对时请求帧后,发送对时响应帧。
8. 由服务器主动向客户端发送变化数据帧。同时,收到客户端发送的控制类命令,回复相应的操作结果。
9. 客户端等到下一个数据总召唤命令数据周期,重复第5步之后的流程。
源码中是针对逆变器进行数据采集,每5分钟发送一次总召,没有涉及对时操作;总召时间间隔可根据需要调整。
程序解析
采用的C#编程语言,Windows Form窗体程序
启动后的窗体
Visual studio开发工具,Common文件夹存放公用类,IEC104文件夹存放104协议逻辑实体,Tools为工具,ZZH是Socket相关类,Form1是主窗体。
void HandleData(int typeId, InformationObject[] infoObjs){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < infoObjs.Length; i++) { //浮点遥测值 if (typeId == 13) { int firstAddress = infoObjs.GetInformationObjectAddress(); // 信息地址起始位 int len = infoObjs.GetInformationElements().Length / 2; for (int j = 0; j < len; j++) { int address = firstAddress + j; if (address < 16385 || address > 17000) { Console.WriteLine("遥测信息地址超出定义范围"); } var value = infoObjs.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.GetInformationObjectAddress(); // 信息地址起始位 int len = infoObjs.GetInformationElements().Length; // 所有信息值长度 for (int j = 0; j < len; j++) { int address = firstAddress + j; if (address > 500 || address < 0) { Console.WriteLine("遥信信息超出定义范围:" + address); } var value = infoObjs.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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!