在面向字节的协议之上改进点对点层的设计

网络工程 设计 点对点
2021-07-11 18:58:01

我想设计一个非常简单、高效的点对点协议层,它在低端 8 位微处理器上使用 UART。它的 RAM 极其有限,而且只有最简单的算术运算。UART 处理字节的成帧,可以假设该层可以依赖这一事实。(错误检测将在更高级别上完成)。

该协议将按如下方式工作:

  • 服务器和客户端无法区分
  • 接收和传输是完全独立的(所以只需要考虑单向)
  • 无连接且不可靠(一个帧/数据包将被发送并被遗忘)
  • 帧/数据包将以start 分隔,因此在检测帧的开始时需要尽可能少的 RAM(以几个字节的顺序)。
  • 帧/数据包将以结束分隔,以区分 1. 接收下一个字节的延迟和 2. 帧/数据包的结束。

我的第一个想法如下:

  • 一帧的开始是由一个字节的:00xxxxxx(其中,x可被用于指定帧的类型的更高级别的)
  • 帧的结尾由一个字节组成:(11xxxxxx其中x被丢弃)
  • 该帧包含任意数量的内容字节:(内容01xxxxxx在哪里x

这意味着微处理器在等待帧开始时实际上不需要任何 RAM;它只需要在接收到的每个字节时检查它是否匹配00xxxxxx

这个想法的问题显然是浪费带宽:每 8 位中有 2 个(加上整个结束分隔字节)。

我的第二个有缺陷的想法是用两个字节而不是一个字节做类似的事情。例如:

  • 一帧的开始由一个字节组成,如上所示: 00xxxxxx
  • 帧的结尾也将是一个字节,如上所示: 11xxxxxx
  • 内容将以字节对为单位:01xxxxxx,xxxxxxxx

但这是有缺陷的,因为内容字节对的第二个字节可能与开始或结束定界符混淆。UART 可以在没有警告的情况下随时断开和重新连接,因此该层需要有一种方法可以通过字节值找到数据包的开头。

我的逻辑失败了 - 我看不出有什么方法可以改进我的第一个版本。我一直在阅读大量不同的协议,但找不到任何可以适应的协议,更不用说按原样使用了。我的第二个想法有致命的缺陷吗?或者我只是没有意识到如何在不在 RAM 中存储太多的情况下将起始分隔符与第二个内容字节的分隔符区分开来?

欢迎任何想法,包括我可以适应或采用的协议,或对上述想法的更改。


回复评论

UDP 看起来很像矫枉过正,太高级了。我当然不需要寻址(端口或其他)。我不认为 OSI 模型可以应用于我的要求,但是我需要在数据链路层而不是稍后传输 (UDP) 上做更多的事情。它将是(可重用堆栈的一部分)通过虚拟 COM(串行)端口(使用蓝牙-UART 或 USB-UART 硬件桥接器)在主机(通常是 PC)和微处理器(PIC、Atmel)之间进行通信)。它绝不是一个一刀切的协议,而只是适合非常有限的硬件功能来执行任务,例如发送键+值对或命令+参数等,其中值/参数将是可变的长度,从 0 到超过 2^16(但可以拆分)。一些接近但也太过分的协议:

最优先考虑的是低计算要求和资源,其次是带宽效率。

微处理器总是要么在接收数据时使用数据,要么在获取数据时发送数据,这就是它们没有太多 RAM 的原因。他们也可能进行实时处理,因此他们需要处理的传输/接收数据越少越好。使用低端微处理器的原因是它们的数量很多,并且需要降低成本。

2个回答

想法 1:ASCII

有关使用 ASCII 控制字符的信息,请参阅SE 上的这个问题

想法2

关于 HLDC 的那篇文章给了我一个简单的想法,它使用一个字节作为开始分隔符,一个字节用于结束分隔符,一个用于转义数据包中的数据:

  • 起始字节:0x00(8 位十六进制表示法)
  • 转义字节: 0x01
  • 结束字节: 0x02

所以一个数据包总是以 开始0x00和结束0x02其中的字节只有在匹配0x00,0x02或 时才会被转义0x03转义是通过在该字节前面加上0x01. 对于随机分布的数据值,这意味着协议每帧的字节开销是这样的:

1 + 3n/256 + 1 

其中 n 是数据字节数。(这比我问题中第一个浪费了 1/4 + n/4 + 1 的例子更有吸引力)

如果以与我问题中的第一个想法相同的方式进行处理,则所需的 RAM 字节数将仅为 1。

我也考虑过做类似的事情,但有两个字节:

开始:0x0000 逃脱:0x0101 结束:0x0202

所需的 RAM 为 3 个字节,协议开销为:

2 + 6n/2^16 + 2

最后一个想法是处理半字节序列(4 位),但它的带宽效率仅比我问题中的第一个示例高,并且计算量要高得多,因此不值得一提。)

所以在这两个选项之间,我现在有很多用途(如果有人关心,我会选择一字节版本!)。我已经订购了一些有关数据链路层的书籍,因此将添加任何进一步的想法。

我仍然很感激任何人提出的任何其他想法

您可以使用以下协议(受 PPP over Asynchronous Links RFC 的启发),带有两个特殊字符 IDLE 和 ESC。发送器需要大约 2 个字节的 RAM,接收器需要 2 个字节来维护状态信息(另外还有两个字节用于 CRC 计算)。有效载荷数据是任意的,从 0x00 到 0xFF。

IDLE是 0x7E,ESC(用于 Escape)是 0x7D。发射器没有要传输的有效载荷数据时,它
可以在任何时候发送空闲
当发射器想要发送一个帧/数据包时,它必须发送 IDLE,然后是转义的(见下文)有效载荷数据,然后是 IDLE。

由于 IDLE 不应该出现在帧数据中,因此我们定义了第二个特殊字符 ESC,发送器要转义数据时将使用该字符。IDLE 和 ESC 本身至少会被转义。

当发送器想要发送有效载荷字节:
- 0x7E (IDLE) 时,它传输两个字节:ESC 后跟 (IDLE ^ 0x40) [IDLE XOR 0x40]
- 0x7D (ESC),它传输两个字节:ESC 后跟 (ESC ^ 0x40)
- 其他字节,它传输字节不变。或者,该字节可以由 ESC 后跟 (byte ^ 0x40) 转义,前提是 (byte ^ 0x40) 不会导致 IDLE 或 ESC。

转义公式(byte ^ 0x40)并不神奇,可以用另一个代替。重要的是,如果转义公式的结果永远不是 ESC 或 IDLE。

从接收器的角度来看,两个 IDLE 之间接收到的任何数据都会构成一个帧/数据包。接收器必须取消转义这些数据才能获得初始有效载荷数据。

当然,强烈建议在帧/数据包的末尾添加一个(2 字节)CRC,就在关闭 IDLE 之前。该 CRC 也必须被 ESCaped。CRC 可以逐字节地动态计算。

我已经多次实现这种协议(准确地说是:封装),它非常简单、可靠和高效。