简介
protobuf是google提供的一个开源序列化框架。主要应用于通信协议,数据存储中的结构化数据的序列化。它类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的XML表示高效短小得多。虽然是二进制数据格式,但并没有因此变得复杂,开发人员通过按照一定的语法定义结构化的消息格式,然后送给命令行工具,工具将自动生成相关的类,可以支持java、c++、python等语言环境。通过将这些类包含在项目中,可以很轻松的调用相关方法来完成业务消息的序列化与反序列化工作。
安装
下载地址:https://github.com/google/protobuf/releases
==注意==: 如果在使用python时,用来编译
.proto
文件的protoc
的版本如果为2.X,那么可以使用pip安装的protobuf,但是如果使用的protoc
的版本如果为3.X,那么需要卸载pip下载的protobuf,因为现在pip上的protobuf的版本还没升级到3.X,为2.X,版本不对应,会产生错误,类似于:
serialized_pb=b'\n\ntest.proto\x12\x02lm\"2\n\nhelloworld\x12\n\n\x02id\x18\x01 \x02(\x05\x12\x0b\n\x03str\x18\x02 \x02(\t\x12\x0b\n\x03opt\x18\x03 \x01(\x05'
TypeError: __init__() got an unexpected keyword argument 'syntax'
linux
./autogen.sh
./configure
make
make check
make install
windows
直接下载编译好的realeases包。方便简单。我们的主要目标是学会google protocal buffer,而不是编译它。
protobuf例子
我们将使用的示例是一个非常简单的“地址簿”应用程序,可以从一个文件中读写人们的联系方式。地址簿中的每个人都有一个名字,一个ID、一个电子邮件地址,和联系电话号码。
为了创建你的“地址簿”应用,你会用到一个.proto文件。这是一个很简单的.proto
文件定义:你可以为你想序列化的数据结构添加一条Message,然后在Message中为每个字段指定一个名称和一个类型。以下是你想为你的Message定义的.proto
文件,addressbook.proto
。
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];
}
required PhoneNumber phone = 4;
}
message AddressBook{
required Person person = 1;
}
在语法上很像C++和Java。那就让我们看看文件中的每个部分和看看它们究竟是干什么的。
包
.proto文件开头是包的声明,为了帮助防止在不同的工程中命名冲突。在Python中,包通常由目录结构决定的,所以这个由你的.proto文件定义的包,在你生成你代码中是没有效果的。但是,你应该坚持声明这条语句,为了在protocol Buffers的命名空间中防止名子的冲突,就像其它非Python的语言那样。
message
-
类型 一个Message是一个包含一组类型字段的集合。 有许多简单的标准的数据类型可以用在类型字段中,包括bool,int32,float,double和string。你也可以使用更加多的结构来定义你的Message。 例如用其它Message类型当作类型字段-在上面的例子PersonMessage中就包含了PhoneNumberMessage,还有AddressBookMessage包含PersonMessage。你也可以定义Message嵌入其它的Message——就如你所见到的那样,PhoneNumber类型就是在Person类型中定义的。你也可以定义一个枚举类型,如果你想你其中一个字段有一个预设的类型列表——在这里,你可以将你的电话号码列举为MOBILE,HOME或者WORK。 枚举类型的那个“=1”,“=2”标记每个元素的识别,作为二进制编码中字段的唯一的标签。标签要求数字1-15比更高的数字少一个字节编码,所以,作为最优化的方案,你可以决定++对常用的和要重复使用的元素使用这些标签(1-15),把16或最高的数字留给不常用和可选择的元素++。每个重复的字段里的元素要求重新编码它的标签号码,所以重复的字段特别适合使用这种优化。
-
修饰语 每个字段一定要被以下的修饰语修饰:
- required:一定要提供一个值给这个字段,否则这条Message会被认为“没有初始化”。序列化一列没有初始化的Message会出现异常。 解析一条没有初始化的Message会失败。除此而外,这个required字段的行为更类似于一个optional字段。
- optional:这个字段可以设置也可以不设置 。如果一个可选字段没有设置值,会用缺省的值。简单来说,你可以指定自己的默认值,就像我们在例子中对phone number类型所做的。另外,系统会缺省这样做:0给整数类型,空串给字符串类型,false给布尔类型。对于嵌入的Message,缺省的值通常会是“默认实例”或“原型”,对那些没有设置字段的Message。调用存取器获得一个可选的(或要求)字段的值,那些通常什么明确给出值的字段总是返回该字段的默认值。
- repeated:这个字段会重复几次(包括0)。重复值的顺序保存在protocol buffer中。重复的字段会被认为是动态的数组。
==Required Is Forever== 你应该非常小心地把字段标记为
required
! 如果在某一时刻你希望停止写或发送一个required
字段,那就把不确定的字段更改为一个optinal
的字段——老版本的解释器会认为没有这个字段Message是不完整的,而且可能会无意中拒绝或删除它们。你应该考虑为你的buffer编写特定于应用程序的自定义验证例程。一些来自Google的软件工程师有这样的结论:使用required弊大于利;他们更愿意只用optional
和repeated
。但是,这一观点并不普遍。
Compiling Your Protocol Buffers
现在你有了自己的.proto文件,下一件你需要去做的事就是生成你需要读写AddressBook(还带有Person和PhoneNumber)Message的类。为了完成这件工作,你需要运行protocol buffer 编译器protoc去编译你的.proto文件:
- 如果你没有安装编译器,请查看本文档++安装++章节。
- 现在运行编译器,指定源目录(你的应用程序源码目录——如果你不提供这个目录,默认就是当前目录),目标目录(你的应用程序编译后生成的代码的目录;通常用$SRC_DIR),还有你.proto文件的目录路径。
protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto
因为你想生成Python的类,所以你要用--python_out
选项——也有类似的选项支持其它语言。 这样addressbook_pb2.py
就会生成在你指定的目标目录中。
生成的文件如下:
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: addressbook.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='addressbook.proto',
package='tutorial',
syntax='proto2',
serialized_pb=_b('\n\x11\x61\x64\x64ressbook.proto\x12\x08tutorial\"\xda\x01\n\x06Person\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\n\n\x02id\x18\x02 \x02(\x05\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12+\n\x05phone\x18\x04 \x02(\x0b\x32\x1c.tutorial.Person.PhoneNumber\x1aM\n\x0bPhoneNumber\x12\x0e\n\x06number\x18\x01 \x02(\t\x12.\n\x04type\x18\x02 \x01(\x0e\x32\x1a.tutorial.Person.PhoneType:\x04HOME\"+\n\tPhoneType\x12\n\n\x06MOBILE\x10\x00\x12\x08\n\x04HOME\x10\x01\x12\x08\n\x04WORK\x10\x02\"/\n\x0b\x41\x64\x64ressBook\x12 \n\x06person\x18\x01 \x02(\x0b\x32\x10.tutorial.Person')
)
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
_PERSON_PHONETYPE = _descriptor.EnumDescriptor(
name='PhoneType',
full_name='tutorial.Person.PhoneType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='MOBILE', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='HOME', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='WORK', index=2, number=2,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=207,
serialized_end=250,
)
_sym_db.RegisterEnumDescriptor(_PERSON_PHONETYPE)
_PERSON_PHONENUMBER = _descriptor.Descriptor(
name='PhoneNumber',
full_name='tutorial.Person.PhoneNumber',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='number', full_name='tutorial.Person.PhoneNumber.number', index=0,
number=1, type=9, cpp_type=9, label=2,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='type', full_name='tutorial.Person.PhoneNumber.type', index=1,
number=2, type=14, cpp_type=8, label=1,
has_default_value=True, default_value=1,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=128,
serialized_end=205,
)
_PERSON = _descriptor.Descriptor(
name='Person',
full_name='tutorial.Person',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='name', full_name='tutorial.Person.name', index=0,
number=1, type=9, cpp_type=9, label=2,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='id', full_name='tutorial.Person.id', index=1,
number=2, type=5, cpp_type=1, label=2,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='email', full_name='tutorial.Person.email', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
_descriptor.FieldDescriptor(
name='phone', full_name='tutorial.Person.phone', index=3,
number=4, type=11, cpp_type=10, label=2,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[_PERSON_PHONENUMBER, ],
enum_types=[
_PERSON_PHONETYPE,
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=32,
serialized_end=250,
)
_ADDRESSBOOK = _descriptor.Descriptor(
name='AddressBook',
full_name='tutorial.AddressBook',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='person', full_name='tutorial.AddressBook.person', index=0,
number=1, type=11, cpp_type=10, label=2,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto2',
extension_ranges=[],
oneofs=[
],
serialized_start=252,
serialized_end=299,
)
_PERSON_PHONENUMBER.fields_by_name['type'].enum_type = _PERSON_PHONETYPE
_PERSON_PHONENUMBER.containing_type = _PERSON
_PERSON.fields_by_name['phone'].message_type = _PERSON_PHONENUMBER
_PERSON_PHONETYPE.containing_type = _PERSON
_ADDRESSBOOK.fields_by_name['person'].message_type = _PERSON
DESCRIPTOR.message_types_by_name['Person'] = _PERSON
DESCRIPTOR.message_types_by_name['AddressBook'] = _ADDRESSBOOK
Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), dict(
PhoneNumber = _reflection.GeneratedProtocolMessageType('PhoneNumber', (_message.Message,), dict(
DESCRIPTOR = _PERSON_PHONENUMBER,
__module__ = 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:tutorial.Person.PhoneNumber)
))
,
DESCRIPTOR = _PERSON,
__module__ = 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:tutorial.Person)
))
_sym_db.RegisterMessage(Person)
_sym_db.RegisterMessage(Person.PhoneNumber)
AddressBook = _reflection.GeneratedProtocolMessageType('AddressBook', (_message.Message,), dict(
DESCRIPTOR = _ADDRESSBOOK,
__module__ = 'addressbook_pb2'
# @@protoc_insertion_point(class_scope:tutorial.AddressBook)
))
_sym_db.RegisterMessage(AddressBook)
# @@protoc_insertion_point(module_scope)
解析和序列化
核心方法:
SerializeToString
:序列化消息并返回字符串;字符串是二进制形式。ParseFormatString
:从字符串的消息中解析出结构化的消息。
#! /usr/bin/python
# coding:utf-8
import addressbook_pb2
__author__ = 'hgf'
def write():
person = addressbook_pb2.Person()
person.name = "hgf"
person.id = 1
person.email="hgf@bupt.edu.cn"
phone = person.phone
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME
handle = open('test.txt','wb')
handle.write(person.SerializeToString())
handle.flush()
handle.close()
def read():
person = addressbook_pb2.Person()
f = open("test.txt",'rb')
person.ParseFromString(f.read())
f.close()
print person.id
print person.name
print person.email
print person.phone.number
print person.phone.type
#write()
read()
==说明==:我的google protocol buffer 的版本是3.X的,官网上嵌套使用message的方式好像是
phone = person.phone.add()
,但是现在并没有方法``
生成文件的截图:
参考文章
在Python中使用protocol buffers参考指南 Protocol Buffer技术详解(Java实例) Google Protocol Buffers 入门-java Protocol Buffers(protobuf)在Java开发中使用 Google Protocol Buffers介绍和总结 Google Protocol Buffer 的使用和原理
{贺广福}(heguangfu)(tm) @2015-10-7 :laughing: