2-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中从未使用过的字段号,甚至没有被删除的字段)。

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

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

相关文章

Xilinx FPGA:vivado串口输入输出控制fifo中的数据

一、实验要求 实现同步FIFO回环测试&#xff0c;通过串口产生数据&#xff0c;写入到FIFO内部&#xff0c;当检测到按键信号到来&#xff0c;将FIFO里面的数据依次读出。 二、信号流向图 三、状态转换图 四、程序设计 &#xff08;1&#xff09;按键消抖模块 timescale 1ns…

python-django-LlamaIndex 精简版

&#x1f680; 一键安装LlamaIndex&#xff0c; pip install llama-index &#x1f4c1; 准备你的数据文件&#xff0c;无论是txt还是pdf&#xff0c;放入data文件夹&#xff0c;一切就绪。 &#x1f527; 简单几步&#xff0c;在views.py中集成LlamaIndex&#xff0c;代码如…

读书笔记-《魔鬼经济学》

这是一本非常有意思的经济学启蒙书&#xff0c;作者探讨了许多问题&#xff0c;并通过数据找到答案。 我们先来看看作者眼中的“魔鬼经济学”是什么&#xff0c;再选一个贴近我们生活的例子进行阐述。 01 魔鬼经济学 中心思想&#xff1a;假如道德代表人类对世界运转方式的期…

uniapp实现一个键盘功能

前言 因为公司需要&#xff0c;所以我.... 演示 代码 键盘组件代码 <template><view class"keyboard_container"><view class"li" v-for"(item, index) in arr" :key"index" click"changArr(item)" :sty…

初学Spring之 AOP 面向切面编程

AOP&#xff08;Aspect Oriented Programming&#xff09;面向切面编程 通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术 是面向对象&#xff08;OOP&#xff09;的延续 AOP 在 Spring 中的作用&#xff1a; 1.提供声明式事务 2.允许用户自定义切面 导…

Objects365数据集介绍

Objects365数据集介绍 什么是Objects365数据集&#xff1f;数据集的规模与内容数据集的特点数据集下载 什么是Objects365数据集&#xff1f; Objects365是一个大规模、高质量的物体检测数据集。该数据集旨在推动物体检测技术的发展&#xff0c;特别是在真实世界场景下的应用。O…

Python 学习之机器学习库(九)

Python的机器学习库种类繁多&#xff0c;每个库都有其独特的特性和应用场景。以下是一些主要的Python机器学习库&#xff0c;按照其功能和特点进行清晰归纳和分点表示&#xff1a; 1. NumPy ● 功能&#xff1a;NumPy是Python中用于科学计算的基础库&#xff0c;提供了高性能的…

【python】python当当数据分析可视化聚类支持向量机预测(源码+数据集+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

基于java+springboot+vue实现的校园外卖服务系统(文末源码+Lw)292

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;外卖信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

数据库SQL Server窗口函数、聚合函数

文章目录 窗口函数窗口函数分类窗口函数示例聚合函数示例注意事项 流水表提取最新状态 窗口函数 SQL Server中的窗口函数&#xff08;也称为分析函数&#xff09;是一组非常强大的SQL功能&#xff0c;**它们允许你在结果集的行上执行计算&#xff0c;而不需要将结果集分组为多…

React-tive优质开源项目

对于初学者来说&#xff0c;接触和学习React相关的优质开源项目是一个非常好的方式来提升编程技能&#xff0c;特别是对于理解React的实际应用和最佳实践。这里推荐几个React开源项目&#xff0c;它们通常会附带详细的文档和示例代码&#xff0c;帮助新手快速上手&#xff1a; …

Java中如何实现线程池的生命周期管理

1、创建线程池 使用Executors工厂类或者ThreadPoolExecutor的构造函数来创建线程池。通常&#xff0c;推荐直接使用ThreadPoolExecutor构造函数来明确指定线程池的参数&#xff0c;如核心线程数、最大线程数、空闲线程存活时间、工作队列等。 2、执行任务 通过调用线程池的s…

使用Charles mock服务端响应数据

背景 服务端未提供接口/服务端接口返回结果有逻辑限制&#xff08;次数限制&#xff09;&#xff0c;不能通过原始接口返回多次模拟预期的返回结果&#xff0c;例如边界值情况 客户端受到接口响应数据的限制&#xff0c;无法继续开发或测试&#xff0c;会极大影响开发测试效率…

Perl 数据类型

Perl 数据类型 Perl 是一种功能丰富的编程语言&#xff0c;广泛应用于系统管理、网络编程、GUI 开发等领域。在 Perl 中&#xff0c;数据类型是编程的基础&#xff0c;决定了变量存储信息的方式以及可以对这些信息执行的操作。本文将详细介绍 Perl 中的主要数据类型&#xff0…

QT滑块图片验证程序

使用QT实现滑块验证程序&#xff0c;原理是画个图片&#xff0c;然后在图片上画个空白区域&#xff0c;再画个滑块图片。 widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widg…

文心智能体平台快速创建一个HY(Lisp)编程小助手

现在可以在文心智能体平台&#xff0c;使用文心一言创建各种智能体了&#xff01;创建步骤如下&#xff1a; 创建知识库 可以使用本地上传的方式来提交&#xff0c;鼠标移动到”查看模板“&#xff0c;可以下载”知识库外链上传示例模版.xlsx“&#xff0c;按照模板里的格式&…

8.14 矢量图层面要素2.5D渲染

文章目录 前言2.5D渲染QGis设置面符号为2.5D二次开发代码实现2.5D 总结 前言 本章介绍矢量图层面要素2.5D渲染的使用说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 2.5D渲染 2.5D渲染可以将多边形渲染为类3D效果。 QGis设置面符号为2.5D 以"hou…

Swagger的原理及应用详解(十一)

本系列文章简介&#xff1a; 在当今快速发展的软件开发领域&#xff0c;特别是随着微服务架构和前后端分离开发模式的普及&#xff0c;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;的设计与管理变得愈发重要。一个清晰、准确且易…

生成式AI的短板在于“Token”的存在

生成式AI模型处理文本的方式与人类不同。理解它们基于“token”的内部环境&#xff0c;可能有助于解释一些奇怪行为和固有局限性。 从小型设备上的Gemma到OpenAI领先行业的GPT-4o&#xff0c;大多数模型都是基于一种称为Transformer的架构。由于Transformer在将文本与其他类型…

[Multi-Modal] MDETR 论文及代码学习笔记

代码地址&#xff1a;https://github.com/ashkamath/mdetr 论文地址&#xff1a;https://arxiv.org/abs/2104.12763 多模态推理系统依靠预先训练的目标检测器从图像中提取感兴趣区域&#xff08;边界框包围区域&#xff09;。然而&#xff0c;这个关键模块通常被用作黑匣子&…