Protocol Buffers(简称ProtoBuf)是Google公司开发的一种与语言和平台无关的、可扩展的、序列化结构数据的方法,可用于(数据)通信协议、数据存储等。用户可以利用ProtoBuf定义数据的结构,然后使用特殊生成的源代码轻松地在各种数据流中使用各种语言来编写和读取结构数据,甚至还可以在不破坏由旧数据结构编译的已部署程序的基础上更新数据结构。ProtoBuf目前有两个版本,分别是proto2和proto3,其中最新版本的proto3提供了对C++、Java、Python、Object-C、C#、Ruby、Go、PHP、Dart、Javascript等多种语言的支持。

ProtoBuf开源地址:https://github.com/protocolbuffers/protobuf

1 序列化与反序列化基础

1.1 序列化与反序列化定义

  • 序列化:将对象转换为字节序列的过程称为对象的序列化;
  • 反序列化:将字节序列恢复为对象的过程称为对象的反序列化;

1.2 什么情况下用到序列化

  • 当你想要把内存中的对象状态保存到一个文件中或者数据库中的时候;
  • 当你想要用套接字在网络上传送对象的时候;

1.3 实现序列化的协议

主流序列化协议:json、xml、ProtoBuf。

2 ProtoBuf的优缺点及适用场景

2.1 ProtoBuf优点

  • 语言无关、平台无关。即 ProtoBuf 支持 C++、Java、Python 等多种语言,支持多个平台。
  • 高效。 序列化后体积相比json和XML很小,而且序列化反序列化速度很快。压缩比高 、解压缩速度更快。
  • 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

2.2 ProtoBuf缺点

  • 二进制格式导致可读性差。 为了提高性能,ProtoBuf采用了二进制格式进行序列化反序列化。没有自描述,这直接导致了可读性差。

2.3 ProtoBuf适用场景

ProtoBuf通常应用于在网络通信和通用数据交换等应用场景中。包括:

  • 跨平台的RPC数据传输;
  • 相比json的解析要更快、数据量更小,而且数据结构明朗的数据传输或存储。

在一个需要大量的数据传输的场景中,如果数据量很大,那么选择ProtoBuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间 。

3 Python3使用ProtoBuf示例

3.1 下载安装protoc编译器

下载protoc https://github.com/protocolbuffers/protobuf/releases

根据自己的平台下载对应的可执行二进制包,也可以下载源码包自行编译。

以windows为例,解压可以在解压缩后的bin目录下看到protoc.exe文件,执行protoc.exec --version可以查看版本号。

如果想在本机任意路径下执行protoc命令,需要将该bin路径添加到系统环境变量中。(右键我的电脑 -> 高级系统设置 -> 高级 -> 环境变量 -> Path -> 编辑 -> 新建 )

3.2 编写.proto文件

新建python项目proto-test,新建文件夹protobuf,在该文件夹下新建addressbook.proto文件,内容如下:

syntax = 'proto3';

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;

    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4;
}

message AddressBook {
    repeated Person people = 1;
}

3.3 编译生成_pb2.py文件

使用protoc编译器将生成python所需的pb文件

protoc --python_out=./ addressbool.proto

在protobuf文件夹下生成addressbook_pb2.py

3.4 调用_pb2.py进行序列化及反序列化

执行pip install protobuf,安装依赖。

新建address_test_writing.py测试序列化,将序列化后的数据写入data/addressboo.txt中。

address_test.writing.py的内容如下:

from protobuf import addressbook_pb2


def PromptForAddress(person):
    person.id = 1
    person.name = "myname"
    person.email = "myname@qq.com"
    phone_number = person.phones.add()
    phone_number.number = "123456789"
    phone_number.type = addressbook_pb2.Person.MOBILE


def write_test():
    address_book = addressbook_pb2.AddressBook()

    address_book_file = "./data/addressbook.txt"

    try:
        f = open(address_book_file, "rb")
        address_book.ParseFromString(f.read())
        f.close()
    except IOError:
        print(address_book_file + ": Could not open file.  Creating a new one.")

    PromptForAddress(address_book.people.add())

    f = open(address_book_file, "wb")
    f.write(address_book.SerializeToString())   # 将对象序列化为二进制字符串
    print(address_book.SerializeToString())
    f.close()


if __name__ == "__main__":
    write_test()

新建address_test_reading.py测试反序列化。

address_test_reading.py的内容如下:

import protobuf.addressbook_pb2 as addressbook_pb2


def ListPeople(address_book):
    for person in address_book.people:
        print("Person ID:", person.id)
        print("  Name:", person.name)
        print("  E-mail address:", person.email)

        for phone_number in person.phones:
            if phone_number.type == addressbook_pb2.Person.MOBILE:
                print("  Mobile phone #: ")
            elif phone_number.type == addressbook_pb2.Person.HOME:
                print("  Home phone #: ")
            elif phone_number.type == addressbook_pb2.Person.WORK:
                print("  Work phone #: ")
            print(phone_number.number)


def read_test():
    address_book = addressbook_pb2.AddressBook()
    address_book_file = "./data/addressbook.txt"

    f = open(address_book_file, "rb")
    address_book.ParseFromString(f.read())      # 解析一个二进制字符串
    print(type(address_book))
    f.close()

    ListPeople(address_book)


if __name__ == "__main__":
    read_test()

总一下Python3使用ProtoBuf目录结构如下:

protoc生成python文件 protobuf python_序列化