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 最有效的应用领域。
Comments | NOTHING