【问题标题】:Best practice to implement fixed byte serial protocol in C++?在 C++ 中实现固定字节串行协议的最佳实践?
【发布时间】:2013-04-24 00:35:01
【问题描述】:

我有一个通过串行接口连接到 BeagleBone 计算机的设备。我以简单的二进制格式进行通信,例如

|MessagID (1 Byte) | Data (n Bytes) | checksum (2 bytes) |

每个命令的消息长度是固定的,这意味着在收到命令的第一个字节后知道要读取多少字节。经过一些初始设置通信后,它每 20 毫秒发送一次数据包。

我的方法是使用 termios 或类似串行库的东西,然后开始一个循环这样做(a:

while(keepRunning)
{
    char* buffer[256];
    serial.read(buffer, 1)
    switch(buffer[0])
        {
        case COMMAND1:
            serial.read(&buffer[1], sizeof(MessageHello)+2); //Read data + checksum
            if (calculateChecksum(buffer, sizeof(MessageHello)+3) )
            {
                extractDatafromCommand(buffer);
            }
            else
            {
                doSomeErrorHandling(buffer[0]);
            }
            break;
        case COMMAND2:
            serial.read(&buffer[1], sizeof(MessageFoo)+2);
            [...]
    }
}

extractDatafromCommand 然后会创建一些结构,例如:

struct MessageHello
{
    char name[20];
    int version;
}

将所有内容放在自己的读取线程中,并使用信号量(或简单标志)向程序的其他部分发出新数据包的可用性信号。

这是一个可行的解决方案还是有更好的改进(我假设是这样)?

也许创建一个抽象类 Message 并派生其他消息?

【问题讨论】:

  • 您可以添加一个退出while循环的命令,这在某些情况下可能很有用
  • 我认为拥有该阅读线程是个好主意。最好不要阻塞命令。一些错误恢复机制也会很有用。
  • 您可以为每条消息定义一个结构,然后使用 sizeof() 代替硬编码的常量。
  • 您的程序似乎假定接收到的第一个字节是消息帧的第一个字节。这种帧同步将在它读取消息时持续存在。如果串行字符丢失,所有这一切都会中断。查找并维护消息帧对于稳健的串行通信协议至关重要。消息 ID 和 2 个校验和字节可能是查找可变长度消息帧的挑战。您可能需要添加一个固定值同步字节来简化寻找消息帧开始的过程。
  • “固定字节” 具有误导性。您有 可变 长度的消息。长度是从 messageID 派生/推导出来的,而不是显式存储在消息中只是一个小细节。与固定长度的消息相比,对于可变长度的消息,搜索消息帧的开头更加困难。如果您的 USART 具有 “接收器超时” 功能,那么您可以使用该计时器来帮助检测单个消息帧(即消息之间的 20 毫秒)。但是检测消息之间的时间间隔必须由硬件完成,而不是软件。

标签: c++ linux serial-port protocols


【解决方案1】:

这真的取决于。两种主要方式将被线程化(就像你提到的那样)和事件。

线程代码很棘手,因为您很容易引入竞争条件。你测试了一百万次的代码在工作了几天、几周或几年后偶尔会出错并做错事。很难“证明”事情总是会正确运行。像“i++”这样看似微不足道的东西突然变成了有漏洞的抽象。 (见why is i++ not thread safe on a single core machine?

另一种选择是事件编程。基本上,您有一个在所有文件句柄上执行 select() 的主循环。任何准备好的东西都会被查看,并且您尝试在不阻塞的情况下读取/写入尽可能多的字节。 (通过 O_NONBLOCK)。有两个棘手的部分:1)在没有办法返回主循环的情况下,您绝不能进行长时间计算,以及 2)您绝不能进行阻塞操作(内核停止您的进程等待读取或写入) .

在实践中,大多数程序的计算时间都不长,而且审核少量代码以阻止调用比审核竞争更容易。 (尽管在不阻塞的情况下做 DNS 比它应该做的要复杂。)

事件代码的好处是不需要锁定(无需担心其他线程)并且浪费的内存更少(在一般情况下,您要创建大量线程。)

您很可能想使用串行库。 termios 处理只是开销和杂散字节做坏事的机会。

【讨论】:

  • 感谢您的概述。我没有在问题中写下,这个通信接口将是已经有不同线程的项目的一部分。因此,这次事件循环不是一个选项,但我会记住它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-03
  • 2014-02-06
  • 1970-01-01
  • 2014-03-10
  • 2010-12-16
  • 1970-01-01
  • 2018-12-18
相关资源
最近更新 更多