前言
如果日常的工作开发中,经常涉及到多设备/多模块协同工作的软件设计,那么定义一个基于硬件通信接口之上的私有通信协议规范是必不可少的。
稳定、可靠、规范的通用通信协议,有利于模块化、提高复用、抗干扰能力强、方便做扩展设计等等,本文以私有串口通信协议为例,分享个人对自定义协议的理解、中间层组件库软件(句柄数据结构、组包解包函数)设计以及业务设计等内容。
协议格式
通信协议通常表现为数据包的格式定义,其可以概括为 起始位、包头部分、数据部分、检验位、结束位 这几项。
包的构成要素
首先,在通信协议中,定义一个数据包的起始位和结束位是必要的,由此来识别收到一个完整的数据包。以下为一个包的常见构成要素:
协议版本、包序号、包类型、控制位、保留位、数据长度、指令、数据、检验位
注意:多设备设计时需要设计地址,本文主要讲述两设备间的串口通信协议,不涉及多设备间的通信。
在通信过程中,设备在收到一个包后,其也要有针对这个包的回复包。所以,定义发送包和回复包格式有一定必要性,具体取决于实际需求。发送包可以有多种包类型,针对发送包的回复包也要相应的回复响应包类型。 回复包需要包含所要回复的发送包包序号。发送方向可以是 控制器 to 控制器, 控制器 to 模块,也可以是 模块 to 控制器,等等不固定
发送包格式
协议数据发送包–包括以下元素:
- 起始位
- 协议版本:用于区分不同版本的通信协议
- 包序号:用于指示当前的包的序号,用于指示
- 包类型:用于区分不同类型的发送包,还是不同类型的回复包,具体设计取决于开发者
- 控制位:用于指示包的特性,比如是否需要校验、是否有分包、是否是分包中的结束包等等,用于满足开发者的特殊需求
- 保留位:用于可能的后续扩展设计
- 数据长度:可以用于指示 指令码 + 数据 的长度
- 指令码:用于指示私有协议中的交互控制命令
- 数据:指令码附带的设置数据
- 检验位:对包数据的检验,可以定义为 CRC 校验,也可以是和校验等等
- 结束位
回复包格式
协议数据回复包–包括以下元素:
- 起始位
- 协议版本:用于区分不同版本的通信协议
- 包序号:用于指示当前的包的序号,用于指示
- 包类型:用于区分不同类型的发送包,还是不同类型的回复包,具体设计取决于开发者
- 控制位:用于指示包的特性,比如是否需要校验、是否有分包、是否是分包中的结束包等等,用于满足开发者的特殊需求
- 保留位:用于可能的后续扩展设计
- 数据长度:可以用于指示 指令码 + 数据 的长度
- 接收的包序号:用于指示是针对收到哪一个包的回复
- 指令码:用于指示收到私有协议的交互控制命令
- 数据:指令码附带的设置数据,可以用于指示确认结果、执行结果、或者执行完命令后附带的回复数据
- 检验位:对包数据的检验
- 结束位
包类型
包类型的设计不是必须的,由开发者根据实际的业务需要进行评估
以本文设计为例,将发送包类型大致分为以下几种:
- 发送命令包:发送方指示接收方执行相应的动作指令,可指定是否需要接收方回复
- 发送数据请求包:发送方指示接收方回复其所需要的业务数据
- 发送通知包:发送方主动通知接收方在己方所变化的一些事件,可指定是否需要接收方回复
回复包类型分为以下几种:
- 命令响应包:接收到发送命令包,接收方进行响应,并回复一个命令响应包至发送方
- 数据响应包:接收到发送方的数据请求,回复数据响应包至发送方
- 通知响应包:接收到发送方的通知,回复通知响应包至发送方
协议工作流程
此部分略,主要为双方开始建立通信,进行初始化的流程,由开发者根据实际需要进行设计即可。
如:握手、回复、交互获取、通知进行基本信息初始化、心跳包
通用中间件软件设计
在本文的私有通信协议开发中,
设计通用中间件,主要有两点,一是私有句柄的数据结构设计,二是软件函数设计
软件设计要素
回调函数(包括私有句柄、包类型、命令码、数据缓存指针、数据长度)
超时设计
分包设计
发送间隔设计
最大最小包限制设计
应用状态机进行解包设计
容错设计
滑动窗口设计:一次传入的并不是整包,而是半包,后面再递进传入后半包。
每个通信接口实现都维护有一个私有句柄,其中包含私有的Buffer,当一次性传入半包数据时,将包数据拷贝到私有Buffer中,而后会停留在解包状态中,而不会认为此包数据无效
粘包:一次读取到两个包
根据Buffer顺序递增解包,解完一包后触发回调函数,返回后接着解Buffer中剩下的包
宏定义及数据类型设计
私有句柄结构设计
注册回调函数和硬件层的发送驱动
解包函数设计
提供多个接口包括:逐个字节解包,多个字节解包
应用状态机的迭代循环解包方式
1 |
组包函数设计
串行通信业务软件设计
初始化软件设计
业务处理软件设计
可以应用命令模式,在业务上层将接收到的串口命令与执行函数建立一个映射执行关系
疑惑解答
复杂度分析及软件方案取舍
包含所有容错的软件设计,对接收到的包逐字节分析,时间复杂度:O(n^2)
在迭代循环里判断,空间复杂度为:O(1)