Protobuf使用基础教程(C++)

本文介绍了C++程序员如何使用Protocol Buffers,通过一个简单的地址簿应用展示了如何定义message、编译protobuf、使用API读写message。Protocol Buffers提供了一种高效、灵活的数据序列化方法,相比XML和其他方式,它具有空间效率和性能优势。
摘要由CSDN通过智能技术生成

本文是一篇译文,原文地址为:https://developers.google.com/protocol-buffers/docs/cpptutorial

Protocol Buffer Basics: C++

本教程为 C++ 程序员如何使用 protocol buffers 做一个基本介绍。通过创建一个简单的示例应用程序,我们可以学会:

  • 如何在一个 .proto 文件中定义 message
  • 如何使用 protocol buffer 编译器
  • 如何使用 C++ protocol buffer 的 API 读写 message

这不是一篇 protocol 的综合性的C++教程。如果想获取更详细的参考信息,请参阅我的另两篇博文 Protobuf语法指南(proto2)Protobuf语法指南(proto3) 以及 C++ APIC++ 生成代码Protocol Buffer 编码

为什么需要 Protocol Buffers?

我们将要使用的示例是一个非常简单的 “地址簿” 应用程序,可以在文件中读写联系人的详细信息。地址簿中的每个人都有姓名、ID、电子邮件地址和联系电话。

你该如何序列化和反序列化如上结构的数据呢?这里有几种解决方案:

  • 可以以二进制形式发送/保存原始内存中数据结构。随着时间的推移,这是一种脆弱的方法,因为接收/读取代码必须使用完全相同的内存布局、字节顺序等进行编译。此外,由于文件以原始格式累积数据,并且解析该格式的代码分散各处,因此很难扩展格式。
  • 你可以发明一种特殊的方法将数据项编码为单个字符串 - 例如将 4 个整数编码为 “12:3:-23:67”。这是一种简单而灵活的方法,虽然它确实需要编写一次性编码和解析的代码,并且解析会产生一些小的运行时成本。但这非常适合非常简单的数据的编码。
  • 将数据序列化为 XML。这种方法非常有吸引力,因为 XML(差不多)是人类可读的,并且有许多语言的绑定库。如果你想与其他应用程序/项目共享数据,这可能是一个不错的选择。然而,众所周知 XML 需要更多的空间,并且编码/解码 XML 会对应用程序造成巨大的性能损失。此外,解析 XML DOM 树通常比解析类中的简单字段要复杂得多。

而 Protocol buffers 是灵活,高效,自动化的解决方案。采用 protocol buffers,你可以写一个 .proto 文件描述你想要读取的数据的结构。由此, protocol buffer 编译器将创建一个类,该类使用有效的二进制格式实现 protocol buffer 数据的自动编码和解析。生成的类为构成 protocol buffer 的字段提供 getter 和 setter,并负责读写 protocol buffer 单元。更重要的是,protocol buffer 支持格式扩展,使得代码仍然可以读取用旧格式编码的数据。

示例代码

示例代码包含在源代码包中的 “examples” 目录下。

定义protocol 格式

要创建地址簿应用程序,你需要从 .proto 文件开始。.proto 文件中的定义很简单:为要序列化的每个数据结构添加 message 定义,然后为 message 中的每个字段指定名称和类型。下面就是定义相关 message 的 addressbook.proto 文件 。

syntax = "proto2";

package tutorial;

message Person {
   
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
   
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
   
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
   
  repeated Person people = 1;
}

可以看出,其语法类似于 C++ 或 Java。让我们浏览文件的每个部分,看看它们的作用。

.proto 文件以 package 声明开头,这有助于解决不同项目之间的命名冲突。在 C++ 中,生成的类将放在与package 的名字匹配的 namespace 中。

接下来,你将看到相关的 message 定义。message 只是包含一组类型字段的集合。许多标准的简单数据类型都可用作字段类型,包括 bool、int32、float、double 和 string。你还可以使用其他 message 类型作为字段类型在消息中添加更多结构 - 在上面的示例中,Person 包含 PhoneNumber message ,而 AddressBook 包含 Person message。你甚至可以定义嵌套在其他 message 中的 message 类型 -​​ 如你所见,PhoneNumber 类型在 Person 中定义。如果你希望其中一个字段具有预定义值列表中的值,你还可以定义枚举类型 - 此处我们指定PhoneType,它的值可以是 MOBILE,HOME 或 WORK 之一。

每个元素中的 “=1”,"=2" 表示该字段在二进制编码中使用的唯一 “标识”。[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。repeated 字段中的每个(string等类型)元素都需要重新编码 Tag,因此 repeated 字段特别适合使用此优化。

必须使用以下修饰符之一修饰每个字段:

  • required: 必须提供该字段的值,否则该消息将被视为“未初始化”。如果是在调试模式下编译 libprotobuf,则序列化一个未初始化的 message 将将导致断言失败。在优化的构建中,将跳过检查并始终写入消息。但是,解析未初始化的消息将始终失败(通过从解析方法返回 false)。除此之外,required 字段的行为与 optional 字段完全相同。
  • optional: 可以设置也可以不设置该字段。如果未设置optional字段的值,则使用默认值。对于简单类型,你可以指定自己的默认值,就像我们在示例中为电话号码类型所做的那样。否则,使用系统默认值:数字类型为 0,字符串为空字符串,bool 为 false。对于嵌入 message,默认值始终是消息的 “默认实例” 或 “原型”,其中没有设置任何字段。调用访问器以获取尚未显式设置的 optional(或 required)字段的值始终返回该字段的默认值。
  • repeated: 该字段可以重复任意次数(包括零次)。重复值的顺序将保留在 protocol buffer 中。可以将 repeated 字段视为动态大小的数组。

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

编译Protocol Buffers

既然你已经有了一个 .proto 文件,那么你需要做的下一件事就是生成你需要读写AddressBook(以及 Person 和 PhoneNumber ) message 所需的类。为此,你需要在 .proto 上运行 protocol buffer 编译器 protoc:

如果尚未安装编译器,请 下载 protocol buffer的源码 并按照 README 文件中的说明进行操作。

现在运行编译器,指定源目录(应用程序的源代码所在的位置 - 如果不提​​供值,则使用当前目录),目标目录(你希望生成代码的目标目录;通常与源目录 $SRC_DIR 相同),以及 .proto 的路径。在这种情况下,你可以执行如下命令:

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

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

这将在指定的目标目录中生成以下文件:

The Protocol Buffer API

让我们看看一些生成的代码,看看编译器为你创建了哪些类和函数。如果你查看 addressbook.pb.h,你会发现你在 addressbook.proto 中指定的每条 message 都有一个对应的类。仔细观察 Person 类,你可以看到编译器已为每个字段生成了访问器。例如,对于 name ,id,email 和 phone 字段,你可以使用以下方法:

  // name
  inline 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
  • 14
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值