google protocal buff

簡介

protobuf是google提供的一個開源序列化框架。主要應用於通訊協議,數據存儲中的結構化數據的序列化。它相似於XML,JSON這樣的數據表示語言,其最大的特色是基於二進制,所以比傳統的XML表示高效短小得多。雖然是二進制數據格式,但並無所以變得複雜,開發人員經過按照必定的語法定義結構化的消息格式,而後送給命令行工具,工具將自動生成相關的類,能夠支持java、c++、python等語言環境。經過將這些類包含在項目中,能夠很輕鬆的調用相關方法來完成業務消息的序列化與反序列化工做。html

安裝

下載地址:https://github.com/google/protobuf/releasesjava

==注意==: 若是在使用python時,用來編譯.proto文件的protoc的版本若是爲2.X,那麼可使用pip安裝的protobuf,可是若是使用的protoc的版本若是爲3.X,那麼須要卸載pip下載的protobuf,由於如今pip上的protobuf的版本還沒升級到3.X,爲2.X,版本不對應,會產生錯誤,相似於:python

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,而不是編譯它。linux

protobuf例子

咱們將使用的示例是一個很是簡單的「地址簿」應用程序,能夠從一個文件中讀寫人們的聯繫方式。地址簿中的每一個人都有一個名字,一個ID、一個電子郵件地址,和聯繫電話號碼。c++

爲了建立你的「地址簿」應用,你會用到一個.proto文件。這是一個很簡單的.proto文件定義:你能夠爲你想序列化的數據結構添加一條Message,而後在Message中爲每一個字段指定一個名稱和一個類型。如下是你想爲你的Message定義的.proto文件,addressbook.protogit

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。那就讓咱們看看文件中的每一個部分和看看它們到底是幹什麼的。github

.proto文件開頭是包的聲明,爲了幫助防止在不一樣的工程中命名衝突。在Python中,包一般由目錄結構決定的,因此這個由你的.proto文件定義的包,在你生成你代碼中是沒有效果的。可是,你應該堅持聲明這條語句,爲了在protocol Buffers的命名空間中防止名子的衝突,就像其它非Python的語言那樣。shell

message

  1. 類型 一個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或最高的數字留給不經常使用和可選擇的元素++。每一個重複的字段裏的元素要求從新編碼它的標籤號碼,因此重複的字段特別適合使用這種優化。windows

  2. 修飾語 每一個字段必定要被如下的修飾語修飾:數組

  • 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弊大於利;他們更願意只用optionalrepeated。可是,這一觀點並不廣泛。

Compiling Your Protocol Buffers

如今你有了本身的.proto文件,下一件你須要去作的事就是生成你須要讀寫AddressBook(還帶有Person和PhoneNumber)Message的類。爲了完成這件工做,你須要運行protocol buffer 編譯器protoc去編譯你的.proto文件:

  1. 若是你沒有安裝編譯器,請查看本文檔++安裝++章節。
  2. 如今運行編譯器,指定源目錄(你的應用程序源碼目錄——若是你不提供這個目錄,默認就是當前目錄),目標目錄(你的應用程序編譯後生成的代碼的目錄;一般用$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:

相關文章
相關標籤/搜索