Protubuf入门

news/2024/9/18 17:52:13/ 标签: 服务器, 运维

⼀、初识 ProtoBuf

1. 序列化概念

序列化和反序列化

序列化:把对象转换为字节序列的过程 称为对象的序列化。
反序列化:把字节序列恢复为对象的过程 称为对象的反序列化。

什么情况下需要序列化

存储数据:当你想把的内存中的对象状态保存到⼀个⽂件中或者存到数据库中时。
⽹络传输:⽹络直接传输数据,但是⽆法直接传输对象,所以要在传输前序列化,传输完成后反
序列化成对象。例如我们之前学习过 socket 编程中发送与接收数据。

如何实现序列化

xml、json、 protobuf

2. ProtoBuf 是什么

Protocol Buffers 是 Google 的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法,它可⽤ 于(数据)通信协议、数据存储等。
Protocol Buffers 类⽐于 XML,是⼀种灵活,⾼效,⾃动化机制的结构数据序列化⽅法,但是⽐
XML 更⼩、更快、更为简单。
你可以定义数据的结构,然后使⽤特殊⽣成的源代码轻松的在各种数据流中使⽤各种语⾔进⾏编写和 读取结构数据。你甚⾄可以更新数据结构,⽽不破坏由旧数据结构编译的已部署程序。
简单来讲, ProtoBuf(全称为 Protocol Buffer)是让结构数据序列化的⽅法,其具有以下特点:
语⾔⽆关、平台⽆关:即 ProtoBuf ⽀持 Java、C++、Python 等多种语⾔,⽀持多个平台。
⾼效:即⽐ XML 更⼩、更快、更为简单。
扩展性、兼容性好:你可以更新数据结构,⽽不影响和破坏原有的旧程序

3. ProtoBuf 的使⽤特点

1. 编写 .proto ⽂件,⽬的是为了定义结构对象(message)及属性内容。
2. 使⽤ protoc 编译器编译 .proto ⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
3. 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对 .proto ⽂件中定义的字段进⾏ 设置和获取,和对 message 对象进⾏序列化和反序列化。

ProtoBuf 是需要依赖通过编译⽣成的头⽂件和源⽂件来使⽤的。有了这种代码⽣成机制,
开发⼈员再也不⽤吭哧吭哧地编写那些协议解析的代码了(⼲这种活是典型的吃⼒不讨好)

4.ProtoBuf 安装

⼀、ProtoBuf 在 window 下的安装

1、下载 ProtoBuf 编译器

下载地址: https://github.com/protocolbuffers/protobuf/releases
下载之后将压缩包解压到本地⽬录下。解压后的⽂件内包含 bin、include⽂件,以及⼀个
readme.txt。

2、配置环境变量

把解压后⽂件中的bin⽬录配置到系统环境变量的Path中去

⼆、ProtoBuf 在 Linux 下的安装

下载 ProtoBuf 前⼀定要安装依赖库:autoconf automake libtool curl make g++ unzip

如未安装,安装命令如下:
Ubuntu ⽤⼾选择:
1 sudo apt-get install autoconf automake libtool curl make g++ unzip -y
CentOS ⽤⼾选择:
1 sudo yum install autoconf automake libtool curl make gcc-c++ unzip
在这⾥我们希望⽀持全部语⾔,所以选择 protobuf-all-21.11.zip,右键将下载链接复制出来。
下载完成后,解压zip包:
unzip protobuf-all-21.11.zip
解压完成后,会⽣成 protobuf-21.11 ⽂件,进⼊⽂件:
cd protobuf-21.11

2、安装 ProtoBuf

进⼊解压好的⽂件,执⾏以下命令
# 第⼀步执⾏ autogen.sh 但如果下载的是具体的某⼀⻔语⾔,不需要执⾏这⼀步。
./autogen.sh
# 第⼆步执⾏ configure ,有两种执⾏⽅式,任选其⼀即可,如下:
# 1 protobuf 默认安装在 /usr/local ⽬录, lib bin 都是分散的
./configure
# 2 修改安装⽬录,统⼀安装在 /usr/local/protobuf
./configure --prefix=/usr/local/protobuf
make // 执⾏ 15 分钟左右
make check // 执⾏ 15 分钟左右
sudo make install
3、检查是否安装成功
输⼊ protoc --version 查看版本,有显⽰说明安装成功。
1 hyb@139-159-150-152:~/install/protobuf-21.11$ protoc --version
2 libprotoc 3.21.11

3.快速上⼿

在快速上⼿中,会编写第⼀版本的通讯录 1.0。在通讯录 1.0 版本中,将实现:
对⼀个联系⼈的信息使⽤ PB 进⾏序列化,并将结果打印出来。
对序列化后的内容使⽤ PB 进⾏反序列,解析出联系⼈信息并打印出来。
联系⼈包含以下信息: 姓名、年龄。
通过通讯录 1.0,我们便能了解使⽤ ProtoBuf 初步要掌握的内容,以及体验到 ProtoBuf 的完整使⽤流 程。

步骤1:创建 .proto ⽂件

1.⽂件规范
1.  创建 .proto ⽂件时,⽂件命名应该使⽤全⼩写字⺟命名,多个字⺟之间⽤ _ 连接。 例如:
lower_snake_case.proto
2.  书写 .proto ⽂件代码时,应使⽤ 2 个空格的缩进。
我们为通讯录 1.0 新建⽂件: contacts.proto
2.添加注释
向⽂件添加注释,可使⽤ // 或者 /* ... */
3.指定 proto3 语法
Protocol Buffers 语⾔版本3,简称 proto3,是 .proto ⽂件最新的语法版本。proto3 简化了 Protocol Buffers 语⾔,既易于使⽤,⼜可以在更⼴泛的编程语⾔中使⽤。它允许你使⽤ Java,C++,Python 等多种语⾔⽣成 protocol buffer 代码。
在 .proto ⽂件中,要使⽤ syntax = "proto3"; 来指定⽂件语法为 proto3,并且必须写在除去
注释内容的第⼀⾏。 如果没有指定,编译器会使⽤proto2语法。
在通讯录 1.0 的 contacts.proto ⽂件中,可以为⽂件指定 proto3 语法
4.package 声明符
package 是⼀个可选的声明符,能表⽰ .proto ⽂件的命名空间,在项⽬中要有唯⼀性。它的作⽤是为 了避免我们定义的消息出现冲突。
在通讯录 1.0 的 contacts.proto ⽂件中,可以声明其命名空间,内容如下:
5.定义消息(message)
消息(message): 要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。
这⾥再提⼀下为什么要定义消息?
在⽹络传输中,我们需要为传输双⽅定制协议。定制协议说⽩了就是定义结构体或者结构化数据,
⽐如,tcp,udp 报⽂就是结构化的。 再⽐如将数据持久化存储到数据库时,会将⼀系列元数据统⼀⽤对象组织起来,再进⾏存储。
所以 ProtoBuf 就是以 message 的⽅式来⽀持我们定制协议字段,后期帮助我们形成类和⽅法来使
⽤。在通讯录 1.0 中我们就需要为 联系⼈ 定义⼀个 message。
定义消息字段
在 message 中我们可以定义其属性字段,字段定义格式为:字段类型 字段名 = 字段唯⼀编号
字段名称命名规范:全⼩写字⺟,多个字⺟之间⽤ _ 连接。
字段类型分为:标量数据类型 和 特殊类型(包括枚举、其他消息类型等)。
字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。
该表格展⽰了定义于消息体中的标量数据类型,以及编译 .proto ⽂件之后⾃动⽣成的类中与之对应的 字段类型。在这⾥展⽰了与 C++ 语⾔对应的类型。
在这⾥还要特别讲解⼀下字段唯⼀编号的范围:
1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可⽤。
19000 ~ 19999 不可⽤是因为:在 Protobuf 协议的实现中,对这些数进⾏了预留。如果⾮要在.proto ⽂件中使⽤这些预留标识号,例如将 name 字段的编号设置为19000,编译时就会报警:
值得⼀提的是,范围为 1 ~ 15 的字段编号需要⼀个字节进⾏编码, 16 ~ 2047 内的数字需要两个字节 进⾏编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以 1 ~ 15 要⽤来标记出现⾮常频 繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。

步骤2:编译 contacts.proto ⽂件,⽣成 C++ ⽂件

编译命令
编译命令⾏格式为:
编译 contacts.proto ⽂件后会⽣成什么
编译 contacts.proto ⽂件后,会⽣成所选择语⾔的代码,我们选择的是C++,所以编译后⽣成了两个 ⽂件: contacts.pb.h contacts.pb.cc
对于编译⽣成的 C++ 代码,包含了以下内容 :
对于每个 message ,都会⽣成⼀个对应的消息类。
在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的⽅法。
编辑器会针对于每个 .proto ⽂件⽣成 .h .cc ⽂件,分别⽤来存放类的声明与类的实现。
上述的例⼦中:
每个字段都有设置和获取的⽅法, getter 的名称与⼩写字段完全相同,setter ⽅法以 set_ 开头。
每个字段都有⼀个 clear_ ⽅法,可以将字段重新设置回 empty 状态。
contacts.pb.cc 中的代码就是对类声明⽅法的⼀些实现,在这⾥就不展开了。
到这⾥大家可能就有疑惑了,那之前提到的序列化和反序列化⽅法在哪⾥呢?在消息类的⽗类
MessageLite 中,提供了读写消息实例的⽅法,包括序列化⽅法和反序列化⽅法。
注意:
序列化的结果为⼆进制字节序列,⽽⾮⽂本格式。
以上三种序列化的⽅法没有本质上的区别,只是序列化后输出的格式不同,可以供不同的应⽤场景 使⽤。
序列化的 API 函数均为const成员函数,因为序列化不会改变类对象的内容, ⽽是将序列化的结果 保存到函数⼊参指定的地址中。
步骤3:序列化与反序列化的使⽤
创建⼀个测试⽂件 main.cc,⽅法中我们实现:
对⼀个联系⼈的信息使⽤ PB 进⾏序列化,并将结果打印出来。
对序列化后的内容使⽤ PB 进⾏反序列,解析出联系⼈信息并打印出来
代码书写完成后,编译 main.cc,⽣成可执⾏程序 TestProtoBuf
1 g++ main.cc contacts.pb.cc -o TestProtoBuf -std=c++11 -lprotobuf
-lprotobuf:必加,不然会有链接错误。
-std=c++11:必加,使⽤C++11语法
执⾏ test  ,可以看⻅ people 经过序列化和反序列化后的结果:
由于 ProtoBuf 是把联系⼈对象序列化成了⼆进制序列,这⾥⽤ string 来作为接收⼆进制序列的容器。 所以在终端打印的时候会有换⾏等⼀些乱码显⽰。
所以相对于 xml 和 JSON 来说,因为被编码成⼆进制,破解成本增⼤,ProtoBuf 编码是相对安全的。

⼩结 ProtoBuf 使⽤流程

1. 编写 .proto ⽂件,⽬的是为了定义结构对象(message)及属性内容。
2. 使⽤ protoc 编译器编译 .proto ⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
3. 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对 .proto ⽂件中定义的字段进⾏ 设置和获取,和对 message 对象进⾏序列化和反序列化。
总的来说:ProtoBuf 是需要依赖通过编译⽣成的头⽂件和源⽂件来使⽤的。有了这种代码⽣成机制, 开发⼈员再也不⽤吭哧吭哧地编写那些协议解析的代码了(⼲这种活是典型的吃⼒不讨好)。

五、proto 3 语法详解

在语法详解部分,依旧使⽤ 项⽬推进 的⽅式完成讲解。这个部分会对通讯录进⾏多次升级,使⽤ 2.x 表⽰升级的版本,最终将会升级如下内容:
不再打印联系⼈的序列化结果,⽽是将通讯录序列化后并写⼊⽂件中。
从⽂件中将通讯录解析出来,并进⾏打印。
新增联系⼈属性,共包括:姓名、年龄、电话信息、地址、其他联系⽅式、备注。

1. 字段规则

消息的字段可以⽤下⾯⼏种规则来修饰:
singular :消息中可以包含该字段零次或⼀次(不超过⼀次)。 proto3 语法中,字段默认使⽤该
规则。
repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理
解为定义了⼀个数组。
更新 contacts.proto , PeopleInfo 消息中新增 phone_numbers 字段,表⽰⼀个联系⼈有多个
号码,可将其设置为 repeated,写法如下:

2. 消息类型的定义与使⽤

2.1 定义
在单个 .proto ⽂件中可以定义多个消息体,且⽀持定义嵌套类型的消息(任意多层)。每个消息体中 的字段编号可以重复。
更新 contacts.proto,我们可以将 phone_number 提取出来,单独成为⼀个消息:
//嵌套写法
syntax = "proto3";
package contacts;
message PeopleInfo {string name = 1; int32 age = 2; message Phone {string number = 1;}
}
// -------------------------- ⾮嵌套写法 -------------------------
syntax = "proto3";
package contacts;
message Phone {string number = 1;
}
message PeopleInfo {string name = 1; int32 age = 2; 
}

2.2 使⽤

消息类型可作为字段类型使⽤
contacts.proto
syntax = "proto3";
package contacts;
// 联系⼈
message PeopleInfo {string name = 1; int32 age = 2; message Phone {string number = 1; }repeated Phone phone = 3; 
}
可导⼊其他 .proto ⽂件的消息并使⽤

syntax = "proto3";
package contacts;
import "phone.proto"; // 使⽤ import 将 phone.proto ⽂件导⼊进来 !!!
message PeopleInfo {string name = 1; int32 age = 2; // 引⼊的⽂件声明了package,使⽤消息时,需要⽤ ‘命名空间.消息类型’ 格式 repeated phone.Phone phone = 3; 
}
注:在 proto3 ⽂件中可以导⼊ proto2 消息类型并使⽤它们,反之亦然

2.3.1 通讯录 2.0 的写⼊实现

write.cc (通讯录 2.0)
#include<iostream>
#include<fstream>
#include"contact.pb.h"
using namespace std;void AddPeopleInfo(contacts::PeopleInfo* info)
{info->set_name("lisi");info->set_age(18);phone::Phone* newphone=info->add_phone();newphone->set_numbers("1234567890");
}int main()
{contacts::Contacts contacter;fstream input("contacts.bin",ios::in | ios::binary);if(!input){cerr<<"Creat A New File"<<endl;}else if(!contacter.ParseFromIstream(&input)) {cerr<<"Parse Error"<<endl;input.close();return -1;}AddPeopleInfo(contacter.add_people());fstream output("contacts.bin",ios::out|ios::binary|ios::trunc);if(!contacter.SerializeToOstream(&output)){cerr<<"write error"<<endl;input.close();output.close();return -1;}cout<<"write success"<<endl;return 0;
}

2.3.2 通讯录 2.0 的读取实现

read.cc (通讯录 2.0)
#include<iostream>
#include<fstream>
#include"contact.pb.h"
using namespace std;
int main()
{contacts::Contacts contacter;fstream input("contacts.bin",ios::in | ios::binary);if(!contacter.ParseFromIstream(&input)) {cerr<<"Parse Error"<<endl;input.close();return -1;}for(int i=0;i<contacter.people_size();i++){const contacts::PeopleInfo& people=contacter.people(i);cout<<people.name()<<":"<<people.age()<<":";for(int j=0;j<people.phone_size();j++){const phone::Phone& numer=people.phone(j);cout<<numer.numbers()<<":";}cout<<endl;}return 0;
}

3. enum 类型

3.1 定义规则

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:
枚举类型名称:
使⽤驼峰命名法,⾸字⺟⼤写。 例如: MyEnum
常量值名称:
全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;
我们可以定义⼀个名为 PhoneType 的枚举类型,定义如下
要注意枚举类型的定义有以下⼏种规则:
1. 0 值常量必须存在,且要作为第⼀个元素。这是为了与 proto2 的语义兼容:第⼀个元素作为默认 值,且值为 0。
2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
3. 枚举的常量值在 32 位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)。

3.2 定义时注意

将两个 ‘具有相同枚举值名称’ 的枚举类型放在单个 .proto ⽂件下测试时,编译后会报错:某某某常
量已经被定义!所以这⾥要注意:
同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
单个 .proto ⽂件下,最外层枚举类型和嵌套枚举类型,不算同级。
多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明 package,每个 proto ⽂
件中的枚举类型都在最外层,算同级。
多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了 package,不算同级
我们继续更新我们的项目
write.cc当中的修改
read.cc的修改

4.Any 类

字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。
型的字段也⽤ repeated 来修饰。
Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有 google 已经定义好的 .proto ⽂件。
PackFrom可以将其他类转化成Any类。
unpackto则可以将Any类还原。

5. oneof 类型

如果消息中有很多可选字段, 并且将来同时只有⼀个字段会被设置, 那么就可以使⽤ oneof 加强这 个⾏为,也能有节约内存的效果。
升级write.cc
升级read.cc
注意:
可选字段中的字段编号,不能与⾮可选字段的编号冲突。
不能在 oneof 中使⽤ repeated 字段。
将来在设置 oneof 字段中值时,如果将 oneof 中的字段设置多个,那么只会保留最后⼀次设置的成 员,之前设置的 oneof 成员会⾃动清除。

6. map 类型

语法⽀持创建⼀个关联映射字段,也就是可以使⽤ map 类型去声明字段类型,格式为:
map<key_type, value_type> map_field = N;
要注意的是:
key_type 是除了 float 和 bytes 类型以外的任意标量类型。 value_type 可以是任意类型。
map 字段不可以⽤ repeated 修饰
map 中存⼊的元素是⽆序的
更新write.cc
更新read.cc

7. 默认值

反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就 会设置为该字段的默认值。不同的类型对应的默认值不同:
对于字符串,默认值为空字符串。
对于字节,默认值为空字节。
对于布尔值,默认值为 false。
•  对于数值类型,默认值为 0。
对于枚举,默认值是第⼀个定义的枚举值, 必须为 0。
对于消息字段,未设置该字段。它的取值是依赖于语⾔。
对于设置了 repeated 的字段的默认值是空的(
通常是相应语⾔的⼀个空列表 )。
对于 消息字段 、 oneof 字段 和 any 字段 ,C++ 和 Java 语⾔中都有 has_ ⽅法来检测当前字段
是否被设置。

8. 更新消息

8.1 更新规则

如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情 况下更新消息类型⾮常简单。遵循如下规则即可:
禁⽌修改任何已有字段的字段编号。
若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号
(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。
int32, uint32, int64, uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个,
⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与 C++ ⼀致的处理⽅案
(例如,若将 64 位整数当做 32 位进⾏读取,它将被截断为 32 位)。
sint32 和 sint64 相互兼容但不与其他的整型兼容。
string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。
bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容。
enum 与 int32,uint32, int64 和 uint64 兼容(注意若值不匹配会被截断)。但要注意当反序
列化消息时会根据语⾔采⽤不同的处理⽅案:例如,未识别的 proto3 枚举类型会被保存在消息
中,但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。
oneof:
将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的。
若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的。
将任何字段移⼊已存在的 oneof 类型是不安全的。

8.2 保留字段 reserved

如果通过 删除 或 注释掉 字段来更新消息类型,未来的⽤⼾在添加新字段时,有可能会使⽤以前已经 存在,但已经被删除或注释掉的字段编号。将来使⽤该 .proto 的旧版本时的程序会引发很多问题:数 据损坏、隐私错误等等。
确保不会发⽣这种情况的⼀种⽅法是:使⽤ reserved 将指定字段的编号或名称设置为保留项 。当
我们再使⽤这些编号或名称时,protocol buffer 的编译器将会警告这些编号或名称不可⽤。举个例
⼦:

8.3 未知字段

未知字段:解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表⽰⽅式。例如,当
旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。
本来,proto3 在解析消息时总是会丢弃未知字段,但在 3.5 版本中重新引⼊了对未知字段的保留机
制。所以在 3.5 或更⾼版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果
中。

8.4 前后兼容性

根据上述的例⼦可以得出,pb是具有向前兼容的。为了叙述⽅便,把增加了“⽣⽇”属性的 service
称为“新模块”;未做变动的 client 称为 “⽼模块”。
向前兼容:⽼模块能够正确识别新模块⽣成或发出的协议。这时新增加的“⽣⽇”属性会被当作未
知字段(pb 3.5版本及之后)。
向后兼容:新模块也能够正确识别⽼模块⽣成或发出的协议。
前后兼容的作⽤:当我们维护⼀个很庞⼤的分布式系统时,由于你⽆法同时 升级所有 模块,为了保证
在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼
容”。

9. 选项 option

.proto ⽂件中可以声明许多选项,使⽤ option 标注。选项能影响 proto 编译器的某些处理⽅式。
选项分为 ⽂件级、消息级、字段级 等等, 但并没有⼀种选项能作⽤于所有的类型

9.1 常⽤选项列举

optimize_for : 该选项为⽂件选项,可以设置 protoc 编译器的优化级别,分别为 SPEED
CODE_SIZE LITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译 .proto ⽂件后⽣
成的代码内容不同。
SPEED : protoc 编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码
编译后会占⽤更多的空间。 SPEED 是默认选项。
CODE_SIZE : proto 编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来
实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这
种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中。
LITE_RUNTIME : ⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常
少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供 encoding+序列化 功能,
所以我们在链接 BP 库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源
有限的平台,例如移动⼿机平台中
allow_alias : 允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。

9.2设置⾃定义选项

ProtoBuf 允许⾃定义选项并使⽤。该功能⼤部分场景⽤不到,在这⾥不拓展讲解。
有兴趣可以参考: https://developers.google.cn/protocol-buffers/docs/proto?hl=zh
cn#customoptions
至此大家应该就已经能够基本使用protobuf了,谢谢大家观看。

http://www.ppmy.cn/news/1526241.html

相关文章

Java零基础-replace(CharSequence target, CharSequence replacement)详解

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云/阿里云/华为云/51CTO&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互…

2024年某大厂HW蓝队面试题分享

&#x1f91f; 基于入门网络安全/黑客打造的资源包无偿分享中&#xff1a; &#x1f449;黑客&网络安全入门&进阶学习资源包 应急响应流程 1&#xff09;首先判断服务器资产、影响范围以及严重程度&#xff0c;确认有没有必要将服务器下线隔离&#xff0c;然后根据服务…

【佳学基因检测】在织梦网站中, 创建或修改目录:/var/www/html/cp 失败! DedeTag Engine Create File False

【佳学基因检测】在织梦网站中, 创建或修改目录&#xff1a;/var/www/html/cp 失败&#xff01; DedeTag Engine Create File False 在使用 DedeCMS&#xff08;一个常用的内容管理系统&#xff09;时&#xff0c;如果遇到“创建或修改目录&#xff1a;/var/www/html/cp 失败&…

工程师 - HUE(Humans in User Experience)介绍

HUE&#xff1a;Humans in User Experience&#xff08;用户体验中的人类&#xff09;是用户体验&#xff08;UX&#xff09;设计领域的一种概念或方法&#xff0c;强调在设计过程中考虑人的因素的重要性。它侧重于了解用户的需求、行为和情感&#xff0c;从而创造出更有效、更…

flask框架

Flask 1 flask简介 我们之所以在浏览器中输入localhost:8080然后就可以把webapps下面的项目文件以浏览器的方式打开&#xff0c;功臣在与tomcat。python语言写的项目&#xff0c;转换为web&#xff0c;Flask框架 轻量级web应用框架。 环境准备&#xff1a; pip install fl…

基于鸿蒙API10的RTSP播放器(七:亮度调节功能测试)

目标&#xff1a; 当我的手指在设备左方进行上下移动的时候&#xff0c;可以进行屏幕亮度的调节&#xff0c;在调节的同时&#xff0c;有实时的调节进度条显示 步骤&#xff1a; 界面逻辑&#xff1a;使用Stack() 组件&#xff0c;完成音量图标和进度条的组合显示&#xff0c…

鹏哥C语言自定义笔记重点(67-)

67. 68. 69. 70. 71.结构体内容 72.理解结构体的字节数 73. #pragma once //头文件中使用&#xff0c;功能是:防止头文件被多次引用 74.结构体传参 结论:结构体传参时&#xff0c;要传结构体地址。 75.位段 76.static是只能在该文件中看到&#xff0c;其他地方看不到 77.…

通俗理解矩阵的秩

通俗理解矩阵的秩 flyfish 一、通俗的理解 想象有一张表格&#xff08;矩阵&#xff09;&#xff0c;表格里有很多数字。矩阵的秩告诉我们这个表格里的数据有多么“特别”或者“复杂”。 1. 行和列的概念&#xff1a; 矩阵是由行&#xff08;横排的数字&#xff09;和列&a…

vue + Lodop 制作可视化设计页面 实现打印设计功能(四)

历史&#xff1a; vue2 Lodop 制作可视化设计页面 实现打印设计功能&#xff08;一&#xff09; vue Lodop 制作可视化设计页面 实现打印设计功能&#xff08;二&#xff09; vue Lodop 制作可视化设计页面 实现打印设计功能&#xff08;三&#xff09; 前言&#xff1a…

uniapp vue3 梯形选项卡组件

实现的效果图&#xff1a; 切换选项卡显示不同的内容&#xff0c;把这个选项卡做成了一个组件&#xff0c;需要的自取。 // 组件名为 trapezoidalTab <template> <view class"pd24"><view class"nav"><!-- 左侧 --><view cla…

web基础之文件上传

1.下载安装 下载地址 链接&#xff1a;百度网盘-链接不存在 提取码&#xff1a;jhks 安装 直接把他放在phpstudy的WWW目录中。&#xff08;phpstudy的下载安装&#xff0c;可以自行百度一下&#xff09; 打开 访问地址&#xff1a;127.0.0.1/upload-labs 问题 这里可能…

MCU与SOC的区别

自动驾驶中 MCU 与 SoC 的区别 在自动驾驶系统中&#xff0c;**MCU&#xff08;微控制单元&#xff0c;Microcontroller Unit&#xff09;和SoC&#xff08;系统级芯片&#xff0c;System on Chip&#xff09;**都是关键的电子元件&#xff0c;但它们在性能、功能和应用领域等…

MATLAB在嵌入式系统设计中的最佳实践

嵌入式系统设计是一个复杂的过程&#xff0c;涉及硬件和软件的紧密集成。MATLAB提供了一套全面的解决方案&#xff0c;从算法开发到代码生成&#xff0c;再到硬件验证&#xff0c;极大地简化了这一过程。本文将探讨使用MATLAB进行嵌入式系统设计的最佳实践&#xff0c;包括模型…

10分钟在钉钉上增加一个AI机器人

您只需 10 分钟&#xff0c;无需任何编码&#xff0c;即可为您的组织在钉钉平台上创建一个有大模型能力加成的 AI 机器人。这个机器人可以全天候&#xff08;7x24&#xff09;响应用户咨询&#xff0c;还能解答私域问题&#xff0c;成为您业务的专属机器人&#xff0c;提升用户…

时序数据库 TDengine 的入门体验和操作记录

时序数据库 TDengine 的学习和使用经验 什么是 TDengine &#xff1f;什么是时序数据 &#xff1f;使用RPM安装包部署默认的网络端口 TDengine 使用TDengine 命令行&#xff08;CLI&#xff09;taosBenchmark服务器内存需求删库跑路测试 使用体验文档纠错 什么是 TDengine &…

k8s service如何实现流量转发

1 基本概念 Service&#xff1a;在Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Service用于将流量转发到后端的Pod中。Service提供了一种稳定的网络入口&#xff0c;尽管后端的Pod可能会动态改变 kube-proxy: kube-proxy是Kubernetes集群中的核心组件之一&#xff0…

C++:opencv多边形逼近二值图轮廓--cv::approxPolyDP

cv::approxPolyDP 是 OpenCV 中一个用于多边形逼近的函数。它通过 Douglas-Peucker 算法将复杂的轮廓简化为更少的点&#xff0c;这在图像处理和计算机视觉中非常有用。例如&#xff0c;简化的轮廓可以帮助提高形状分析和轮廓检测的效率。 函数原型 void cv::approxPolyDP(co…

基于Python的自然语言处理系列(9):使用TorchText与预训练词嵌入进行新闻分类

在前一篇文章中&#xff0c;我们展示了如何使用TorchText和RNN进行新闻分类。在这篇文章中&#xff0c;我们将改进之前的模型&#xff0c;通过使用预训练词嵌入、优化器的更改、正交初始化以及打包填充序列的技巧&#xff0c;提升模型的学习效率和效果。 1. 改进方向 提高模型…

计算机网络八股总结

这里写目录标题 网络模型划分&#xff08;五层和七层&#xff09;及每一层的功能五层网络模型七层网络模型&#xff08;OSI模型&#xff09; 三次握手和四次挥手具体过程及原因三次握手四次挥手 TCP/IP协议组成UDP协议与TCP/IP协议的区别Http协议相关知识网络地址&#xff0c;子…

HarmonyOS开发之使用PhotoViewPicker(图库选择器)保存图片

一&#xff1a;效果图 二&#xff1a;添加依赖 import fs from ohos.file.fs;//文件管理 import picker from ohos.file.picker//选择器 三&#xff1a;下载&#xff0c;保存图片的实现 // 下载图片imgUrldownloadAndSaveImage(imgUrl: string) {http.createHttp().request(…