C++程序员Protocol Buffers基础指南.docx

上传人:b****8 文档编号:28443956 上传时间:2023-07-13 格式:DOCX 页数:17 大小:170.86KB
下载 相关 举报
C++程序员Protocol Buffers基础指南.docx_第1页
第1页 / 共17页
C++程序员Protocol Buffers基础指南.docx_第2页
第2页 / 共17页
C++程序员Protocol Buffers基础指南.docx_第3页
第3页 / 共17页
C++程序员Protocol Buffers基础指南.docx_第4页
第4页 / 共17页
C++程序员Protocol Buffers基础指南.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

C++程序员Protocol Buffers基础指南.docx

《C++程序员Protocol Buffers基础指南.docx》由会员分享,可在线阅读,更多相关《C++程序员Protocol Buffers基础指南.docx(17页珍藏版)》请在冰豆网上搜索。

C++程序员Protocol Buffers基础指南.docx

C++程序员ProtocolBuffers基础指南

C++程序员ProtocolBuffers基础指南

这篇教程提供了一个面向C++程序员关于protocolbuffers的基础介绍。

使用Protocolbuffers,你需要写一个.proto说明,用于描述你所希望存储的数据结构。

利用.proto文件,protocolbuffer编译器可以创建一个类,用于实现对高效的二进制格式的protocolbuffer数据的自动化编码和解码。

作者:

佚名来源:

segmentfault|2016-11-0720:

43

 收藏

  分享

这篇教程提供了一个面向C++程序员关于protocolbuffers的基础介绍。

通过创建一个简单的示例应用程序,它将向我们展示:

∙在.proto文件中定义消息格式

∙使用protocolbuffer编译器

∙使用C++protocolbufferAPI读写消息

这不是一个关于在C++中使用protocolbuffers的全面指南。

要获取更详细的信息,请参考ProtocolBufferLanguageGuide和EncodingReference。

为什么使用ProtocolBuffers

我们接下来要使用的例子是一个非常简单的"地址簿"应用程序,它能从文件中读取联系人详细信息。

地址簿中的每一个人都有一个名字、ID、邮件地址和联系电话。

如何序列化和获取结构化的数据?

这里有几种解决方案:

∙以二进制形式发送/接收原生的内存数据结构。

通常,这是一种脆弱的方法,因为接收/读取代码必须基于完全相同的内存布局、大小端等环境进行编译。

同时,当文件增加时,原始格式数据会随着与该格式相关的软件而迅速扩散,这将导致很难扩展文件格式。

∙你可以创造一种ad-hoc方法,将数据项编码为一个字符串——比如将4个整数编码为12:

3:

-23:

67。

虽然它需要编写一次性的编码和解码代码且解码需要耗费一点运行时成本,但这是一种简单灵活的方法。

这最适合编码非常简单的数据。

∙序列化数据为XML。

这种方法是非常吸引人的,因为XML是一种适合人阅读的格式,并且有为许多语言开发的库。

如果你想与其他程序和项目共享数据,这可能是一种不错的选择。

然而,众所周知,XML是空间密集型的,且在编码和解码时,它对程序会造成巨大的性能损失。

同时,使用XMLDOM树被认为比操作一个类的简单字段更加复杂。

Protocolbuffers是针对这个问题的一种灵活、高效、自动化的解决方案。

使用Protocolbuffers,你需要写一个.proto说明,用于描述你所希望存储的数据结构。

利用.proto文件,protocolbuffer编译器可以创建一个类,用于实现对高效的二进制格式的protocolbuffer数据的自动化编码和解码。

产生的类提供了构造protocolbuffer的字段的getters和setters,并且作为一个单元来处理读写protocolbuffer的细节。

重要的是,protocolbuffer格式支持格式的扩展,代码仍然可以读取以旧格式编码的数据。

在哪可以找到示例代码

示例代码被包含于源代码包,位于“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)。

消息只是一个包含一系列类型字段的集合。

大多标准的简单数据类型是可以作为字段类型的,包括bool、int32、float、double和string。

你也可以通过使用其他消息类型作为字段类型,将更多的数据结构添加到你的消息中——在以上的示例,Person消息包含了PhoneNumber消息,同时AddressBook消息包含Person消息。

你甚至可以定义嵌套在其他消息内的消息类型——如你所见,PhoneNumber类型定义于Person内部。

如果你想要其中某一个字段的值是预定义值列表中的某个值,你也可以定义enum类型——这儿你可以指定一个电话号码是MOBILE、HOME或WORK中的某一个。

每一个元素上的=1、=2标记确定了用于二进制编码的唯一“标签”(tag)。

标签数字1-15的编码比更大的数字少需要一个字节,因此作为一种优化,你可以将这些标签用于经常使用的元素或repeated元素,剩下16以及更高的标签用于非经常使用的元素或optional元素。

每一个repeated字段的元素需要重新编码标签数字,因此repeated字段适合于使用这种优化手段。

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

∙required:

必须提供该字段的值,否则消息会被认为是“未初始化的”(uninitialized)。

如果libprotobuf以调试模式编译,序列化未初始化的消息将引起一个断言失败。

以优化形式构建,将会跳过检查,并且无论如何都会写入该消息。

然而,解析未初始化的消息总是会失败(通过parse方法返回false)。

除此之外,一个required字段的表现与optional字段完全一样。

∙optional:

字段可能会被设置,也可能不会。

如果一个optional字段没被设置,它将使用默认值。

对于简单类型,你可以指定你自己的默认值,正如例子中我们对电话号码的type一样,否则使用系统默认值:

数字类型为0、字符串为空字符串、布尔值为false。

对于嵌套消息,默认值总为消息的“默认实例”或“原型”,它的所有字段都没被设置。

调用accessor来获取一个没有显式设置的optional(或required)字段的值总是返回字段的默认值。

∙repeated:

字段可以重复任意次数(包括0次)。

repeated值的顺序会被保存于protocolbuffer。

可以将repeated字段想象为动态大小的数组。

你可以查找关于编写.proto文件的完整指导——包括所有可能的字段类型——在ProtocolBufferLanguageGuide里面。

不要在这里面查找与类继承相似的特性,因为protocolbuffers不会做这些。

required是永久性的

在把一个字段标识为required的时候,你应该特别小心。

如果在某些情况下你不想写入或者发送一个required的字段,那么将该字段更改为optional可能会遇到问题——旧版本的读者(LCTT译注:

即读取、解析旧版本ProtocolBuffer消息的一方)会认为不含该字段的消息是不完整的,从而有可能会拒绝解析。

在这种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。

Google的一些工程师得出了一个结论:

使用required弊多于利;他们更愿意使用optional和repeated而不是required。

当然,这个观点并不具有普遍性。

编译你的ProtocolBuffers

既然你有了一个.proto,那你需要做的下一件事就是生成一个将用于读写AddressBook消息的类(从而包括Person和PhoneNumber)。

为了做到这样,你需要在你的.proto上运行protocolbuffer编译器protoc:

1.如果你没有安装编译器,请下载这个包,并按照README中的指令进行安装。

2.现在运行编译器,指定源目录(你的应用程序源代码位于哪里——如果你没有提供任何值,将使用当前目录)、目标目录(你想要生成的代码放在哪里;常与$SRC_DIR相同),以及你的.proto路径。

在此示例中:

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

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

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

∙addressbook.pb.h,声明你生成类的头文件。

∙addressbook.pb.cc,包含你的类的实现。

ProtocolBufferAPI

让我们看看生成的一些代码,了解一下编译器为你创建了什么类和函数。

如果你查看addressbook.pb.h,你可以看到有一个在addressbook.proto中指定所有消息的类。

关注Person类,可以看到编译器为每个字段生成了读写函数(accessors)。

例如,对于name、id、email和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字段的方法,你可以看到:

∙检查repeated字段的_size(也就是说,与Person相关的电话号码的个数)

∙使用下标取得特定的电话号码

∙更新特定下标的电话号码

∙添加新的电话号码到消息中,之后你便可以编辑。

(repeated标量类型有一个add_方法,用于传入新的值)

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

枚举和嵌套类

与.proto的枚举相对应,生成的代码包含了一个PhoneType枚举。

你可以通过Person:

:

PhoneType引用这个类型,通过Person:

:

MOBILE、Person:

:

HOME和Person:

:

WORK引用它的值。

(实现细节有点复杂,但是你无须了解它们而可以直接使用)

编译器也生成了一个Person:

:

PhoneNumber的嵌套类。

如果你查看代码,你可以发现真正的类型为Person_PhoneNumber,但它通过在Person内部使用typedef定义,使你可以把Person_PhoneNumber当成嵌套类。

唯一产生影响的一个例子是,如果你想要在其他文件前置声明该类——在C++中你不能前置声明嵌套类,但是你可以前置声明Person_PhoneNumber。

标准的消息方法

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

∙boolIsInitialized()const;:

检查是否所有required字段已经被设置。

∙stringDebugString()const;:

返回人类可读的消息表示,对调试特别有用。

∙voidCopyFrom(constPerson&from);:

使用给定的值重写消息。

∙voidClear();:

清除所有元素为空的状态。

上面这些方法以及下一节要讲的I/O方法实现了被所有C++protocolbuffer类共享的消息(Message)接口。

为了获取更多信息,请查看completeAPIdocumentationforMessage。

解析和序列化

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

这些方法包括:

∙boolSerializeToString(string*output)const;:

序列化消息并将消息字节数据存储在给定的字符串中。

注意,字节数据是二进制格式的,而不是文本格式;我们只使用string类作为合适的容器。

∙boolParseFromString(conststring&data);:

从给定的字符创解析消息。

∙boolSerializeToOstream(ostream*output)const;:

将消息写到给定的C++ostream。

∙boolParseFromIstream(istream*input);:

从给定的C++istream解析消息。

这些只是两个用于解析和序列化的选择。

再次说明,可以查看MessageAPIreference完整的列表。

ProtocolBuffers和面向对象设计

Protocolbuffer类通常只是纯粹的数据存储器(像C++中的结构体);它们在对象模型中并不是一等公民。

如果你想向生成的protocolbuffer类中添加更丰富的行为,最好的方法就是在应用程序中对它进行封装。

如果你无权控制.proto文件的设计的话,封装protocolbuffers也是一个好主意(例如,你从另一个项目中重用一个.proto文件)。

在那种情况下,你可以用封装类来设计接口,以更好地适应你的应用程序的特定环境:

隐藏一些数据和方法,暴露一些便于使用的函数,等等。

但是你绝对不要通过继承生成的类来添加行为。

这样做的话,会破坏其内部机制,并且不是一个好的面向对象的实践。

写消息

现在我们尝试使用protocolbuffer类。

你的地址簿程序想要做的第一件事是将个人详细信息写入到地址簿文件。

为了做到这一点,你需要创建、填充protocolbuffer类实例,并且将它们写入到一个输出流(outputstream)。

这里的程序可以从文件读取AddressBook,根据用户输入,将新Person添加到AddressBook,并且再次将新的AddressBook写回文件。

这部分直接调用或引用protocolbuffer类的代码会以“//pb”标出。

1.#include  

2.#include  

3.#include  

4.#include "addressbook.pb.h" // pb 

5.using namespace std; 

6.// This function fills in a Person message based on user input. 

7.void PromptForAddress(tutorial:

:

Person* person) { 

8.  cout << "Enter person ID number:

 "; 

9.  int id; 

10.  cin >> id; 

11.  person->set_id(id);   // pb 

12.  cin.ignore(256, '\n'); 

13.  cout << "Enter name:

 "; 

14.  getline(cin, *person->mutable_name());    // pb 

15.  cout << "Enter email address (blank for none):

 "; 

16.  string email; 

17.  getline(cin, email); 

18.  if (!

email.empty()) { // pb 

19.    person->set_email(email);   // pb 

20.  } 

21.  while (true) { 

22.    cout << "Enter a phone number (or leave blank to finish):

 "; 

23.    string number; 

24.    getline(cin, number); 

25.    if (number.empty()) { 

26.      break; 

27.    } 

28.    tutorial:

:

Person:

:

PhoneNumber* phone_number = person->add_phone();  //pb 

29.    phone_number->set_number(number);   // pb 

30.    cout << "Is this a mobile, home, or work phone?

 "; 

31.    string type; 

32.    getline(cin, type); 

33.    if (type == "mobile") { 

34.      phone_number->set_type(tutorial:

:

Person:

:

MOBILE); // pb 

35.    } else if (type == "home") { 

36.      phone_number->set_type(tutorial:

:

Person:

:

HOME);   // pb 

37.    } else if (type == "work") { 

38.      phone_number->set_type(tutorial:

:

Person:

:

WORK);   // pb 

39.    } else { 

40.      cout << "Unknown phone type.  Using default." << endl; 

41.    } 

42.  } 

43.} 

44.// Main function:

  Reads the entire address book from a file, 

45.//   adds one person based on user input, then writes it back out to the same 

46.//   file. 

47.int main(int argc, char* argv[]) { 

48.  // Verify that the v

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 求职职场 > 简历

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1