一种分层、高扩展性通信协议的总结

渊源

大概因为自己专业是通信工程,所以一直以来对通信都还是很有兴趣的,通信协议的作用就是让通信的双方明确知道通信实体的意义。通信协议的范畴太大了,从最底层的硬件之间接口的协议到最上层的应用层的协议无一不是为了传输数据所做的工作

硬件的接口协议

  • 硬件的接口协议主要是为了正确的传输数据的逻辑0和1,在不同的物理实体接口中通过不同的电平标准,不同的时序,不同的物理线数来完成整个工作。平时接触到最多的IIC、SPI、UART、USB他们都有着自己一套物理层的通信接口不过线数和时序比较简单,除此之外,SATA接口,RJ45网口,HDMI接口等也有自己的一套物理层的接口协议他们的接口线数和时序通常都比较复杂。如果你不按照物理层的接口协议来传输数据那么对方连获取正确数据的能力都不具备。
  • 当两种不同的接口间相互兼容时就需要转换器,这个转换器其实就是具备两种硬件接口协议的设备,将一种接口的数据正确接收回来转化为另外一种接口可以识别的物理电平数据然后通过时序和接口线发送出去。当然有些时候是需要进行取舍或填补的,像VGA和HDMI,他们都属于传输视频的接口,但是VGA并不具备传输音频的能力所以HDMI转VGA必然会丢失音频的数据,同时因为VGA传输的是模拟的数据当转为HDMI时必然会丢失细节数据。
  • 硬件的接口协议类比于声带和耳朵,声带发声,耳朵收听声音。声音可通过空气传播,空气就是物理线,声带和耳朵就是一套收发的物理设备,他们之间的通信通过声带震动空气发射,耳朵通过耳膜接收震动进行获取。

上层的通信协议

  • 完成了硬件的接口协议就能正确的传输你想要发送的数据了对方也能正确的接收到你的数据,相当于你拥有一个完好的耳朵,能接收到各种符合人耳能听到的声音了(超声波次声波听不到).
  • 此时你还是语言不通!因为在你眼里都是声音但是你无法知道对面所说的是什么意思,这时候你就需要一个学习的过程,学习对方的语言。这个语言其实就是规定了各种发音的组合构成的意义也就是”通信协议”,我们只有知道每个发音以及构成的意义才能正确了解对方所说的含义.

前言

促使本文产生的源头就是入职这段时间里负责的一个测试工具的开发中所用到的通信协议,因为之前自己也做过一些通信协议不过感觉看了项目组里大佬给的这个协议以及思想感觉很受用,所以在这里记录一下。这里说的是(串口)UART为通信硬件载体进行说明.

自己做过的一些协议

  • 之前自己在本科期间做过的一些项目也需要根据传输的东西制定一些通信协议,不过都比较简单考虑的点也没有那么多,有基于16进制传输的也有基于字符串传输的16进制传输的基本就是为了机器更好识别,解析效率也会高很多。字符串为载体的传输就是为了更好的观察,更加容易分析16进制的传输比如SDH,网络音视频流等;字符串传输比如JSON,HTTP包。
  • 一个简16进制传输的控制协议,16进制传输,通过解析头尾提取出帧,但是没有差错校验。这可以当成一个数组然后分析每一部分就可以进行解析了,因为实现的功能比较简单所以也用不着很复杂。这种方式在不易调试但是数据量小并且解析较快。

|帧头|受控设备类型| 受控设备编号| 网关设备识别号| 命令/数据| 帧尾 |
| :—: | :—: | :—: | :—: | :—: | :—: | :—: |
|0XAA |设备属性|网关分配| 网关| 控制和交互|0X55|
|8bit |8bit| 8bit| 16bit| 8bit| 8bit|

  • 一个简单的字符串传输的控制协议示例,用于控制指示灯的状态,诸如充电,语音交互等模式。采用JSON为载体,方便调试和查看,但是解析效率较16进制传输低。
    JSON载体通信协议实例
  • 其实自己做的时候也有考虑过兼容和扩展的问题,不过其实都没有很好的解决办法,因为最好的就是变长的数据传输可以任意扩展数据的大小和数据形式,但是依然没有能解决后续扩展协议和前期协议之间的潜在冲突。但是接下来的这个协议组织方式很好的解决了这个麻烦。读完下面这个协议你就会觉得之前我做的,多捞哦。

分层、高扩展协议

  • 分层:分层的思想类比于计算机网络分层,不同的层次负责不同的功能。不过这里没有那么复杂只是思想上一致,那就是将以协议帧分为不同的层次,便于数据的控制和分析。所有的数据都按1字节对齐 。
  1. 顶级:控制传输层,基本包括,帧识别头、帧校验(CRC)、命令码、操作码、数据段长度、数据段载体。根据需要可以增加目的地址,源地址,返回码,消息码等用作透传和反馈的字段。其展现结构示例为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      struct{
    type Head; //帧识别头,用于标识帧的开始
    type CRC; //CRC校验,用于验证帧的完整性
    type Cmd; //命令码,用于标识帧的类型或者帧的作用
    // type MsgID; //消息码,用于标识本通信协议帧的识别码。
    type Operat; //操作码,用于标识帧的一些使用操作,比如是否是需要接收方进行响应。
    // type SrcAddr; //源地址,用于存储发送方的识别号,因为可能存在多个设备进行数据交互。
    // type DstAddr; //目的地址,用于存储接收方的识别号,因为可能需要中间设备进行透传。
    // type Ret; //返回码,用于反馈接收方的处理结果是否成功或者错误码。
    type Reserve; //预留
    type Datalen; //数据段的长度,用于进行数据段的准确读取。
    type Data[1]; //数据段的载体,这里只标识数据段的第一个字节使其内存连续,可连续读取数据。
    };
  2. 二级:用于统计三级数据子包的个数,因为每个帧可能包含若干个数据包,所以需要一个统计本次数据包的总数,便于数据包的分包。这里是不需要增加数据长度字段的因为每个数据包的长度是不一定的,其展现结构示例为:
    1
    2
    3
    4
      struct{
    type CellCnt; //数据细胞单元数量,用于统计三级数据细胞单元的个数
    type Data[1]; //数据段的载体,这里只标识数据段的第一个字节使其内存连续,可连续读取数据。
    };
  3. 三级:在该层级用于存储每个数据细胞单元的实体,包括一个数据包意义字段,一个数据长度字段,一个数据载体字段构成。这里是必须要使用数据长度字段的,因为每个数据包的长度是不定长的但是在内存里是连续的,如果没有该字段将无法进行下一个数据包的准确读取。其展现结构示例为:
    1
    2
    3
    4
    5
      struct{
    type CellCmd; //数据细胞单元的意义或命令,用于识别该数据包的具体意义,做相应具体的操作和处理。
    type Datalen; //数据段的长度,用于进行数据段的准确读取。
    type Data[1]; //数据段的载体,这里只标识数据段的第一个字节使其内存连续,可连续读取数据。
    };
  • 高扩展性 :因为分层之后每一个级别都有不同的作用,顶层和二层是不会变的,但是三层的数据结构是可以自由扩展并且不影响之前的已有的功能,可以根据需要增加四级五级等等,但是对于上层来说都是三级数据包都去按照 CellCmd 去识别是否是自己已有的,能处理的包,不能处理就丢弃,不会影响现有的功能。

抛砖引玉

一开始读取这个协议的是否觉得晦涩难懂,明明就那么点功能还要大费周折,又是封装又是又是分层,解析的难度大大加大,直接用数组下标宏去解析不就OK了。
后来搞通了,不得不说真的太有用了,无论你是多复杂的数据结构,按照这个来组包兼容性极强,并且代码阅读更加清晰。其传输采用16进制传输,找到帧头,强制类型转化为结构体指针,然后一层一层的剖析下去,扩展的数据包解析,就依次按照剥洋葱的方式层层递减。
当然上面的这个只是一种思想,至于所有的数据的分层结构,体系都可以千变万化,比如二级的数据包个数就可以归在顶级里面从而取消二级结构。
感谢安维大佬以及盛培大哥的代码、文档


一种分层、高扩展性通信协议的总结
http://example.com/2018/07/22/一种分层高扩展性通信协议的总结/
作者
Ekko bao
发布于
2018年7月22日
许可协议