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

通信协议理论

  • 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.rhkb.cn/news/272986.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】函数模板和类模板

目录 1.泛型编程 2.函数模板 2.1函数模板的定义格式 2.2函数模板的实例化 2.3函数模板参数的匹配原则 3.类模板 3.1类模板的定义格式 3.2类模板的实例化 3.3模板的分离编译 1.泛型编程 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段…

分割模型TransNetR的pytorch代码学习笔记

这个模型在U-net的基础上融合了Transformer模块和残差网络的原理。 论文地址:https://arxiv.org/pdf/2303.07428.pdf 具体的网络结构如下: 网络的原理还是比较简单的, 编码分支用的是预训练的resnet模块,解码分支则重新设计了。…

抖音素材网站去哪下载?给你推荐六个抖音自媒体网站

各位抖音视频创作达人们,是否在苦苦寻觅那些能够点燃观众热情,让视频内容跃然屏上的素材宝库呢?此刻,你们的寻觅之旅将迎来终点!我将向你们隆重推荐10个精心挑选的视频素材库,它们定能让你们的抖音视频如同…

【微服务】SpringBoot整合Resilience4j使用详解

目录 一、前言 二、熔断器出现背景 2.1 几个核心概念 2.1.1 熔断 2.1.2 限流 2.1.3 降级 2.2 为什么会出现熔断器 2.3 断路器介绍 2.3.1 断路器原理 三、Resilience4j介绍 3.1 Resilience4j概述 3.1.1 Resilience4j是什么 3.1.2 Resilience4j功能特性 3.2 Resilie…

微服务自动化管理初步认识与使用

目录 一、ETCD 1.1、ETCD简介 对于实施工程师: 1.2、特点 1.3. 使用场景 1.4、 关键字 1.5 工作原理 二、ETCD的安装 2.1、下载路径 2.2、介绍 2.3、具体操作 安装服务端 安装etcd客户端 测试 三、ETCD使用 3.1、前奏具体操作 3.2、 常用操作 一、ET…

利用GPT开发应用001:GPT基础知识及LLM发展

文章目录 一、惊艳的GPT二、大语言模型LLMs三、自然语言处理NLP四、大语言模型LLM发展 一、惊艳的GPT 想象一下,您可以与计算机的交流速度与与朋友交流一样快。那会是什么样子?您可以创建哪些应用程序?这正是OpenAI正在助力构建的世界&#x…

ELFK 分布式日志收集系统

ELFK的组成: Elasticsearch: 它是一个分布式的搜索和分析引擎,它可以用来存储和索引大量的日志数据,并提供强大的搜索和分析功能。 (java语言开发,)logstash: 是一个用于日志收集,处理和传输的…

Linux系统下使用C++推流多路视频流

先看拉取的视频流效果: 代码如下: 一开始打算使用python写多路视频推流,但在ubuntu系统上搞了好久就是搞不定openh264导致的错误,然后改用c了,代码如下,我这里推了两路视频流,一路是网络摄像头&…

2024护网面试题精选(二)完

0x02. 内网渗透篇 00- 内网渗透的流程 拿到跳板后,先探测一波内网存活主机,用net user /domian命令查看跳板机是否在域 内,探测存活主机、提权、提取hash、进行横向移动,定位dc位置,查看是否有能直接提权域 管的漏洞…

pytorch什么是梯度

目录 1.导数、偏微分、梯度1.1 导数1.2 偏微分1.3 梯度 2. 通过梯度求极小值3. learning rate3. 局部最小值4. Saddle point鞍点 1.导数、偏微分、梯度 1.1 导数 对于yx 2 2 2 的导数,描述了y随x值变化的一个变化趋势,导数是个标量反应的是变化的程度&…

【HTML】HTML基础7.1(无序列表)

目录 标签 属性 效果 注意 标签 <ul> <li>列表里要装的东西</li> <li>列表里要装的东西</li> <li>列表里要装的东西</li> </ul> 属性 type&#xff1a; circle空心圆disc实心圆square方框 效果 circle空心圆效果…

vi/vim编辑器

vi/vim编辑器 vi的特点与运用场景vi的使用简易执行一个案例按键说明第一部分&#xff1a;命令模式的按键说明(光标移动、复制粘贴、查找替换)第二部分&#xff1a;命令模式切换到输入模式的可以按键第三部分&#xff1a;命令模式切换到底线命令模式的可用按键 命令行模式的保存…

【fastllm】学习框架,本地运行,速度还可以,可以成功运行chatglm2模型

1&#xff0c;关于 fastllm 项目 https://www.bilibili.com/video/BV1fx421k7Mz/?vd_source4b290247452adda4e56d84b659b0c8a2 【fastllm】学习框架&#xff0c;本地运行&#xff0c;速度还可以&#xff0c;可以成功运行chatglm2模型 https://github.com/ztxz16/fastllm &am…

ai学习前瞻-python环境搭建

python环境搭建 Python环境搭建1. python的安装环境2. MiniConda安装3. pycharm安装4. Jupyter 工具安装5. conda搭建虚拟环境6. 安装python模块pip安装conda安装 7. 关联虚拟环境运行项目 Python环境搭建 1. python的安装环境 ​ python环境安装有4中方式。 从上图可以了解…

YOLO语义分割标注文件txt还原到图像中

最近做图像分割任务过程中&#xff0c;使用labelme对图像进行标注&#xff0c;得到的数据文件是json&#xff0c;转换为YOLO训练所需的txt格式后&#xff0c;想对标注文件进行检验&#xff0c;即将txt标注文件还原到原图像中&#xff0c;下面是代码&#xff1a; import cv2 im…

C++指针(五)完结篇

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09;、C指针&#xff08;四&#xff09;万字图文详解&#xff01; 本篇博客是介…

交易平台开发:构建安全/高效/用户友好的在线交易生态圈

在数字化浪潮的推动下&#xff0c;农产品现货大宗商品撮合交易平台已成为连接全球买家与卖家的核心枢纽。随着电子商务的飞速发展&#xff0c;一个安全、高效、用户友好的交易平台对于促进交易、提升用户体验和增加用户黏性至关重要。本文将深入探讨交易平台开发的关键要素&…

git学习(创建项目提交代码)

操作步骤如下 git init //初始化git remote add origin https://gitee.com/aydvvs.git //建立连接git remote -v //查看git add . //添加到暂存区git push 返送到暂存区git status // 查看提交代码git commit -m初次提交git push -u origin "master"//提交远程分支 …

Pytorch学习 day09(简单神经网络模型的搭建)

简单神经网络模型的搭建 针对CIFAR 10数据集的神经网络模型结构如下图&#xff1a; 由于上图的结构没有给出具体的padding、stride的值&#xff0c;所以我们需要根据以下公式&#xff0c;手动推算&#xff1a; 注意&#xff1a;当stride太大时&#xff0c;padding也会变得很大…

视频推拉流EasyDSS平台直播通道重连无法转推的原因排查与解决

视频推拉流EasyDSS视频直播点播平台&#xff0c;集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务。 用户使用EasyDSS平台对直播通道进行转推&#xff0c;发现只要关闭…