ProtoBuffer-nanopb介绍
一、需求
- 了解.proto文件的配置语法规则
- 目前平台上关于protocol buffer的使用例子较少
二、环境
- 版本:Android 12
- 平台:展锐 SPRD8541E
三、相关概念
3.1 protocol buffer介绍
protocol buffer是一种google开发的,语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储等。google在2008年7月7号将其作为开源项目对外公布。值得注意的是,proto buffer是以二进制来存储数据的。相对于JSON和XML具有以下优点:
- 简洁
- 体积小:消息大小只需要XML的1/10 ~ 1/3;
- 速度快:解析速度比XML快20 ~ 100倍;
- json\xml都是基于文本格式,protobuf是二进制格式;
protobuf是PB协议使用较广的一个框架,支持C++,JAVA,Python,Ruby,Go,PHP等多种语言。
3.2 nanopb(支持C语言)
protobuf支持多种语言,但是却不支持纯C语言,而且protobuf的使用笨重,在一些内存紧张的嵌入式设备上不能使用,nanopb是谷歌协议缓冲数据格式的一个纯C实现。它的目标是32位微控制器,但也适用于其他嵌入式系统的严格(< 10kB ROM,< 1kB RAM)内存限制。
3.3 proto文件
.proto文件是Google Protocol Buffers的核心组成部分,定义了数据的结构和格式。它支持多种基本数据类型和自定义数据类型的定义,可以嵌套定义。每个字段有类型、名称和字段序号三个特性,字段规则定义了字段是单值、重复值还是可选值。在.proto文件定义完成后,需要使用protobuf编译器将其编译成对应语言的代码,然后在代码中使用这些生成的代码文件定义数据类型、序列化和反序列化数据。
四、proto基本语法
4.1 proto文件的定义
如下为一个*.proto文件的基本定义:

4.2 字段规则
字段 | 介绍 |
---|---|
required | 格式良好的 message 必须包含该字段一次(在proto3中已经为兼容性彻底抛弃 required。) |
optional | 格式良好的 message 可以包含该字段零次或一次(不超过一次)。 |
repeated | 该字段可以在格式良好的消息中重复任意多次(包括零)。其中重复值的顺序会被保留。 |
4.3 字段类型
proto类型 | 介绍 |
---|---|
double | 64位浮点数 |
float | 32位浮点数 |
int32 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint32。 |
int64 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint64。 |
uint32 | 使用可变长度编码。 |
uint64 | 使用可变长度编码。 |
sint32 | 使用可变长度编码。符号整型值。这些比常规int32s编码负数更高效。 |
sint64 | 使用可变长度编码。符号整型值。这些比常规int64s编码负数更高效。 |
fixed32 | 总是四字节。如果值通常大于228,则比uint 32更高效 |
fixed64 | 总是八字节。如果值通常大于256,则比uint64更高效 |
sfixed32 | 总是四字节。 |
sfixed64 | 总是八字节。 |
bool | 布尔类型 |
string | 字符串必须始终包含UTF - 8编码或7位ASCII文本 |
bytes | 可以包含任意字节序列 |
4.4 字段编号
message 定义中的每个字段都有唯一编号。这些数字以message二进制格式标识你的字段,并且一旦你的message被使用,这些编号就无法再更改。请注意,1到15范围内的字段编号需要一个字节进行编码,编码结果将同时包含编号和类型。16到2047范围内的字段编号占用两个字节。因此,你应该为非常频繁出现的message元素保留字段编号1到15。
4.5 proto语法
目前proto语法,可以分为proto2版本和proto3版本,proto3在proto2的基础上做了升级与改动,其区别如下:
https://blog.csdn.net/ymzhu385/article/details/122307593
Android12上发现采用proto2语法场景较多,本文的话我也将继续沿用proto2语法进行分析。
4.6 进阶语法
4.6.1 message嵌套
messsage除了能放简单数据类型外,还能存放另外的message类型:
message CarMessage {
required string name = 1;
required int32 price = 2;
}
message UserMessage {
enum Sex {
WOMAN = 0;
MAN = 1;
}
required string username = 1;
optional int32 age = 2;
required Sex sex = 3;
repeated CarMessage cars = 4;
}
4.6.2 enum关键字
在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表,我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作:
message UserMessage {
enum Sex {
WOMAN = 0;
MAN = 1;
}
required string username = 1;
optional int32 age = 2;
required Sex sex = 3;
}
4.6.3 oneof关键字
如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:
message OneOfMessage {
oneof IdData {
int32 id = 1;
int32 passport = 2;
};
}
五、nanopb分析
5.1 nanopb版本下载
nanopb各个版本: https://jpa.kapsi.fi/nanopb/download/
5.2 nanopb相关Api
Protocol指导文档: https://jpa.kapsi.fi/nanopb/docs/index.html
5.2.1 编码相关API
API | 说明 |
---|---|
pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); | 构造用于写入内存缓冲区的输出流。 |
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); | 将结构的内容编码为协议缓冲区消息,并将其写入输出流 |
bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags); | 使用由标志设置的扩展行为对消息进行编码: |
bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct); | 计算已编码消息的长度。 |
bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); | 以Protocol Buffers二进制格式开始一个字段:编码字段号和数据的类型。 |
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field); | 与pb_encode_tag相同,只是从pb_field_iter_t结构体获取参数。 |
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); | 以可变格式编码有符号或无符号整数。适用于bool、enum、int32、int64、uint32和uint64类型的字段: |
bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); | 将字符串的长度写入变量,然后写入字符串的内容。适用于bytes和string类型的字段: |
bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); | 对子消息字段进行编码,包括它的大小报头。适用于任何消息类型的字段。 |
... | ... |
5.2.2 解码相关API
API | 说明 |
---|---|
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize); | 用于创建从内存缓冲区读取数据的输入流的辅助函数。 |
bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct); | 读取和解码结构的所有字段。读取输入流直到EOF。 |
bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags); | 与pb_decode相同,但允许扩展选项。 |
bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); | 读取和解码一个变量编码的整数。 |
bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); | 类似于pb_decode_varint,不同之处在于它对值执行zigzag解码。这对应于协议缓冲区sint32和sint64数据类型。 |
... | ... |
5.3 总体结构
5.3.1 结构图

Step 1. 第一阶段: MyMessage.proto文件经过编译,会生成MyMessage.pb.c和MyMessage.pb.h临时文件;
Step 2. 第二阶段: 通过Nanopb提供的相关库文件,以及第一个阶段生成的MyMessage.pb.c和MyMessage.pb.h临时文件,可以编写我们的应用程序User application;
Step 3. 第三阶段: 我们的业务数据Data structures和Protocol Buffers messages的数据,通过Nanopb library提供的编解码方法pb_encode()和pb_decode(), 实现序列化和反序列化的操作。
5.3.2 相关文件
一个标准的nanopb项目,会包含如下文件:
类型 | 文件 | 备注 |
Nanopb runtime library | pb.h | 必须有 |
pb_common.h pb_common.c |
必须有 | |
pb_decode.h pb_decode.c |
编码相关 | |
pb_encode.h pb_encode.c |
解码相关 | |
Protocol description | MyMessage.proto | 必须有 |
MyMessage.pb.c MyMessage.pb.h |
编译后自动生成 |
六、nanopb应用
6.1 基于Android平台nanopb应用
基于Android平台,创建一个c程序,用于测试nanopb的使用规则。文末附上相关demo仓库地址。
6.1.2 定义.proto文件
定义.proto文件,定义数据结构与格式。
syntax = "proto2";
...
message CarMessage {
required string name = 1;
required int32 price = 2;
}
message PetMessage {
required string name = 1;
}
message UserMessage {
enum Sex {
WOMAN = 0;
MAN = 1;
}
required string username = 1;
optional int32 age = 2;
required Sex sex = 3;
repeated CarMessage cars = 4;
optional PetMessage pets = 5;
}
6.1.3 生成动态库
将proto相关文件打包成libprototest动态库,以便于需要使用的模块去引用。(之前想将proto直接编译到对应的测试程序,但是export_proto_headers等相关编译标识未找到,导致无法正常编译,故将其先编译成一个动态库)
cc_library {
name: "libprototest",
srcs: [
"proto/simple.proto",
],
...
proto: {
type: "nanopb-c-enable_malloc-32bit",
export_proto_headers: true,
},
vendor: true,
}
6.1.4 临时文件
libprototest模块编译后,会根据.proto文件的数据结构,生成一个临时文件simple.pb.c和simple.pb.h(临时文件路径:out\soong\ .intermediates\vendor\sprd\proprietories-source\rild\protocol\libprototest\android_vendor.31_arm_armv8-a_cortex-a53_static\gen\proto\vendor\sprd\proprietories-source\rild\protocol\proto\),相关文件也有备份到Demo代码仓库。
@simple.pb.h
...
typedef enum _UserMessage_Sex {
UserMessage_Sex_WOMAN = 0,
UserMessage_Sex_MAN = 1
} UserMessage_Sex;
#define _UserMessage_Sex_MIN UserMessage_Sex_WOMAN
#define _UserMessage_Sex_MAX UserMessage_Sex_MAN
#define _UserMessage_Sex_ARRAYSIZE ((UserMessage_Sex)(UserMessage_Sex_MAN+1))
/* Struct definitions */
typedef struct _CarMessage {
char name[100];
int32_t price;
/* @@protoc_insertion_point(struct:CarMessage) */
} CarMessage;
typedef struct _PetMessage {
char name[100];
/* @@protoc_insertion_point(struct:PetMessage) */
} PetMessage;
...
typedef struct _UserMessage {
char username[200];
bool has_age;
int32_t age;
UserMessage_Sex sex;
pb_callback_t cars;
bool has_pets;
PetMessage pets;
/* @@protoc_insertion_point(struct:UserMessage) */
} UserMessage;
...
/* Struct field encoding specification for nanopb */
extern const pb_field_t SimpleMessage_fields[5];
extern const pb_field_t CarMessage_fields[3];
extern const pb_field_t PetMessage_fields[2];
extern const pb_field_t UserMessage_fields[6];
...
6.1.5 测试用例
message嵌套使用测试,其相关流程如下:
void test_nest(void){
RLOGD("lzq add for test_nest START >>>>>>>>\n");
/*************************写入数据***************************/
//Step 1.创建写入数据
UserInfo userinfo;
strcpy(userinfo.username,"linzhiqin");
userinfo.age = 18;
userinfo.has_age = true;
strcpy(userinfo.cars[0].name,"BMW");
userinfo.cars[0].price = 380000;
strcpy(userinfo.cars[1].name,"Benz");
userinfo.cars[1].price = 450000;
strcpy(userinfo.cars[2].name,"Audi");
userinfo.cars[2].price = 280000;
userinfo.car_num = 3;
//Step 2.写入数据赋值给编码相关对象
uint8_t encodeBuffer[1024] = {0};
int encodeBufferLen = 0;
UserMessage pack_user = UserMessage_init_zero;
strcpy(pack_user.username,userinfo.username);
pack_user.age = userinfo.age;
pack_user.has_age = userinfo.has_age;
pack_user.sex = UserMessage_Sex_MAN;
//strcpy(pack_user.pets.name,"Ragdoll");
pack_user.cars.funcs.encode = carEncode;//编码回调函数
pack_user.cars.arg = &userinfo;
//Step 3.数据编码
pb_ostream_t o_stream = {0};
o_stream = pb_ostream_from_buffer(encodeBuffer, 1024);
if(pb_encode(&o_stream, UserMessage_fields, &pack_user) == false){
printf("encode failed\n");
return;
}
encodeBufferLen = o_stream.bytes_written;
/**************************读取数据**************************/
//Step 4.创建解码相关对象
UserInfo userinfo2;
memset(&userinfo2,0,sizeof(UserInfo));
UserMessage unpack_user = UserMessage_init_zero;
unpack_user.cars.funcs.decode = carDecode;//解码回调
unpack_user.cars.arg = &userinfo2;
//Step 5.数据解码&打印
pb_istream_t i_stream = {0};
i_stream = pb_istream_from_buffer(encodeBuffer, encodeBufferLen);
if(pb_decode(&i_stream, UserMessage_fields, &unpack_user) == true){
strcpy(userinfo2.username,unpack_user.username);
//strcpy(userinfo2.pets.name,unpack_user.pets.name);
if(unpack_user.has_age) {
userinfo2.age = unpack_user.age;
}
printf("\n");
printf("UserInfo.pets.name = %s\n", userinfo2.pets.name);
printf("UserInfo.username = %s\n", userinfo2.username);
printf("UserInfo.age = %d\n", userinfo2.age);
printf("UserInfo.sex = %d\n", unpack_user.sex);
for(int i=0;i<userinfo2.car_num;i++)
printf("CarInfo name:%s score:%d\n",userinfo2.cars[i].name,userinfo2.cars[i].price);
}
RLOGD("lzq add for test_nest END >>>>>>>>\n");
}
打印结果:

6.2 基于Windows平台的nanopb应用
6.2.1 环境配置
(1)Windows版本: Windows 10 专业版
(2)gcc版本: gcc version 8.1.0(https://sourceforge.net/projects/mingw-w64/)
(3)C程序IDE: January 2024 (version 1.86)(插件: C/C++、Code Runner)
(4)nanopb版本: nanopb-0.4.8-windows-x86.zip(https://jpa.kapsi.fi/nanopb/download/)
6.2.2 临时文件生成
Step 1. 解压nanopb文件夹 解压nanopb-0.4.8-windows-x86.zip文件,其相关内容如下:

Step 2. 新增.proto文件 在nanopb-0.4.8-windows-x86.zip文件文件夹下,新增simple.proto文件,相关内容如下:
syntax = "proto2";
message SimpleMessage {
enum Sex {
WOMAN = 0;
MAN = 1;
}
required int32 code = 1;
optional string msg = 2;
repeated int32 data = 3;
required Sex sex = 4;
}
Step 3. 设置系统环境变量 将nanopb-0.4.8-windows-x86\generator-bin设置为系统全局变量,方便引用;
Step 4. 生成临时文件 进入当前文件夹下,通过如下指令生成simple.pb.c和simple.pb.h:
protoc --nanopb_out=. simple.proto
Step 5. nanopb程序关键文件 nanopb 将需要保存的参数写在.proto文件里面,然后生成对应的.pb.h和.pb.c 文件。nanopb程序需要将如下几个关键文件拷贝到对应工程:


6.2.3 测试用例
#include <stdio.h>
#include <stdlib.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "simple.pb.h"
/******************** test_simple *************************/
/*
* 简单测试
*/
void test_simple(){
SimpleMessage req;
memset(&req, 0, sizeof(SimpleMessage));
req.code = 1109;
req.sex = SimpleMessage_Sex_MAN;
size_t encodedSize = 0;
if (!pb_get_encoded_size(&encodedSize, SimpleMessage_fields, &req)) {
exit(0);
}
uint8_t *buffer = (uint8_t *)calloc(1, encodedSize);
if (buffer == NULL) {
exit(0);
}
pb_ostream_t stream = pb_ostream_from_buffer(buffer, encodedSize);
if (!pb_encode(&stream, SimpleMessage_fields, &req)) {
exit(0);
}
/**************************读取数据**************************/
SimpleMessage message = SimpleMessage_init_zero;
pb_istream_t stream2 = pb_istream_from_buffer(buffer, encodedSize);
if (!pb_decode(&stream2, SimpleMessage_fields, &message))
{
exit(0);
}
printf("code = %d | sex = %d \n",message.code,message.sex);
}
int main(int argc, char **argv) {
//简单测试
test_simple();
...
}
打印结果:

七、遗留问题
- const pb_field_t UserMessage_fields[6] 数组的数据打印异常(nanopb例子using_union_messages)
- message多级嵌套除了使用repeated关键字,采用回调函数处理外,不知道是否有其他处理方式?
八、代码仓库
Demo地址: https://gitee.com/linzhiqin/protocol
九、参考资料
https://zhuanlan.zhihu.com/p/494788890#nanopb的使用
https://www.jianshu.com/p/6f68fb2c7d19
https://blog.csdn.net/hsy12342611/article/details/129517588
https://www.jianshu.com/p/bdd94a32fbd1
https://blog.csdn.net/Gefangenes/article/details/131319610
参考例子:
https://blog.csdn.net/du2005023029/article/details/130861308
VsCode调试C程序
https://blog.csdn.net/ABYSS_CL/article/details/119961975
nanopb在window平台的使用
https://www.cnblogs.com/ymchen/p/16861605.html
ProtoBuffer-nanopb介绍的更多相关文章
- Erlang使用ProtoBuffer
最近有工作需要打算为项目服务器做一个机器人,测试测试压力,根据自己的经验,使用Erlang来做是最合适不过的了,但是服务器使用的C++语言,使用了Google的ProtoBuffer作为协议进行数据交 ...
- 基于.NET CORE微服务框架 -surging 基于messagepack、protobuffer、json.net 性能对比
1.前言 surging内部使用的是高性能RPC远程服务调用,如果用json.net序列化肯定性能上达不到最优,所以后面扩展了protobuf,messagepack序列化组件,以支持RPC二进制传输 ...
- 基于.NET CORE微服务框架 -谈谈surging 的messagepack、protobuffer、json.net 序列化
1.前言 surging内部使用的是高性能RPC远程服务调用,如果用json.net序列化肯定性能上达不到最优,所以后面扩展了protobuf,messagepack序列化组件,以支持RPC二进制传输 ...
- Protobuffer简介c#
一.Protobuffer和json深度对比 JSON相信大家都知道是什么东西,如果不知道,那可就真的OUT了,GOOGLE一下去.这里就不介绍啥的了. Protobuffer大家估计就很少听说了,但 ...
- 从微信SDK看ProtoBuffer文件的生成
前言 Protocol Buffers (下面简称PB)是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式.它可用于通讯协议.数据存储等领域的语言无 ...
- Thrift RPC框架介绍
u 简介 Thrift是一种开源的跨语言的RPC服务框架.Thrift最初由facebook公司开发的,在2007年facebook将其提交apache基金会开源了.对于当时的facebook来说创造 ...
- 实战:一种在http请求中使用protobuffer+nginx+lua收集打点日志的方案
背景 app打点日志的上报和收集,是互联网公司的基本需求. 一.方案选择 1.1 protobuffer vs json 探究一种以最高效的方式上报和解析打点数据是一个系统性的问题,需要解决的子问题有 ...
- CSS3 background-image背景图片相关介绍
这里将会介绍如何通过background-image设置背景图片,以及背景图片的平铺.拉伸.偏移.设置大小等操作. 1. 背景图片样式分类 CSS中设置元素背景图片及其背景图片样式的属性主要以下几个: ...
- MySQL高级知识- MySQL的架构介绍
[TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...
- Windows Server 2012 NIC Teaming介绍及注意事项
Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...
随机推荐
- [转帖]linux--Segfault详解
linux--Segfault详解 1 简介 1.1 段错误的定义 1.2 痛点 2 知识点 2.1 报错内容 2.2 error number 3 排除步骤(借助汇编) 3.1 日志确定错误类型 3 ...
- 01显示转换隐私转换 有8个值转为false 显示转换Number的注意点
prompt()函数会弹出一个框,接受用户的输入.但是在实际的开发中.这样的操作是很少. 至少在我做开发的过程中没有使用过.我二没有看见人家在过开发的使用使用. console.log(Number( ...
- elementui表格内容超出显示省略号
有些时候表格的内容太长了: 但是elementui中的表格,会进行换行处理: 此时表格的高度就会发生变化 这样就不好看,此时就要进行省略号来出来这个问题: el-table是有这个控制属性的::sho ...
- 【云原生】为什么要虚拟化,为什么要容器,为什么要Docker,为什么要K8S?
前言 如标题中的问题所提到的虚拟化,容器,Docker和K8s那样,我们不妨这样问:这些技术到底适用于哪些场景,有没有别的技术可以替代?这些技术的优劣在哪里? 下面我将针对性地从以上几个问题的出发点, ...
- 史上最大电池!小米智能家庭屏Pro 8图赏
今天小米智能家庭屏 Pro 8正式开售,集智能家居中控,智能网关以及娱乐教育三大功能为一体,首发749元. 它是一款全新的智能生态产品中控屏,配备了7500mAh大容量电池以及通用性更好的USB Ty ...
- 1cm+1kg纤薄身材 带来14小时超长续航!华硕灵耀13 2023评测
一.前言:华硕推出1cm+1kg超轻薄笔记本 还有OLED好屏加持 随着处理器能效的不断进步,轻薄本已经不存在性能羸弱的问题了,也给了各大厂商极大的发挥空间,不过市面上的轻薄本厚度普遍在15~20mm ...
- CF351D Jeff and Removing Periods 题解
题目链接:CF 或者 洛谷 挺有意思的题,一开始看到了 \(start+k\times step\),以为是根号分治方向的题,结果发现这题还给了一个"重排"操作玩玩.所以这题其实算 ...
- 错误:基于tensorflow识别mnist数据集出现ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[10000,32,28,28] and type float on
错误:最近,在尝试运行我以前博客代码的时候出现了如下错误 2020-04-03 10:53:22.982491: W tensorflow/core/common_runtime/bfc_alloca ...
- 【译】介绍 MSTest Runner – CLI、Visual Studio 等
原文 | Amaury Levé, Marco Rossignoli, Jakub Jareš 翻译 | 郑子铭 我们很高兴推出 MSTest runner,这是一个用于 MSTest 测试的新型轻量 ...
- 《ASP.ENT Core 与 RESTful API 开发实战》-- 读书笔记(第1章)
第 1 章 REST 简介 1.1 API 与 REST API 是一个系统向外暴露或公开的一套接口,通过这些接口,外部应用程序能够访问该系统 REST 是一种基于资源的架构风格,任何能够命名的对象都 ...