Win 下使 Google Protocol Buffer


protocol buffers简介

Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML,不过它比xml更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构

为什么不直接使用XML

同XML相比,Protobuf的优势在于高性能,它以高效的二进制存储方式比XML小3到10倍,快20到100倍,原因在于:

  • ProtoBuf序列化后所生成的二进制消息非常紧凑
  • ProtoBuf封解包过程非常简单

一个简单的例子

我打算使用 Protobuf 和 Go 开发一个十分简单的例子程序。

该程序由两部分组成。第一部分被称为 Writer,第二部分叫做 Reader。

Writer 负责将一些结构化的数据写入一个磁盘文件,Reader 则负责从该磁盘文件中读取结构化数据并打印到屏幕上。

准备用于演示的结构化数据是官方go示例addressbook

1.安装 Google Protocol Buffer

在网站 http://code.google.com/p/protobuf/downloads/list 上可以下载 Protobuf 的源代码。然后解压后放到自定义目录,设置环境变量后便可以使用它了。如果不方便登录google,也提下载我提供的安装包,下载地址在这

使用“protoc --version” 命令验证是否安装成功

ps:目录一定是英文路径,不要包含中文字符

2.书写.proto文件

首先我们需要编写一个 proto 文件addressbook.proto,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义。代码清单 1 显示了例子应用中的 proto 文件内容。

syntax = "proto3";

package protoDemo;

import "google/protobuf/timestamp.proto";

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;

    google.protobuf.Timestamp last_updated = 5;
}

message AddressBook {
    repeated Person people = 1;
}

ps:

  • 在最新版的 Protobuf 中,强制要求 proto 文件必须指定版本,否则编译报错
  • 这里定义的所有字段(无论大小写),在生成的 Go 语言中的结构体中都是导出字段,也就是都会变成大写

3.安装插件protoc-gen-go

go get -u github.com/golang/protobuf/protoc-gen-go

4.编译.proto文件

写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。
假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

命令将生成addressbook.pb.go文件
执行 protoc,并使用 --go_out 选项指定输出目录,即可生成 Go 源码文件。因为安装了 protoc-gen-go 之后,--go_out 选项会自动搜索 protoc-gen-go,只要其在 PATH 目录中可以找到即可。

--go_out 支持以下参数

  • plugins=plugin1+plugin2 指定插件,目前只支持 grpc,即:plugins=grpc
  • M 参数 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
  • import_prefix=xxx 为所有 import 路径添加前缀,主要用于编译子目录内的多个 proto 文件,这个参数按理说很有用,尤其适用替代一些情况时的 M 参数。
  • import_path=foo/bar 用于指定未声明 package 或 go_package 的文件的包名,最右面的斜线前的字符会被忽略

扩展

各语言生成命令:

C++

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

C#

protoc -I=$SRC_DIR --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto

Dart

protoc -I=$SRC_DIR --dart_out=$DST_DIR $SRC_DIR/addressbook.proto

Go

protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

Java

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

Python

 protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto

5.编写 writer 和 Reader

如前所述,Writer 将把一个结构化数据写入磁盘,以便其他人来读取。假如我们不使用 Protobuf,其实也有许多的选择。一个可能的方法是将数据转换为字符串,然后将字符串写入磁盘。这非常简单。数字 123 可以变成字符串”123”。

这样做似乎没有什么不妥,但是仔细考虑一下就会发现,这样的做法对写 Reader 的那个人的要求比较高,Reader 的作者必须了 Writer 的细节。比如”123”可以是单个数字 123,但也可以是三个数字 1,2 和 3,等等。这么说来,我们还必须让 Writer 定义一种分隔符一样的字符,以便 Reader 可以正确读取。但分隔符也许还会引起其他的什么问题。最后我们发现一个简单的 addressbook也需要写许多处理消息格式的代码。

如果使用 Protobuf,那么这些细节就可以不需要应用程序来考虑了。

使用 Protobuf,Writer 的工作很简单,需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个addressbook.pb.go文件。

Writer 需要 import该头文件,然后便可以使用这个文件了。

当我们需要将该结构化数据保存到磁盘上时,addressbook.pb.go 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

对于想要读取这个数据的程序来说,也只需要使用addressbook.pb.go 的相应反序列化方法来将这个字节序列重新转换会结构化数据。这同我们开始时那个“123”的想法类似,不过 Protobuf 想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给 Protobuf 吧。

package main

import (
    "fmt"
    "github.com/golang/protobuf/proto"
    "io/ioutil"
    "log"
    "protoDemo/proto"
)

func write() {
    p := &protoDemo.Person{
        Id:    1234,
        Name:  "Pony",
        Email: "pony@ponycool.com",
        Phones: []*protoDemo.Person_PhoneNumber{
            {Number: "555-4321", Type: protoDemo.Person_HOME},
        },
    }
    book := &protoDemo.AddressBook{}
    book.People = append(book.People, p)
    out, err := proto.Marshal(book)
    if err != nil {
        log.Fatalln("Failed to encode address book:", err)
    }
    if err := ioutil.WriteFile("./test.txt", out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
    }
}
func read() {
    in, err := ioutil.ReadFile("./test.txt")
    if err != nil {
        log.Fatalln("Error reading file:", err)
    }
    book := &protoDemo.AddressBook{}
    if err := proto.Unmarshal(in, book); err != nil {
        log.Fatalln("Failed to parse address book:", err)
    }
    for _, v := range book.People {
        fmt.Println(v.Id, v.Name)
        for _, vv := range v.Phones {
            fmt.Println(vv.Type, vv.Number)
        }
    }
}
func main() {
    write()
    read()
}

运行结果

Reader 读取文件中的序列化信息并打印到屏幕上。本文中所有的例子代码都可以在附件中下载。您可以亲身体验一下。

这个例子本身并无意义,但只要您稍加修改就可以将它变成更加有用的程序。比如将磁盘替换为网络 socket,那么就可以实现基于网络的数据交换任务。而存储和交换正是 Protobuf 最有效的应用领域。

附件

声明:初心|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Win 下使 Google Protocol Buffer


愿你勿忘初心,并从一而终