C/C++编程-理论学习-通信协议理论

news2024/10/6 19:55:06

通信协议理论

  • protobuf
    • 简述
    • 使用简介
      • proto 文件
        • 为了nanopb 编译.proto文件
        • 修改生成器行为
      • streams
        • output streams
        • input streams
      • Data types(数据类型)
      • Field callbacks(字段回调)
      • Encoding callbacks(编码回调)
      • Message descriptor(信息描述)
        • 三个关键字required、optional、repeated 的理解(这个protobuf的关键字,nanopb不支持)
      • Oneof(多中呈一,可理解是一种联合体)
      • Extension fields(扩展字段)
      • Default values(默认值)
      • Message framing
      • Return values and error handling
      • static assertions
    • 参考文献

protobuf

简述

作用:
1. 将结构化数据 序列化 进行信息通信、存储。意为,数据结构化管理;意为,对结构化的数据进行序列化,便于发送、存储。可类比XML、JSON。

弊端:
1. buffer占用额外空间,传输比透传降低很多。(这里有一个故事,我们领导极力让单片机和上位机通信,采用protobuf传输数据,端口为串口。我问有何好处,不采用透传自定义协议的原因?他回答protobuf传输效率更高,比如连续8个字节都是0x00,它会智能简化、压缩传输,比如传输0x00,还附带额外信息表明共有8个连续此字节信息。后来事实证明串口透传效率更高,如果透传花费2ms的话,protobuf装填message,序列化,然后将绑定buf传输出去,总共约需要将近20ms,大约差10倍的效率),其实道理也很简单,自定义的透传协议本质不需要序列化,已经是二进制的数据序列了,只要根据格式直接传输、解析即可,效率自然极高。

使用简介

《Nanopb:Basic concenpts》
在这里插入图片描述

contents :

  • Proto 文件
    • 编译文件
    • 修改生成行为
    • 输出流
    • 输入流
  • 数据类型
    proto 描述 和 生成的数据结构
  • Field callbacks
    • 编码回调
    • 解码回调
    • 功能名字绑定回调
  • 信息描述
  • Oneof
  • Extension fields
  • Default values
  • Message framing
  • Return values and error handling
  • Static assertions

proto 文件

为了nanopb 编译.proto文件
修改生成器行为

写一个和.proto同名的 .options文件。使用生成器选项文件,可以设置字段的最大大小,进而静态申请其内存。

# Foo.proto
message Foo {
   required string name = 1;
}

# Foo.options
Foo.name max_size:16

streams

回调的通用准则:

  1. IO有错误,编码和解码进程立即终止
  2. 用 ”state“ 存储自己的数据,例如文件描述符
  3. 通过pb_write 和 pb_read 更新 bytes_written 和 bytes_left
  4. 回调也可用于次数据流,,结构内值和初始流近似
  5. 总是读取或写入所请求的完整长度的数据
output streams
struct _pb_ostream_t
{
   bool (*callback)(pb_ostream_t *stream, const uint8_t *buf, size_t count);
   void *state;
   size_t max_size;
   size_t bytes_written;
};

如果callback是空,则只简单的数bytes_written进行发送,并且max_size会被忽略。
否则,bytes_written(要写的数据)+ 已经被写的数据 比 max_size 大, pb_write 会在做任何事之前,返回错误。 如果你不想限制流的大小,注销掉SIZE_MAX即可。

/* example1 */
Person myperson = ...;
pb_ostream_t sizestream = {0};
pb_encode(&sizestream, Person_fields, &myperson);
printf("Encoded size is %d\n", sizestream.bytes_written);

/* example2 */
bool callback(pb_ostream_t `stream, const uint8_t `buf, size_t count)
{
   FILE *file = (FILE*) stream->state;
   return fwrite(buf, 1, count, file) == count;
}

pb_ostream_t stdoutstream = {&callback, stdout, SIZE_MAX, 0};
input streams

不需要知道消息长度。读取时获得EOF错误,将bytes_left设置为0,并返回false。pb_decode会检测到,并且如果EOF出现在恰当位置,会返回ture。

struct _pb_istream_t
{
   bool (*callback)(pb_istream_t *stream, uint8_t *buf, size_t count);
   void *state;
   size_t bytes_left;
};

callback 必须赋值函数指针。bytes_left是将要读取的字节数的上限。如果回调函数像上面描述的那样处理EOF,则可以使用SIZE_MAX。

bool callback(pb_istream_t *stream, uint8_t *buf, size_t count)
{
   FILE *file = (FILE*)stream->state;
   bool status;

   if (buf == NULL)
   {
       while (count-- && fgetc(file) != EOF);
       return count == 0;
   }

   status = (fread(buf, 1, count, file) == count);

   if (feof(file))
       stream->bytes_left = 0;

   return status;
}

pb_istream_t stdinstream = {&callback, stdin, SIZE_MAX};

Data types(数据类型)

Field callbacks(字段回调)

Encoding callbacks(编码回调)

Message descriptor(信息描述)

要使用pb_encode()和pb_decode()函数,你需要对消息中包含的所有字段进行描述。该描述通常从.proto文件自动生成。

三个关键字required、optional、repeated 的理解(这个protobuf的关键字,nanopb不支持)
  • required关键字
    顾名思义,就是必须的意思,数据发送方和接收方都必须处理这个字段,不然还怎么通讯呢
  • optional关键字
    字面意思是可选的意思,具体protobuf里面怎么处理这个字段呢,就是protobuf处理的时候另外加了一个bool的变量,用来标记这个optional字段是否有值,发送方在发送的时候,如果这个字段有值,那么就给bool变量标记为true,否则就标记为false,接收方在收到这个字段的同时,也会收到发送方同时发送的bool变量,拿着bool变量就知道这个字段是否有值了,这就是option的意思。

这也就是他们说的所谓平滑升级,无非就是个兼容的意思。

  • repeated关键字
    字面意思大概是重复的意思,其实protobuf处理这个字段的时候,也是optional字段一样,另外加了一个count计数变量,用于标明这个字段有多少个,这样发送方发送的时候,同时发送了count计数变量和这个字段的起始地址,接收方在接受到数据之后,按照count来解析对应的数据即可。

【注】上面关键字说明是基于proto2版本的,在proto3上,关键字做了很多调整,比如去掉了required,默认什么都不写就是required。如果使用optional,可以使用。但是protobuf-c的实现(即c语言版本的protobuf)没有支持该关键字,所以最好改成oneof关键字替代,效果是一样的,repeated保持和proto2版本一样,整体说proto3的语法简洁很多。

举一个二级信息的例子,在Person.proto 文件中:

message Person {
    message PhoneNumber {
        required string number = 1 [(nanopb).max_size = 40];
        optional PhoneType type = 2 [default = HOME];
    }
}

这会在.pb.h文件中转换生生一个宏

#define Person_PhoneNumber_FIELDLIST(X, a) \
X(a, STATIC,   REQUIRED, STRING,   number,            1) \
X(a, STATIC,   OPTIONAL, UENUM,    type,              2)

然后在.pb.c文件中有一个宏”PB_BIND"会被调用:

PB_BIND(Person_PhoneNumber, Person_PhoneNumber, AUTO)

这个宏会组合生成 pb_msgdesc_t 结构 和 相关列表:

const uint32_t Person_PhoneNumber_field_info[] = { ... };
const pb_msgdesc_t * const Person_PhoneNumber_submsg_info[] = { ... };
const pb_msgdesc_t Person_PhoneNumber_msg = {
  2,
  Person_PhoneNumber_field_info,
  Person_PhoneNumber_submsg_info,
  Person_PhoneNumber_DEFAULT,
  NULL,
};

编码和解码函数接受一个指向该结构的指针,并使用它来处理消息中的每个字段。
【注1】:原来这就是我找不到生成的消息描述结构的原因,只找到了在.pb.h中的声明(如下图),原来是在.pb.c中通过宏生成的。

/* .pb.h */
extern const pb_msgdesc_t Serial_ack_repeat_msg;
extern const pb_msgdesc_t Serial_fault_msg;
extern const pb_msgdesc_t Serial_heart_beat_msg;
extern const pb_msgdesc_t Serial_ready_msg;
extern const pb_msgdesc_t Serial_system_info_msg;
extern const pb_msgdesc_t Serial_ir_msg;
extern const pb_msgdesc_t Serial_fan_msg;
extern const pb_msgdesc_t Serial_imu_msg;
extern const pb_msgdesc_t Serial_imu_speed_msg;
extern const pb_msgdesc_t Serial_motor_msg;
extern const pb_msgdesc_t Serial_power_msg;
extern const pb_msgdesc_t Serial_power_batt_msg;
extern const pb_msgdesc_t Serial_power_chg_msg;
extern const pb_msgdesc_t Serial_nfc_msg;
extern const pb_msgdesc_t Serial_button_msg;
extern const pb_msgdesc_t Serial_tx_rx_msg;
extern const pb_msgdesc_t Serial_tof_msg;
extern const pb_msgdesc_t Serial_touch_msg;

/* .pb.c */
PB_BIND(Serial_ack_repeat, Serial_ack_repeat, AUTO)
PB_BIND(Serial_fault, Serial_fault, AUTO)
PB_BIND(Serial_heart_beat, Serial_heart_beat, AUTO)
PB_BIND(Serial_ready, Serial_ready, AUTO)
PB_BIND(Serial_system_info, Serial_system_info, AUTO)
PB_BIND(Serial_ir, Serial_ir, AUTO)
PB_BIND(Serial_fan, Serial_fan, AUTO)
PB_BIND(Serial_imu, Serial_imu, AUTO)
PB_BIND(Serial_imu_speed, Serial_imu_speed, AUTO)
PB_BIND(Serial_motor, Serial_motor, AUTO)
PB_BIND(Serial_power, Serial_power, AUTO)
PB_BIND(Serial_power_batt, Serial_power_batt, AUTO)
PB_BIND(Serial_power_chg, Serial_power_chg, AUTO)
PB_BIND(Serial_nfc, Serial_nfc, AUTO)
PB_BIND(Serial_button, Serial_button, AUTO)
PB_BIND(Serial_tx_rx, Serial_tx_rx, AUTO)
PB_BIND(Serial_tof, Serial_tof, AUTO)
PB_BIND(Serial_touch, Serial_touch, AUTO)

【注2】:看到这里,我突然明白原来nanopb采用了两个结构体,一个用于数据结构,一个用于数据结构的描述。举例如下:

/* 数据结构 */
typedef struct _Serial_fan {
    Serial_fan_value_t value;
    Serial_fan_id_t id;
    Serial_fan_fg_t fg;
} Serial_fan;

/* 数据的描述结构 - 通过.pb.c的宏生成 */
extern const pb_msgdesc_t Serial_fan_msg;

Oneof(多中呈一,可理解是一种联合体)

举例子:

/* .proto */
message MsgType1 {
    required int32 value = 1;
}

message MsgType2 {
    required bool value = 1;
}

message MsgType3 {
    required int32 value1 = 1;
    required int32 value2 = 2;
} 

message MyMessage {
    required uint32 uid = 1;
    required uint32 pid = 2;
    required uint32 utime = 3;

    oneof payload {
        MsgType1 msg1 = 4;
        MsgType2 msg2 = 5;
        MsgType3 msg3 = 6;
    }
}

Nanopb 以C联合体的形式生成 payload,并增加了额外字段“which_payload”:
typedef struct _MyMessage {
  uint32_t uid;
  uint32_t pid;
  uint32_t utime;
  pb_size_t which_payload;
  union {
      MsgType1 msg1;
      MsgType2 msg2;
      MsgType3 msg3;
  } payload;
} MyMessage;

"which_payload"表示哪个字段被实际设置。用户需要使用正确的字段标签手动设置字段:

MyMessage msg = MyMessage_init_zero;
msg.payload.msg2.value = true;
msg.which_payload = MyMessage_msg2_tag;

不论是 “which_payload”字段 还是在“payload” 中未使用的字段都不会在生成编码信息(encoded message)中消耗任何空间。

当在oneof 中包含一个pb_callback_t字段是,回调值不能在编码前被设置。 这是因为 在C 联合体中 不同的字段分享共同的存储空间。相反,可以使用函数名称绑定回调或单独的消息级别回调。

Extension fields(扩展字段)

Default values(默认值)

Protobuf 有两种语法变体, proto2 和 proto3。在proto2有用户可定义的默认值,可以在.proto文件中给出:

message MyMessage {
    optional bytes foo = 1 [default = "ABC\x01\x02\x03"];
    optional string bar = 2 [default = "åäö"];
}

Nanopb将为默认值生成静态初始化和运行时初始化。在myprotob .pb.h中有一个#define MyMessage_init_default{…}可以用来将整个消息初始化为默认值:

MyMessage msg = MyMessage_init_default;

除此之外,pb_decode()将在运行时将消息字段初始化为默认值。如果不希望这样做,可以使用pb_decode_ex()。

Message framing

Return values and error handling

static assertions

参考文献

《protobuf的Required,Optional,Repeated限定修饰符》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1501920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【编程实践】matlab中的转义字符

简记 这个报错是因为在sprintf函数中使用了无效的转义字符\U。在MATLAB中,转义字符\U是无效的,因此会导致警告。 检查sprintf函数中的格式化字符串是否包含了无效的转义字符。确保只使用MATLAB支持的转义字符。 如果想要输出一个反斜杠字符\&#xff0c…

Graphpad Prism10.2.1(395) 安装教程 (含Win/Mac版)

GraphPad Prism GraphPad Prism是一款非常专业强大的科研医学生物数据处理绘图软件,它可以将科学图形、综合曲线拟合(非线性回归)、可理解的统计数据、数据组织结合在一起,除了最基本的数据统计分析外,还能自动生成统…

ES分布式搜索-IK分词器

ES分词器-IK 1、为什么使用分词器? es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。 我们在kibana的DevTools中测试: GET /_analyze {"analyzer": "…

java数据结构与算法刷题-----LeetCode208. 实现 Trie (前缀树)

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路 就是一种数据结构,一般自动补完&#xff0c…

window vscode安装node.js

window vscode安装node.js 官网下好vscode 和nodejs 选.msi的安装 点这个安装 下载完 继续安装 完毕后倒杯水喝个茶等2分钟 重启VScode 或者在cmd 运行 npm -v node -v 显示版本号则成功

Swift SwiftUI 学习笔记 2024

Swift SwiftUI 学习笔记 2024 一、资源 视频资源 StanfordUnivercity 公开课 2023: https://cs193p.sites.stanford.edu/2023 教程 Swift 初识:基础语法:https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/…

CACLP预告 | 飞凌嵌入式与您相约山城重庆

第二十一届中国国际检验医学暨输血仪器试剂博览会(CACLP)将于2024年3月16日-18日在重庆国际博览中心举行。本次会议将探讨科技创新趋势,展示最新成果,发现和挖掘颠覆性技术和创新产品,引领实验医学体外诊断科技创新和未…

【HarmonyOS】ArkTS-对象

目录 对象对象的定义对象的使用实例 对象 作用:用于描述一个物体的特征和行为。 对象:是一个可以存储多个数据的容器。 对象的定义 let 对象名称: 对象结构类型 值通过interface 接口约定 对象结构类型 interface 接口名 { 属性1: 类型1 属性2: 类型2…

数字音频工作站(DAW)fl studio 21 for mac 21.2.3.3586中文版图文安装教程

随着音乐制作行业的不断发展,越来越多的音乐人和制作人开始使用数字音频工作站(DAW)来创作和制作音乐。其中FL Studio 21是一个备受欢迎的选择,因为它提供了强大的音乐制作工具和易于使用的界面。 然而,一直以来&…

在IDEA使用HBase Java API连接

一、下载安装Maven并加载到IDEA中 官网地址:Maven – Download Apache Maven 将对应版本的压缩包下载到本地,并新建一个文件夹Localwarehouse,用来保存下载的依赖文件 配置maven的系统环境配置,将maven安装的bin目录地址写入path环境变量: …

面向对象中接口(亦称: 协议、protocol、 “鸭子类型”)与抽象类

接口与类相比 由编译器强制的一个模块间协作的合约(Contract): 接口是一个由编译器强制的模块间协作的合约。它定义了一组方法的契约,所有实现该接口的类都必须提供这些方法的具体实现。这种强制性保证了不同模块之间的协作方式的一致性和可靠性。举例来…

荔枝派zero驱动开发06:GPIO操作(platform框架)

参考: 正点原子Linux第五十四章 platform设备驱动实验 一张图掌握 Linux platform 平台设备驱动框架 上一篇:荔枝派zero驱动开发05:GPIO操作(使用GPIO子系统) 下一篇:更新中… 概述 platform是一种分层思…

在Leaflet中使用Turf.js生成范围多边形的两种实现方式

目录 前言 一、场景需求 1、Leaflet.js的不足 2、Turf.js 二、原始数据展示 1、点位数据展示 2、定义样式 3、定位数据初始化 三、Turfjs中bbox生成 1、官网讲解 2、轨迹bbox生成 四、Turfjs生成外包多边形 1、官网例子 2、凸多边形生成 总结 前言 在一些共享出…

比特币普通地址、隔离见证(兼容)、隔离见证(原生)、Taproot 地址傻傻分不清楚

我们在使用比特币钱包的时候,可以看到各种地址类型:普通地址、隔离见证(兼容)、隔离见证(原生)、Taproot 地址。 看得我们一脸懵逼,为什么会有这么多种类型的地址? 它们之间都有什么…

17、电源管理入门之Power supply子系统

目录 1. Power supply框架都做些什么 2. 相关数据结构和接口 2.1 数据结构 2.2 接口 3. 充电驱动 3.1 Charger Manager 3.2 Fuel Gauge 3.3 Charger IC 4. 怎样基于power supply class编写PSY driver 参考资料: 对于便携设备来说,电源管理更加的重要,因为电池电量…

SaulLM-7B: A pioneering Large Language Model for Law

SaulLM-7B: A pioneering Large Language Model for Law 相关链接:arxiv 关键字:Large Language Model、Legal Domain、SaulLM-7B、Instructional Fine-tuning、Legal Corpora 摘要 本文中,我们介绍了SaulLM-7B,这是为法律领域量…

web组态

演示地址 :by组态[web组态插件] 这是一款可以嵌入到任何项目组态插件,功能全面,可根据自己的项目需要进行二次开发,能大大的节省在组态上的开发时间,代码简单易懂。 一、数据流向图及嵌入原理 数据流向 嵌入原理 …

[N1CTF 2018]eating_cms 不会编程的崽

题倒是不难,但是实在是恶心到了。 上来就是登录框,页面源代码也没什么特别的。寻思抓包看一下,数据包直接返回了sql查询语句。到以为是sql注入的题目,直到我看到了单引号被转义。。。挺抽象,似乎sql语句过滤很严格。又…

读算法的陷阱:超级平台、算法垄断与场景欺骗笔记04_共谋(上)

1. 共谋 1.1. 共谋总比相互竞争要容易得多 1.1.1. 通过共同抬价或稳定价格,企业通常可以赚取更多利润 1.1.2. 依靠人为切割市场,卡特尔组织成员得以在各自的势力范畴内实现垄断 1.2. 一直以来,人类都是价格操纵行为背后的行动者 1.2.1. …

解决方案|珈和科技推出农业特色产业数字化服务平台

今年中央一号文件提出,鼓励各地因地制宜大力发展特色产业,支持打造乡土特色品牌。 然而,农业特色产业的生产、加工和销售仍然面临诸多挑战。产品优质不能优价,优质不能优用的现象屡见不鲜,产业化程度低、生产附加值不…