Protocol Buffer 基础(c++)
本教程提供了使用协议缓冲区的基本介绍。通过逐步创建一个简单的示例应用程序,介绍以下内容:
1.在.proto文件中定义消息格式。
2.使用 protocol buffer 编译器。
3.使用c++ protocol buffer API来写入和读取消息。
一、问题描述
将要使用的示例是一个非常简单的“地址簿”应用程序,它可以从文件中读取和写入人们的联系详细信息。地址簿中的每个人都有姓名、ID、电子邮件地址和联系电话号码。
二、定义协议格式
要创建地址簿应用程序,需要从.proto文件开始。proto文件中的定义很简单:为要序列化的每个数据结构添加消息,然后为消息中的每个字段指定名称和类型。下面是定义消息的.proto文件addressbook.proto。
syntax = "proto2";package tutorial;message Person {optional string name = 1;optional int32 id = 2;optional string email = 3;enum PhoneType {PHONE_TYPE_UNSPECIFIED = 0;PHONE_TYPE_MOBILE = 1;PHONE_TYPE_HOME = 2;PHONE_TYPE_WORK = 3;}message PhoneNumber {optional string number = 1;optional PhoneType type = 2 [default = PHONE_TYPE_HOME];}repeated PhoneNumber phones = 4;
}message AddressBook {repeated Person people = 1;
}
proto文件以包声明开始,这有助于防止不同项目之间的命名冲突。在c++中,生成的类将放在与包名匹配的命名空间中。
接下来,您有您的消息定义。消息只是包含一组类型化字段的聚合。许多标准的简单数据类型都可以作为字段类型使用,包括bool、int32、float、double和string。您还可以通过使用其他消息类型作为字段类型来为消息添加进一步的结构—在上面的示例中,Person消息包含PhoneNumber消息,而AddressBook消息包含Person消息。您甚至可以定义嵌套在其他消息中的消息类型—如您所见,PhoneNumber类型是在Person中定义的。如果您希望某个字段具有预定义值列表中的一个,也可以定义枚举类型—这里您希望指定电话号码可以是以下电话类型之一:PHONE_TYPE_MOBILE、PHONE_TYPE_HOME或PHONE_TYPE_WORK。
每个元素上的“= 1”、“= 2”标记标识该字段在二进制编码中使用的唯一字段号。字段数1-15比更高的数字需要少一个字节编码,因此作为优化,您可以决定将这些数字用于常用或重复的元素,将字段数16或更高的数字用于不太常用的可选元素。repeated字段中的每个元素都需要重新编码字段号,因此repeated字段特别适合进行此优化。
optional:该字段可以设置,也可以不设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像在示例中为电话号码类型所做的那样。否则,使用系统默认值:数字类型为零,字符串为空字符串,bool为false。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,它没有设置任何字段。调用访问器获取未显式设置的可选(或必需)字段的值总是返回该字段的默认值。
repeated:该字段可以重复任何次数(包括零)。重复值的顺序将保留在protocol buffer中。可以将重复字段视为动态大小的数组。
Required:必须提供该字段的值,否则消息将被视为“未初始化”。如果libprotobuf在调试模式下编译,序列化未初始化的消息将导致失败。除此之外,Required字段的行为与optional字段完全相同。
注意:非常小心地将字段标记为Required。如果在某些时候您希望停止写入或发送Required字段,将该字段更改为可选字段将会出现问题-旧的阅读器会认为没有此字段的消息是不完整的,并且可能会拒绝或无意中丢弃它们。您应该考虑为缓冲区编写特定于应用程序的自定义验证例程。在谷歌内部,Required字段是非常不受欢迎的;在proto2语法中定义的大多数消息只使用可选的和重复的。(Proto3根本不支持必填字段。)
三、编译Protocol Buffers
现在有了.proto,接下来需要做的就是生成读取和写入AddressBook(因此还有Person和PhoneNumber)消息所需的类。要做到这一点,你需要在.proto上运行协议缓冲区编译器协议:
1.如果您还没有安装编译器,请下载该包并按照README中的说明进行操作。
2. 现在运行编译器,指定源目录(应用程序源代码所在的位置——如果不提供值则使用当前目录),目标目录(生成的代码存放的位置;通常与$SRC_DIR相同),以及.proto的路径。
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
因为需要c++类,所以使用--cpp_out选项——为其他受支持的语言提供了类似的选项。
这将在指定的目标目录中生成以下文件:
addressbook.pb.h,声明生成类的头文件。
addressbook.pb.cc,其中包含类的实现。
四、Protocol Buffers的API
让我们看一下生成的一些代码,看看编译器创建了哪些类和函数。如果查看addressbook.pb.h,可以看到在addressbook.proto中指定的每条消息都有一个类。仔细观察Person类,您可以看到编译器已经为每个字段生成了访问器。例如,对于name、id、email和phones字段,您可以使用这些方法:
// nameinline bool has_name() const;inline void clear_name();inline const ::std::string& name() const;inline void set_name(const ::std::string& value);inline void set_name(const char* value);inline ::std::string* mutable_name();// idinline bool has_id() const;inline void clear_id();inline int32_t id() const;inline void set_id(int32_t value);// emailinline bool has_email() const;inline void clear_email();inline const ::std::string& email() const;inline void set_email(const ::std::string& value);inline void set_email(const char* value);inline ::std::string* mutable_email();// phonesinline int phones_size() const;inline void clear_phones();inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phones() const;inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phones();inline const ::tutorial::Person_PhoneNumber& phones(int index) const;inline ::tutorial::Person_PhoneNumber* mutable_phones(int index);inline ::tutorial::Person_PhoneNumber* add_phones();
如上所示,getter方法的名称正好是小写的字段,而setter方法以set_开头。对于每个单个字段(必需的或可选的)也有has_方法,如果该字段已设置,则返回true。最后,每个字段都有一个clear_方法,用于将字段取消设置为空状态。
虽然数字id字段只有上面描述的基本访问器集,但name和email字段有两个额外的方法,因为它们是字符串——一个mutable_getter,让您可以直接获得指向字符串的指针,还有一个额外的setter。注意,你可以调用mutable_email(),即使email还没有设置;它将自动初始化为空字符串。如果在这个示例中有一个重复的消息字段,那么它也会有一个mutable_方法,而不是set_方法。
repeated字段也有一些特殊的方法——如果你看一下repeated电话字段的方法,你会发现有以下方法:
1.检查重复字段的_size(换句话说,有多少个电话号码与这个Person相关联)。
2.使用其索引获取指定的电话号码。
3.更新指定索引处的现有电话号码。
4.将另一个电话号码添加到消息中,然后可以对其进行编辑(重复的标量类型有一个add_,它只允许您传入新值)。
五、枚举和嵌套类
生成的代码包括一个PhoneType枚举,它对应于您的.proto枚举。您可以将此类型引用为Person::PhoneType,将其值引用为Person::PHONE_TYPE_MOBILE、Person::PHONE_TYPE_HOME和Person::PHONE_TYPE_WORK(实现细节稍微复杂一些,但您不需要理解它们就可以使用enum)。
编译器还生成了一个名为Person::PhoneNumber的嵌套类。如果查看代码,可以看到“真正的”类实际上被称为Person_PhoneNumber,但是在Person内部定义的typedef允许您将其视为嵌套类。
六、标准消息方法
每个消息类还包含许多其他方法,可以让您检查或操作整个消息,包括:
1.bool IsInitialized() const;:检查是否设置了所有必需的字段。
2.string DebugString() const;:返回消息的人类可读的表示形式,对调试特别有用。
3.void CopyFrom(const Person& from);:用给定消息的值覆盖消息。
4.void Clear();:将所有元素清除回空状态。
七、解析和序列化
每个协议缓冲区类都有使用协议缓冲区二进制格式写入和读取所选类型消息的方法。这些包括:
1.bool SerializeToString(string* output) const;:序列化消息并将字节存储在给定的字符串中。请注意,字节是二进制的,而不是文本;我们仅将string类用作方便的容器。
2.bool ParseFromString(const string& data);:从给定字符串中解析消息。
3.bool serializetostream (ostream* output) const;:将消息写入给定的c++ ostream。
4.bool parsefroerror (istream* input);解析来自给定c++ istream的消息。
八、写入message
现在让我们尝试使用协议缓冲类。你希望你的地址簿应用能够做的第一件事是将个人详细信息写入你的地址簿文件。为此,您需要创建并填充协议缓冲区类的实例,然后将它们写入输出流。
下面是一个程序,它从一个文件中读取一个地址簿,根据用户输入添加一个新的Person,并将新的地址簿再次写回文件。直接调用或引用 protocol编译器生成的代码的部分突出显示。
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {cout << "Enter person ID number: ";int id;cin >> id;person->set_id(id);cin.ignore(256, '\n');cout << "Enter name: ";getline(cin, *person->mutable_name());cout << "Enter email address (blank for none): ";string email;getline(cin, email);if (!email.empty()) {person->set_email(email);}while (true) {cout << "Enter a phone number (or leave blank to finish): ";string number;getline(cin, number);if (number.empty()) {break;}tutorial::Person::PhoneNumber* phone_number = person->add_phones();phone_number->set_number(number);cout << "Is this a mobile, home, or work phone? ";string type;getline(cin, type);if (type == "mobile") {phone_number->set_type(tutorial::Person::PHONE_TYPE_MOBILE);} else if (type == "home") {phone_number->set_type(tutorial::Person::PHONE_TYPE_HOME);} else if (type == "work") {phone_number->set_type(tutorial::Person::PHONE_TYPE_WORK);} else {cout << "Unknown phone type. Using default." << endl;}}
}// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
int main(int argc, char* argv[]) {// Verify that the version of the library that we linked against is// compatible with the version of the headers we compiled against.GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2) {cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;return -1;}tutorial::AddressBook address_book;{// Read the existing address book.fstream input(argv[1], ios::in | ios::binary);if (!input) {cout << argv[1] << ": File not found. Creating a new file." << endl;} else if (!address_book.ParseFromIstream(&input)) {cerr << "Failed to parse address book." << endl;return -1;}}// Add an address.PromptForAddress(address_book.add_people());{// Write the new address book back to disk.fstream output(argv[1], ios::out | ios::trunc | ios::binary);if (!address_book.SerializeToOstream(&output)) {cerr << "Failed to write address book." << endl;return -1;}}// Optional: Delete all global objects allocated by libprotobuf.google::protobuf::ShutdownProtobufLibrary();return 0;
}
注意GOOGLE_PROTOBUF_VERIFY_VERSION宏。在使用c++协议缓冲区库之前执行这个宏是一个很好的实践(尽管不是严格必要的)。它验证程序没有意外地链接到与您所编译的头文件版本不兼容的库版本。如果检测到版本不匹配,程序将中止。请注意,每个.pb。Cc文件在启动时自动调用此宏。
还要注意在程序末尾对ShutdownProtobufLibrary()的调用。这样做的目的是删除由Protocol Buffer库分配的所有全局对象。这对大多数程序来说是不必要的,因为进程无论如何都会退出,操作系统会负责回收所有的内存。但是,如果您使用内存泄漏检查器,要求释放每个最后的对象,或者如果您正在编写一个可能由单个进程多次加载和卸载的库,那么您可能希望 Protocol Buffers清理所有内容。
九、读取message
当然,如果你不能从中获取任何信息,地址簿就没有多大用处了!这个示例读取上面示例创建的文件并打印其中的所有信息。
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {for (int i = 0; i < address_book.people_size(); i++) {const tutorial::Person& person = address_book.people(i);cout << "Person ID: " << person.id() << endl;cout << " Name: " << person.name() << endl;if (person.has_email()) {cout << " E-mail address: " << person.email() << endl;}for (int j = 0; j < person.phones_size(); j++) {const tutorial::Person::PhoneNumber& phone_number = person.phones(j);switch (phone_number.type()) {case tutorial::Person::PHONE_TYPE_MOBILE:cout << " Mobile phone #: ";break;case tutorial::Person::PHONE_TYPE_HOME:cout << " Home phone #: ";break;case tutorial::Person::PHONE_TYPE_WORK:cout << " Work phone #: ";break;}cout << phone_number.number() << endl;}}
}// Main function: Reads the entire address book from a file and prints all
// the information inside.
int main(int argc, char* argv[]) {// Verify that the version of the library that we linked against is// compatible with the version of the headers we compiled against.GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2) {cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;return -1;}tutorial::AddressBook address_book;{// Read the existing address book.fstream input(argv[1], ios::in | ios::binary);if (!address_book.ParseFromIstream(&input)) {cerr << "Failed to parse address book." << endl;return -1;}}ListPeople(address_book);// Optional: Delete all global objects allocated by libprotobuf.google::protobuf::ShutdownProtobufLibrary();return 0;
}
十、扩展Protocol Buffer
在发布了使用协议缓冲区的代码之后,迟早会想要“改进”协议缓冲区的定义。如果希望新缓冲区向后兼容,而旧缓冲区向前兼容(肯定希望这样),那么需要遵循一些规则。在新版本的Protocol Buffer中:
1.不能更改任何现有字段的字段号。
2.不能添加或删除任何required字段。
3.可以删除optional或repeated的字段。
4.可以添加新的optional字段或repeated字段,但必须使用新的字段号(即,在此Protocol Buffer中从未使用过的字段号,甚至没有被删除的字段)。
相关文章:
Protocol Buffer 基础(c++)
本教程提供了使用协议缓冲区的基本介绍。通过逐步创建一个简单的示例应用程序,介绍以下内容: 1.在.proto文件中定义消息格式。 2.使用 protocol buffer 编译器。 3.使用c protocol buffer API来写入和读取消息。 一、问题描述 将要使用的示例是…...

上位机网络通讯
目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 using System; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace 上位机网络通讯 {public partial class Form1 : Form{public Form1(){Initializ…...

转让5000万无区域能源公司要求和流程
国家局的公司,也就是无地域无区域性的公司名称。这样的公司是还可以继续注册的,但是想要拥有国家局无区域的名称就不是那么容易的了。总局的企业要求高,也是实力的体现。对字号有保护。所以有很多人都对无地域的名称一直情有独钟。现有一家名…...

WordPress Quiz Maker插件 SQL注入漏洞复现(CVE-2024-6028)
0x01 产品简介 WordPress Quiz Maker插件是一款功能强大的测验生成工具,旨在帮助用户轻松、快速地构建复杂的测验和考试。插件支持多种问题类型,包括单选框(MCQ)、复选框(MCQ)、下拉列表(MCQ)、文本、短文本、数字、日期等。还支持横幅(HTML)显示信息性消息、填空题…...

Swift中的二分查找:全面指南
Swift中的二分查找:全面指南 简介 二分查找是计算机科学中的经典算法,被广泛用于在已排序的数组中高效地搜索目标值。与线性查找逐个检查每个元素不同,二分查找不断将搜索区间减半,因此在处理大数据集时要快得多。 在这篇博客中…...

BUG TypeError: GPT2Model.forward() got an unexpected keyword argument ‘past’
TypeError: GPT2Model.forward() got an unexpected keyword argument past’ 环境 transformers 4.38.1详情 这是由于新版的transformers 对GPT2Model.forward() 参数进行了改变导致的错误。具体是past名称改为了 past_key_values 。 解决方法 找到错误语…...
解析Kotlin中的Lambda【笔记摘要】
先看实例: fun b(param: Int): String {return param.toString() }fun a(funParam: (Int) -> String): String {return funParam(1) }a(::b) val d ::b1.双冒号 ::method 到底是什么?答:一个指向和该函数具有相同功能的对象的引用 因为…...
rust单元测试顺序执行
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 存在的问题 有时候,不同单元测试之间可能会竞争相同的资源,比如读写相同的文件。在这种情况下,如果…...
力扣-744. 寻找比目标字母大的最小字母
文章目录 力扣题目代码工程 力扣题目 给你一个字符数组 letters,该数组按非递减顺序排序,以及一个字符 target。letters 里至少有两个不同的字符。 返回 letters 中大于 target 的最小的字符。如果不存在这样的字符,则返回 letters 的第一个…...

一篇文章搞懂弹性云服务器和轻量云服务器的区别
前言 在众多的云服务器类型中,弹性云服务器和轻量云服务器因其各自的特点和优势,受到了广大用户的青睐。那么,这两者之间到底有哪些区别呢?本文将为您详细解析。 弹性云服务器:灵活多变的计算资源池 弹性云服务器&…...
横穿自动驾驶
如果有一条线,可以穿起来所有自动驾驶的核心模块,那么我感觉它就是最优化,选择优化变量、构造优化问题、求解优化问题,这几个步骤贯穿了自动驾驶的始终。 先从我的自身接触顺序写起。最开始做个一点深度学习,那还是20…...

为什么网上商店需要翻译成其他语言
网上商店不仅仅是一个可以买到商品的网站。它是一个完整的电子商务平台,为来自世界各地的用户提供购买所需物品的机会。但是,为了让这些用户舒适地使用网站,需要高质量的翻译和本地化。 本地化是指产品或服务适应特定文化或市场的过程。它包…...
【高考志愿】交通运输工程
目录 一、专业概述 二、课程设置 三、就业前景 四、报考注意 五、未来发展 六、交通运输工程专业排名 高考志愿选择交通运输工程专业,无疑是一个既具远见又富有挑战性的决定。这个专业以其综合性强、实用性高的特点,吸引了大批有志于投身交通事业的…...

【深度学习】【Lora训练3】StabelDiffusion,Lora训练过程,秋叶包,Linux,SDXL Lora训练
为了便于使用,构建一个docker镜像来使用秋叶包。2024年6月26日。 docker run -it --gpus all -v /ssd/xiedong:/datax --net host kevinchina/deeplearning:pytorch2.3.0-cuda12.1-cudnn8-devel-xformers bashgit clone --recurse-submodules https://github.com/A…...
ubuntu系统下如何安装python
在Ubuntu系统下安装Python,有多种方法可供选择。以下是两种常见的方法: 一、使用apt包管理器安装 安装步骤如下: 首先更新软件包列表 sudo apt update安装Python 3: 输入以下命令以安装Python 3(Ubuntu的默认Pyth…...
邦芒攻略:职场中学会这五种管好情绪的方法
我们在公司里面在跟同事的一些往来,或者说是工作的一些合作当中。相信很多人都会有与周围的一些人发生过一些各种的争执,或者说是一些分歧。当然作为每一个职场的人来说,每天都是工作很累的,也是都很辛苦的,所以说…...
Linux各种命令——tac命令,more 命令, less命令,head命令,tail命令,file 命令, stat 命令
注意:tac命令是倒置输出文件内容 #### tac - **作用:倒叙访问文件内容** - 格式:tac 参数 文件名 - **例如:** **tac /etc/passwd** #### more 命令 - 作用:翻页查看文件内容,适合内容较多的文件查看…...

【Rust入门教程】hello world程序
文章目录 前言Hello World程序运行总结 前言 对于学习任何一种新的编程语言,我们都会从编写一个简单的Hello World程序开始。这是一个传统,也是一个开始。在这篇文章中,我们将一起学习如何在Rust中编写你的第一个程序:Hello Worl…...
激活函数、向前传播、损失函数、梯度下降
激活函数 作用:主要引入了非线性。从而能解决很复杂的非线性关系。能更好地处理现实世界的数据和任务。 向前传播 向前传播描述了,神经网络中,输入层到输出层的信号传播和处理过程。输入层将特征数据输入,加权求和,…...

three.js - MeshStandardMaterial(标准网格材质)- 金属贴图、粗糙贴图
金属贴图、粗糙贴图 金属贴图:metalnessMap 和 粗糙贴图:roughnessMap,是用于模拟物体表面属性的两种重要贴图技术,这两种贴图,通常与基于物理的渲染(PBR)材质(如:MeshSt…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...