Protocol Buffer

ProtocolBuffer是Google公司的一个开源项目,用于结构化数据串行化的灵活、高效、自动的方法,有如XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。

一个例子

比如有个电子商务的系统(假设用C++实现),其中的模块A需要发送大量的订单信息给模块B,通讯的方式使用socket。

假设订单包括如下属性:
--------------------------------
  时间:time(用整数表示)
  客户id:userid(用整数表示)
  交易金额:price(用浮点数表示)
  交易的描述:desc(用字符串表示)
--------------------------------

如果使用protobuf实现,首先要写一个proto文件(不妨叫Order.proto),在该文件中添加一个名为"Order"的message结构,用来描述通讯协议中的结构化数据。该文件的内容大致如下:

message Order
{
required int32 time = 1;
required int32 userid = 2;
required float price = 3;
optional string desc = 4;
}

然后,使用protobuf内置的编译器编译该proto。由于本例子的模块是C++,你可以通过protobuf编译器让它生成 C++语言的“订单包装类”(一般来说,一个message结构会生成一个包装类)。

然后你使用类似下面的代码来序列化/解析该订单包装类:

发送方:

Order order;
order.set_time(XXXX);
order.set_userid();
order.set_price(100.0f);
order.set_desc("a test order");
string sOrder;
order.SerailzeToString(&sOrder);

然后调用某种socket的通讯库把序列化之后的字符串sOrder发送出去;

接收方:

string sOrder;
// 先通过网络通讯库接收到数据,存放到某字符串sOrder
// ......
Order order;
if(order.ParseFromString(sOrder)){ // 解析该字符串
cout << "userid:" << order.userid() << endl
<< "desc:" << order.desc() << endl;
} else {
cerr << "parse error!" << endl;
}

有了这种代码生成机制,开发人员再也不用吭哧吭哧地编写那些协议解析的代码了。万一将来需求发生变更,要求给订单再增加一个“状态”的属性,那只需要在Order.proto文件中增加一行代码。对于发送方,只要增加一行设置状态的代码;对于接收方只要增加一行读取状态的代码。另外,如果通讯双方使用不同的编程语言来实现,使用这种机制可以有效确保两边的模块对于协议的处理是一致的。

从某种意义上讲,可以把proto文件看成是描述通讯协议的规格说明书(或者叫接口规范)。这种伎俩其实老早就有了,搞过微软的COM编程或者接触过CORBA的同学,应该都能从中看到IDL的影子。它们的思想是相通滴。

ProtoBuf支持向后兼容(backward compatible)和向前兼容(forward compatible):

  1. 向后兼容,比如说,当接收方升级了之后,它能够正确识别发送方发出的老版本的协议。由于老版本没有“状态”这个属性,在扩充协议时,可以考 虑把“状态”属性设置成非必填 的(optional),或者给“状态”属性设置一个缺省值;
  2. 向前兼容,比如说,当发送方升级了之后,接收方能够正常识别发送方发出的新版本的协议。这时候,新增加的“状态”属性会被忽略;

向后兼容和向前兼容有啥用捏?俺举个例子:当你维护一个很庞大的分布式系统时,由于你无法同时 升级所有 模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的向后兼容或向前兼容。

proto文件

如上面的例子,使用protobuf,首先需要在一个 .proto 文件中定义你需要做串行化的数据结构信息。每个ProtocolBuffer信息是一小段逻辑记录,包含一系列的键值对。

例如:

syntax = "proto3";

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 phone=4;
}

一旦你定义了自己的报文格式(message),你就可以运行ProtocolBuffer编译器,将你的 .proto 文件编译成特定语言的类。这些类提供了简单的方法访问每个字段(像是 query() 和 set_query() ),像是访问类的方法一样将结构串行化或反串行化。例如你可以选择C++语言,运行编译如上的协议文件生成类叫做 Person 。随后你就可以在应用中使用这个类来串行化的读取报文信息。你可以这么写代码:

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream.output("myfile",ios::out | ios::binary);
person.SerializeToOstream(&output);

然后,你可以读取报文中的数据:

fstream input("myfile",ios::in | ios:binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

你可以在不影响向后兼容的情况下随意给数据结构增加字段,旧有的数据会忽略新的字段。所以如果使用ProtocolBuffer作为通信协议,你可以无须担心破坏现有代码的情况下扩展协议。

当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
  • 对go来说,编译器会位每个消息类型生成了一个.pd.go文件。

protobuf3 消息定义

message由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式:

字段规则 | 数据类型 | 字段名称 | = | 字段编码值 | [字段默认值]

字段规则:

  • singular:格式良好的消息可以包含该字段中的零个或一个(但不超过一个);
  • repeated:此字段可以在格式良好的消息中重复任意次数(包括零),将保留重复值的顺序;

字段名称:

字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的。
protobuf建议字段的命名采用以下划线分割的驼峰式,例如 first_name 而不是firstName。

字段编码值:

编码值的取值范围为 1~2^32(4294967296)。

消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。

[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。

切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。

字段默认值:

当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于string,默认是一个空string
  • 对于bytes,默认是一个空的bytes
  • 对于bool,默认是false
  • 对于数值类型,默认是0
  • 对于枚举,默认是第一个定义的枚举值,必须为0;
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的;

注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。

对于可重复域的默认值是空(通常情况下是对应语言中空列表)。

另外:

  • message消息支持嵌套定义,消息可以包含另一个消息作为其字段,也可以在消息内定义一个新的消息;
  • proto定义文件支持import导入其它proto定义文件;
  • 每个proto文件指定一个package名称,对于java解析为java中的包。对于C++则解析为名称空间。

标量数值类型

.proto Type Notes C++  Java  Python  Go
double   double double float float64
float   float float float float32
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 int32 int int int32
uint32 使用变长编码 uint32 int int/long uint32
uint64 使用变长编码 uint64 long int/long uint64
sint32 使用变长编码,这些编码在负值时比int32高效的多 int32 int int int32
sint64 使用变长编码,有符号的整型值。编码时比通常的int64高效。 int64 long int/long int64
fixed32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 uint32 int int uint32
fixed64 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 uint64 long int/long uint64
sfixed32 总是4个字节 int32 int int int32
sfixed64 总是8个字节 int64 long int/long int64
bool   bool boolean bool bool
string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 string String str/unicode string
bytes 可能包含任意顺序的字节数据。 string ByteString str []byte

其中:

varint(type=0),动态类型

  1. 每个字节第一位表示有无后续字节,有为1,无为0,(双字节,低字节在前,高字节在后);
  2. 剩余7位倒序合并。
举例: 300 的二进制为 10 0101100

第一位:1(有后续) + 0101100

第二位:0(无后续) + 0000010

最终结果: 101011000000010

枚举类型

在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段:

message SearchRequest {
string query = ;
int32 page_number = ;
int32 result_per_page = ;
enum Corpus {
UNIVERSAL = ;
WEB = ;
IMAGES = ;
LOCAL = ;
NEWS = ;
PRODUCTS = ;
VIDEO = ;
}
Corpus corpus = ;
}

如你所见,Corpus枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:

  • 必须有有一个0值,我们可以用这个0值作为默认值。
  • 这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。

Google数据交换格式:ProtoBuf的更多相关文章

  1. 常用两种数据交换格式之XML和JSON的比较

    目前,在web开发领域,主要的数据交换格式有XML和JSON,对于XML相信每一个web developer都不会感到陌生: 相比之下,JSON可能对于一些新步入开发领域的新手会感到有些陌生,也可能你 ...

  2. XML和JSON两种数据交换格式的比较

    在web开发领域,主要的数据交换格式有XML和JSON,对于在 Ajax开发中,是选择XML还是JSON,一直存在着争议,个人还是比较倾向于JSON的.一般都输出Json不输出xml,原因就是因为 x ...

  3. Java 常见数据交换格式——xml、json、yaml

    目录 数据交换格式介绍 XML 使用DOM方式解析 使用SAX方式解析 使用DOM4J方式解析 使用JDOM方式解析 JSON 使用JSONObject方式将数据转换为JSON格式 利用JSONObj ...

  4. 数据交换格式XML和JSON对比

    1.简介: XML:extensible markup language,一种类似于HTML的语言,他没有预先定义的标签,使用DTD(document type definition)文档类型定义来组 ...

  5. JSON——IT技术人员都必须要了解的一种数据交换格式

    JSON作为目前Web主流的数据交换格式,是每个IT技术人员都必须要了解的一种数据交换格式.尤其是在Ajax和REST技术的大行其道的当今,JSON无疑成为了数据交换格式的首选! 今天大家就和猪哥一起 ...

  6. Atitit.常见软件 数据 交换格式 标准

    Atitit.常见软件 数据 交换格式 标准 1. 常见的数据格式txt ,doc ,pic,music ,vodio1 2. 通用格式json yaml phpstr1 3. 专用格式1 4. 用户 ...

  7. 在.NET使用JSON作为数据交换格式

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://zhoufoxcn.blog.51cto.com/792419/517093 我们 ...

  8. json 数据交换格式与java

    http://wiki.mbalib.com/wiki/数据交换 数据交换是指为了满足不同信息系统之间数据资源的共享需要,依据一定的原则,采取相应的技术,实现不同信息系统之间数据资源共享的过程. 数据 ...

  9. NDEF-NFC数据交换格式

    为实现NFC标签.NFC设备以及NFC设备之间的交互通信,NFC论坛(NFC FROUM)定义了称为NFC数据交换格式(NDEF)的通用数据格式.NDEF是轻量级的紧凑的二进制格式,可带有URL,vC ...

随机推荐

  1. Maven详解

    一.前言     以前做过的项目中,没有真正的使用过Maven,只知道其名声很大,其作用是用来管理jar 包的.最近一段时间在项目过程中使用Maven,用Maven构建的web项目,其项目结构只停留在 ...

  2. 使用c/c++扩展python

    用python脚本写应用比较方便,但有时候由于种种原因需要扩展python(比如给程序提供python接口等). 之前一直想整理下,今天终于坐下来把这件事情给做了,这里记录下,也方便我以后查阅. 说明 ...

  3. Event Handler

    在Event Handler中,有一种特殊的Event Handler,称之为Synchronizer或者Denormalizer,其作用就是为了同步“Query Database”.Query Da ...

  4. 使用bulkload向hbase中批量写入数据

    1.数据样式 写入之前,需要整理以下数据的格式,之后将数据保存到hdfs中,本例使用的样式如下(用tab分开): row1 N row2 M row3 B row4 V row5 N row6 M r ...

  5. Android快乐贪吃蛇游戏实战项目开发教程-02虚拟方向键(一)自定义控件概述

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.自定义控件简介 在本项目中,无论是游戏主区域还是虚拟方向键都是通过自定义控件 ...

  6. [No000096]程序员面试题集【上】

    对几家的面试题凭记忆做个总结,基本全部拿到offer,由于时间比较长,题目只写大体意思,然后给出自己当时的答案(不保证一定正确): abstract类不可以被实例化 蛋糕算法: 平面分割空间:(n-1 ...

  7. [LeetCode] Strobogrammatic Number III 对称数之三

    A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside ...

  8. anaconda

    python 集成环境 anaconda的使用 https://www.zhihu.com/question/35757251

  9. hihocoder-1453-Rikka with Tree

    #Hihocoder 1453 : Rikka with Tree 时间限制:10000ms 单点时限:1000ms 内存限制:256MB   source: https://hihocoder.co ...

  10. C /C++ 语言练习册

    /************************************** 整数对应 32 bit 二进制数串中数字1的个数 2016-10-24 liukun ***************** ...