设备通信协议制定的一些误区避免

计算机设备,通信的核心和执行的核心,是01组成的字节流,而字节流中的指令和数据,如何有效的执行和传输,需要对应的协议来规范和执行。

因项目和编写框架需求,前段时间,写了一套IO板卡的设备通信协议,一套RMI远程过程调用的自封装协议,一套与PLC的自动化通信协议,在制定协议的过程中,遇到了很多问题,尤其是协议编写完成之后,后期实现过程,在解包数据处理等过程中,发现了很多致命的问题,曾经为了解决这些问题绕了很多弯路。

现将遇到的几大误区总结一下:

1、字节序问题,这个其实如果稍微有点设备协议开发经验,都会注意的,字节序分big-endian和little-endian,一般基于X86的设备,或常用的高级编程语言编写的代码,都是基于little-endian方式,比如:C#或C++开发的程序,而Arm处理器架构下的应用包括一些PLC等工业自动化设备,都是基于big-endian,这个在协议通信中,尤其是传输Int32,Int16等多字节数据类型时,一定要考虑字节序问题,传输时,只是字节流,需要截取字节流中的字节做转换,稍不注意,就可能转换不了,或转换不对,这个曾经在IO板卡的设备通信协议制定中,被折腾了很长时间,IO板卡底层是基于STM32的处理器,自然是big-endian,而上位机用的是little-endian,字节序很小,但是协议中很关键。

2、协议关键字问题,一般在制定协议时,为了便于分包处理,我们会有包头、包尾、校验位、长度位、数据位等,在复杂协议制定中,通常预先,我们是不明确,数据位中所传输的数据,可能数据位,是基于序列化方式,存储的序列化字节流,也有可能就是一段特定的字节流,这会引起什么问题呢,数据包中的数据字节流,有可能与我们制定的包头、包尾字节冲突了,相同的了,这势必会造成在连接会话层,对数据分包,做很多条件限制,要明确知道在通信链路上的每一包的处理,此时包括:连包、粘包等各种通信上数据包的拆分问题,这种情况很容易出现拆错包的问题,在RMI框架设计时,一般是将结构化数据序列化之后,一定要进行转码处理,转成UTF-8或UTF16等传输,因为转码的过程中,其对应的高字节上,会刻意的加入了转码处理,不会出现连续的0xFFFF等高位的字节流,我们就可以把这种字节流做为包头包尾字节位使用,这会极大的减轻拆包的压力,算法会简单很多。转码带来的麻烦是,传输的数据流增大。在设备通信协议的过程中,我们如果没有转码机制,可以采取另外一种方式,将每一个字节,按照前4位或后4位,拆分成两个字节,比如BCD转码,这样数据流中传输的数据,也不会出现0xFF这种大的字节位,我们可以把大的字节位,利用起来做为包头、包尾分割。

3、校验位很重要,所有的数据包一定要有校验位,在一些场合,我们只能用串口等一些非可靠的传输链路,进行数据传输,必须要考虑传输过程中,由于距离或电磁环境等,造成的丢帧情况,此时的数据包中,一定要有校验位,校验位,可避免错误拆包读取到脏数据,也可以避免数据包在传输过程中被篡改,造成的数据不一致,这在工业现场会很致命的,假设某一个位,本来表示关闭某一个设备,因为电磁干扰,对应位发生变化,执行机构获取到的可能就是一个打开设备状态。校验位需要注意,一定不要搞很长,尤其是在一些协议比较短的场合,校验位建议,用一个自己就可以了,可以规定对某段字节流,做异或或校验和运算,复杂一点的来个CRC校验等都可以。

4、消息ID也很关键,在一些交互性比较高的应用场合,必须加入消息ID,比如RMI的远程过程调用,不加入消息ID,在异步回调回客户端,更新发起请求同步时,很容易找不到对应的发起请求,而错误的更新了不该更新的数据。在设备通信场合,如果能在协议包中,加入消息ID,可以实现对每一条设备指令的监控,无论是同步指令还是异步指令,消息ID结合着超时时间,我们可以实现对设备的精准控制,这在闭环设备控制中尤其重要,每一条指令都应该有明确的ACK、NAK、TimeOut状态,这样才可以实现明确的闭合控制。

5、指令和数据尽可能在单一数据包中,曾经有个项目,与PLC的自动化通信,双向交互就两个数据包,一个大的发送数据包,一个大的接收数据包,所有的指令数据都,放在这些大包里,指令和数据最终还是要分解的,因为大包中的每一个位的变化,其对应的数据包就需要重发,这会造成,在解包之后,需要对每一次解包的数据,进行状态机保存,检查对应的位是否发生过变化,如果发生过变化,再更新状态机,这种包的制定的最大悲剧是,当增加了新的协议功能时,需要对整个解包策略重新编写,会发现麻烦一大堆,好处是制定数据包简单,只有一个数据包。好的做法是,一条指令,一个数据包,每条指令有自己对应的应答代码和解包逻辑,增加了新的指令时,只需要加入新的指令的应答代码和解包策略即可。在IO板卡的协议编写时,因为初期功能不是太明确,功能需要较少,随着逐渐的熟悉,功能越加越多,最后整个板卡协议做完,有几十个功能,对应着几十种处理命令和应答命令,好处是,随着开发周期的延长和功能的逐步递加,新的版本总是对老的版本自动进行协议兼容,因为新的功能增加,不会对老的协议或功能产生任何影响。

6、需要注意传输数据量的问题,在协议包制定中,比较省事的处理是,每一个过程调用或指令处理,都是放在一个数据包中,但这个在一些大数据量调用的过程中会遇到问题,比如RMI框架的调用中,如果数据流只有几个M或几十M,因为网络传输速度比较快,我们可以放到同一个封包中,但是当一个过程调用中出现了海量数据,上GB的数据,就会遇到很大的麻烦,经常可能传输到最后一个环节,因为网路问题,造成前面传的所有数据,跟着一起丢失,此时封包协议一定要有数据分包处理模块,因为虽然是海量数据,但所有的数据,需要在一个事务中进行,这就需要在封包中,引入应用层的分包和拆包环节,对整个流,引入偏移量,这样在封包中才能从容应对大数据。

7、不要太依赖,通信环节的时间做自动分包,我接触过一些协议,喜欢利用通信帧,之间的间隔时间,做数据拆包的依据,这个在一些情况下会有问题的,比如,上位机的线程调度过去频繁时,对通信缓冲区上的中断响应,可能就不会那么及时,不能立刻对缓冲区的数据取走,这就会造成粘包的出现,如果这时候,利用时间间隔,做为分包依据,很可能得到了两个错误的数据包,我们可以利用,通信帧的时间间隔,辅助检测缓冲区的数据情况,实现数据快速转移,而不是在包协议制定中,利用了传输层的功能,协议包属于应用层的一定要明确。

8、协议规范中,尽可能的用UniCode 做数据编码,而不要用AcsII,虽然协议可能眼前是用来做英文字符传输,可能哪一天突然就要求传输中文或其他字符,这会儿再去改变就悲剧了。


你可能感兴趣的:(设备通信协议制定的一些误区避免)