所在的位置: java >> 主要特点 >> C程序员ProtocolBuffer

C程序员ProtocolBuffer

本文导航

-为什么使用ProtocolBuffers02%

-在哪可以找到示例代码09%

-定义你的协议格式10%

-编译你的ProtocolBuffers29%

-ProtocolBufferAPI33%

-枚举和嵌套类46%

-标准的消息方法49%

-解析和序列化52%

-写消息59%

-读取消息74%

-扩展ProtocolBuffer83%

-优化技巧90%

-高级用法94%

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

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

使用protocolbuffer编译器

使用C++protocolbufferAPI读写消息

这不是一个关于在C++中使用protocolbuffers的全面指南。要获取更详细的信息,请参考ProtocolBufferLanguageGuide[1]和EncodingReference[2]。

为什么使用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”文件夹。可在这里[3]下载代码。

定义你的协议格式

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

packagetutorial;

messagePerson{

requiredstringname=1;

requiredint32id=2;

optionalstringemail=3;

enumPhoneType{

MOBILE=0;

HOME=1;

WORK=2;

}

messagePhoneNumber{

requiredstringnumber=1;

optionalPhoneTypetype=2[default=HOME];

}

repeatedPhoneNumberphone=4;

}

messageAddressBook{

repeatedPersonperson=1;

}

如你所见,其语法类似于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[4]里面。不要在这里面查找与类继承相似的特性,因为protocolbuffers不会做这些。

required是永久性的

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

编译你的ProtocolBuffers

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

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

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

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中指定所有消息的类。







































北京白癜风哪家医院最好
一般治疗白癜风要多少钱



转载请注明:http://www.jiaju1314.com/zytd/zytd/5015.html