12.2. 四个层¶
协议库构建为四个层的堆栈,每一层解决单个问题,并构建在它下面的层之上。本章的其余部分将从下到上遍历这个堆栈。
12.2.1. 传输¶
最底部是摄像头与主机之间的字节管道。协议库不在意由哪一种来承载字节:
通过摄像头插入的 USB 端口进行 USB-CDC。这是每台摄像头的默认且带宽最高的选项。
通过摄像头上一对 GPIO 引脚进行 UART,连接到主机上的串行适配器。适用于 USB 端口繁忙或无法物理访问的无头部署。
传输层的唯一工作就是“字节进去,字节出来,且保持顺序”。这一层之上的一切都假定传输按写入顺序传递字节,但允许字节本身被损坏或链路完全中断。有损突发(缺失几个字节)和干净中断(整条链路消失一段时间后又恢复)都在更上层处理。
12.2.2. 分帧¶
再往上一层为字节流强加结构。每条消息都成为一个数据包——一个 10 字节的头部,后跟一个有效载荷,再后跟一个 4 字节的尾部。头部携带:
一个 2 字节的同步字(
0xD5AA),让接收方在失步后重新找到数据包的起始位置。一个 1 字节的序列号,供可靠性层使用。
一个 1 字节的通道 ID,表示数据包属于哪个逻辑流。
一个 1 字节的标志字段,用于 ACK / NAK / 分片 / 事件位。
一个 1 字节的操作码,用于区分协议命令、系统命令和通道命令。
一个 2 字节的有效载荷长度。
一个针对前八个头部字节的 2 字节 CRC。
有效载荷紧随其后,然后是一个针对有效载荷本身的 4 字节 CRC。这两个 CRC 独立地捕获损坏:头部中翻转的一位会使头部 CRC 失效,接收方无需读取有效载荷即可丢弃该数据包。
12.2.3. 可靠性¶
可靠性层将“可能到达的数据包”变为“已经到达的数据包”。它跟踪头部中的序列号,要求对方为每个需要确认的数据包发送确认,并在确认未在超时内到达时进行重传。默认情况下,重传超时从 500 毫秒开始,每次重试翻倍,放弃前重试三次。
上述每种行为都可在 protocol.init() 调用中配置:对于单向流可以关闭 ACK,在完全干净的传输上可以跳过 CRC 验证,并且可以针对慢速或高延迟链路调整重传参数。
12.2.4. 通道¶
最顶层是应用代码所看到的。通道 是一个由 0 到 31 的通道 ID 标识的命名逻辑流。一个传输上最多可共存 32 个通道;每个通道都独立于其他通道,通过其在每个数据包头部中的 ID 寻址。摄像头启动时带有四个内置通道——stdin、stdout、stream 和 profile——应用代码通过调用 protocol.register() 并传入一个 Python 类在其之上注册更多通道。
这四个层不混淆各自的职责。分帧层不了解通道;可靠性层不了解数据包内容;通道层不了解字节如何到达。正是这种分离使得传输的更换(例如从 USB 到 UART)不会向上波及通道代码,也正是它使得本章的其余部分可以一次一层地逐步阅读。