Google数据交换格式:ProtoBuf

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(123);
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信息是一小段逻辑记录,包含一系列的键值对。

例如:

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;
}

每个消息类型拥有一个或多个特定的数字字段,每个字段拥有一个名字和一个值类型。值类型可以是数字(整数或浮点)、布尔型、字符串、原始字节或者其他ProtocolBuffer类型,还允许数据结构的分级。你可以指定可选字段(optional),必选字段(required)和重复字段。

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

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("[email protected]");
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作为通信协议,你可以无须担心破坏现有代码的情况下扩展协议。

 

你可能感兴趣的:(Google数据交换格式:ProtoBuf)