C++_程序员_Protocol_Buffers_基础指南

摘要: 这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示: 在 .proto 文件中定义消息格式 使用 protocol buffer 编译器 使用 C++ protocol buffer API 读写消息 这不是一个关于在 C++ 中使用 protocol buffers 的全面指南。

这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍。通过创建一个简单的示例应用程序,它将向我们展示:

这不是一个关于在 C++ 中使用 protocol buffers 的全面指南。要获取更详细的信息,请参考 Protocol Buffer Language Guide 和 Encoding Reference。

为什么使用 Protocol Buffers

我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。地址簿中的每一个人都有一个名字、ID、邮件地址和联系电话。

如何序列化和获取结构化的数据?这里有几种解决方案:

Protocol buffers 是针对这个问题的一种灵活、高效、自动化的解决方案。使用 Protocol buffers,你需要写一个 .proto 说明,用于描述你所希望存储的数据结构。利用 .proto 文件,protocol buffer 编译器可以创建一个类,用于实现对高效的二进制格式的 protocol buffer 数据的自动化编码和解码。产生的类提供了构造 protocol buffer 的字段的 getters 和 setters,并且作为一个单元来处理读写 protocol buffer 的细节。重要的是,protocol buffer 格式支持格式的扩展,代码仍然可以读取以旧格式编码的数据。

在哪可以找到示例代码

示例代码被包含于源代码包,位于“examples”文件夹。可在这里下载代码。

定义你的协议格式

为了创建自己的地址簿应用程序,你需要从 .proto 开始。.proto 文件中的定义很简单:为你所需要序列化的每个数据结构添加一个消息message,然后为消息中的每一个字段指定一个名字和类型。这里是定义你消息的.proto 文件 addressbook.proto


  1. package tutorial;
  2. message Person {
  3. required string name = 1;
  4. required int32 id = 2;
  5. optional string email = 3;
  6. enum PhoneType {
  7. MOBILE = 0;
  8. HOME = 1;
  9. WORK = 2;
  10. }
  11. message PhoneNumber {
  12. required string number = 1;
  13. optional PhoneType type = 2 [default = HOME];
  14. }
  15. repeated PhoneNumber phone = 4;
  16. }
  17. message AddressBook {
  18. repeated Person person = 1;
  19. }

如你所见,其语法类似于 C++ 或 Java。我们开始看看文件的每一部分内容做了什么。

.proto 文件以一个 package 声明开始,这可以避免不同项目的命名冲突。在 C++,你生成的类会被置于与package 名字一样的命名空间。

下一步,你需要定义消息message。消息只是一个包含一系列类型字段的集合。大多标准的简单数据类型是可以作为字段类型的,包括 boolint32floatdouble 和 string。你也可以通过使用其他消息类型作为字段类型,将更多的数据结构添加到你的消息中——在以上的示例,Person 消息包含了 PhoneNumber 消息,同时AddressBook 消息包含 Person 消息。你甚至可以定义嵌套在其他消息内的消息类型——如你所见,PhoneNumber 类型定义于 Person 内部。如果你想要其中某一个字段的值是预定义值列表中的某个值,你也可以定义 enum 类型——这儿你可以指定一个电话号码是 MOBILEHOME 或 WORK 中的某一个。

每一个元素上的 = 1= 2 标记确定了用于二进制编码的唯一“标签”tag。标签数字 1-15 的编码比更大的数字少需要一个字节,因此作为一种优化,你可以将这些标签用于经常使用的元素或 repeated 元素,剩下 16 以及更高的标签用于非经常使用的元素或 optional 元素。每一个 repeated 字段的元素需要重新编码标签数字,因此 repeated 字段适合于使用这种优化手段。

每一个字段必须使用下面的修饰符加以标注:

你可以查找关于编写 .proto 文件的完整指导——包括所有可能的字段类型——在 Protocol Buffer Language Guide 里面。不要在这里面查找与类继承相似的特性,因为 protocol buffers 不会做这些。

required 是永久性的

在把一个字段标识为 required 的时候,你应该特别小心。如果在某些情况下你不想写入或者发送一个 required 的字段,那么将该字段更改为 optional 可能会遇到问题——旧版本的读者(LCTT 译注:即读取、解析旧版本 Protocol Buffer 消息的一方)会认为不含该字段的消息是不完整的,从而有可能会拒绝解析。在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google 的一些工程师得出了一个结论:使用 required 弊多于利;他们更愿意使用 optional 和 repeated 而不是required。当然,这个观点并不具有普遍性。

编译你的 Protocol Buffers

既然你有了一个 .proto,那你需要做的下一件事就是生成一个将用于读写 AddressBook 消息的类(从而包括Person 和 PhoneNumber)。为了做到这样,你需要在你的 .proto 上运行 protocol buffer 编译器protoc

  1. 如果你没有安装编译器,请下载这个包,并按照 README 中的指令进行安装。
  2. 现在运行编译器,指定源目录(你的应用程序源代码位于哪里——如果你没有提供任何值,将使用当前目录)、目标目录(你想要生成的代码放在哪里;常与 $SRC_DIR 相同),以及你的 .proto 路径。在此示例中:

  1. protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

因为你想要 C++ 的类,所以你使用了 --cpp_out 选项——也为其他支持的语言提供了类似选项。

在你指定的目标文件夹,将生成以下的文件:

Protocol Buffer API

让我们看看生成的一些代码,了解一下编译器为你创建了什么类和函数。如果你查看 addressbook.pb.h,你可以看到有一个在 addressbook.proto 中指定所有消息的类。关注 Person 类,可以看到编译器为每个字段生成了读写函数accessors。例如,对于 nameidemail 和 phone 字段,有下面这些方法:(LCTT 译注:此处原文所指文件名有误,径该之。)


  1. // name
  2. inline bool has_name() const;
  3. inline void clear_name();
  4. inline const ::std::string& name() const;
  5. inline void set_name(const ::std::string& value);
  6. inline void set_name(const char* value);
  7. inline ::std::string* mutable_name();
  8. // id
  9. inline bool has_id() const;
  10. inline void clear_id();
  11. inline int32_t id() const;
  12. inline void set_id(int32_t value);
  13. // email
  14. inline bool has_email() const;
  15. inline void clear_email();
  16. inline const ::std::string& email() const;
  17. inline void set_email(const ::std::string& value);
  18. inline void set_email(const char* value);
  19. inline ::std::string* mutable_email();
  20. // phone
  21. inline int phone_size() const;
  22. inline void clear_phone();
  23. inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const;
  24. inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();
  25. inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
  26. inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
  27. inline ::tutorial::Person_PhoneNumber* add_phone();

正如你所见到,getters 的名字与字段的小写名字完全一样,并且 setter 方法以 set_ 开头。同时每个单一singular(required 或 optional)字段都有 has_ 方法,该方法在字段被设置了值的情况下返回 true。最后,所有字段都有一个 clear_ 方法,用以清除字段到空empty状态。

数字型的 id 字段仅有上述的基本读写函数accessors集合,而 name 和 email 字段有两个额外的方法,因为它们是字符串——一个是可以获得字符串直接指针的mutable_ 的 getter ,另一个为额外的 setter。注意,尽管email 还没被设置set,你也可以调用 mutable_email;因为 email 会被自动地初始化为空字符串。在本例中,如果你有一个单一的(required 或 optional)消息字段,它会有一个 mutable_ 方法,而没有 set_方法。

repeated 字段也有一些特殊的方法——如果你看看 repeated 的 phone 字段的方法,你可以看到:

为了获取 protocol 编译器为所有字段定义生成的方法的信息,可以查看 C++ generated code reference。

枚举和嵌套类

与 .proto 的枚举相对应,生成的代码包含了一个 PhoneType 枚举。你可以通过 Person::PhoneType 引用这个类型,通过 Person::MOBILEPerson::HOME 和 Person::WORK 引用它的值。(实现细节有点复杂,但是你无须了解它们而可以直接使用)

编译器也生成了一个 Person::PhoneNumber 的嵌套类。如果你查看代码,你可以发现真正的类型为Person_PhoneNumber,但它通过在 Person 内部使用 typedef 定义,使你可以把 Person_PhoneNumber 当成嵌套类。唯一产生影响的一个例子是,如果你想要在其他文件前置声明该类——在 C++ 中你不能前置声明嵌套类,但是你可以前置声明 Person_PhoneNumber

标准的消息方法

所有的消息方法都包含了许多别的方法,用于检查和操作整个消息,包括:

上面这些方法以及下一节要讲的 I/O 方法实现了被所有 C++ protocol buffer 类共享的消息Message接口。为了获取更多信息,请查看 complete API documentation for Message。

解析和序列化

最后,所有 protocol buffer 类都有读写你选定类型消息的方法,这些方法使用了特定的 protocol buffer 二进制格式。这些方法包括:

这些只是两个用于解析和序列化的选择。再次说明,可以查看 Message API reference 完整的列表。

Protocol Buffers 和面向对象设计

Protocol buffer 类通常只是纯粹的数据存储器(像 C++ 中的结构体);它们在对象模型中并不是一等公民。如果你想向生成的 protocol buffer 类中添加更丰富的行为,最好的方法就是在应用程序中对它进行封装。如果你无权控制 .proto 文件的设计的话,封装 protocol buffers 也是一个好主意(例如,你从另一个项目中重用一个 .proto 文件)。在那种情况下,你可以用封装类来设计接口,以更好地适应你的应用程序的特定环境:隐藏一些数据和方法,暴露一些便于使用的函数,等等。但是你绝对不要通过继承生成的类来添加行为。这样做的话,会破坏其内部机制,并且不是一个好的面向对象的实践。

写消息

现在我们尝试使用 protocol buffer 类。你的地址簿程序想要做的第一件事是将个人详细信息写入到地址簿文件。为了做到这一点,你需要创建、填充 protocol buffer 类实例,并且将它们写入到一个输出流output stream。

原文链接

时间: 2024-08-01 22:03:39

C++_程序员_Protocol_Buffers_基础指南的相关文章

黑马程序员_Java基础_接口

------- android培训.java培训.期待与您交流! ---------- 0.接口知识体系 Java接口的知识体系如下图所示,掌握下图中的所有知识就可精通接口. 1.接口概论 1)接口概念 接口是从多个相似类中抽象出来的规范,接口中不包含普通方法,所有方法都是抽象方法,接口不提供实现.接口体现的是规范和实现分离的哲学.规范和实现分离正是接口的好处,让软件系统的各个组件之间面向接口耦合,是一种松耦合的设计.接口定义的是多个类共同的公共行为规范,定义的是一组公用方法. 2)接口与抽象类

黑马程序员——Java基础---IO(下)

黑马程序员——Java基础---IO(下) ------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------ 一.概述 Java除了基本的字节流.字符流之外,还提供了File类.properties类.打印流.序列流等和输入输出相关的类,它们能够帮助我们更好的处理信息.下面将对它们进行简单的介绍. 一.正

黑马程序员_JAVA 基础加强学习笔记

一.面向对象 (一)继承  1.继承的好处: (1) 提高了代码的复用性. (2) 让类与类之间产生了关系,提供了另一个特征多态的前提. 注意: 子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();  如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数. 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数. 2.final特点

黑马程序员——Java基础---集合框架工具类

黑马程序员——Java基础<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! ------ 一.概述 Java为操作Set.List和Map提供了一系列工具类,主要有Collections和Arrays.这两个工具类的特点:类中的方法都是静态的,不需要创建对象,直接使用类名调用即可.Collections:是集合对象

黑马程序员_Java基础加强(下)

8.注解类 注解相当于一种标记,加了注解就等于打上了某种标记,没加就等于没打特殊标记,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记就去干什么事,标记可以加在包.类.字段.方法,方法的参数以及局部变量上. 注解的应用结构图: 为注解增加属性 定义基本类型的属性和应用属性 在注解类中增加String color(); @MyAnnotation(color = "red") 用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法 MyAnno

黑马程序员_Java基础加强(上)

1.静态导入 静态导入是jdk1.5版本以后出现的新特性,一般是指导入静态方法,如:import static java.lang.System.out 是指导入系统输出的静态方法. 例: import static java.lang.System.out //导入java.lang包下的System类的静态方法out public class StaticImport { public static void main(String[] args) { int x=1; x++; out.p

黑马程序员-正则表达式基础

正则表达式是一种描述字符串集的方法,它是以字符串集中各字符串的共有特征为依据的.正则表达式可以用于搜索.编辑或者是操作文本和数据.它超出了 Java 程序设计语言的标准语法,因此有必要去学习特定的语法来构建正则表达式.正则表达式的变化是复杂的,一旦你理解了它们是如何被构造的话,你就能解析或者构建任意的正则表达式了..正则表达式由一些普通字符和一些元字符组成. 普通字符包括大小写的字母和数字,而元字符则具有特殊的含义.在最简单的情况下,一个正则表达式看上去就是一个普通的查找串.例如,正则表达式"a

程序员装逼指南(转)

程序员嘛,外行人看起来已经是不可理解的奇怪生物了,自然也没必要跟他们再装逼 所以呢,如何对其他程序员装逼就是一门很有学问的事了 ---------------------------------------------------- 一.准备工作 "工欲善其事必先利其器." 1.电脑不一定要配置高,但是双屏是必须的,越大越好,能一个横屏一个竖屏更好.一个用来查资料,一个用来写代码.总之要显得信息量很大,效率很高. 2.椅子不一定要舒服,但是一定要可以半躺着. 3.大量的便签,各种的颜色

黑马程序员——java基础——多线程

 黑马程序员--java基础--多线程 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 进程:是一个正在执行中的程序.每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元.线程在控制着进程的执行.一个进程中至少有一个线程. 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区.自己的变量.