Google Protocol Buffers 数据交换协议

protobuf 简介

protobuf是什么

protobuf(Protocol Buffers)是Google推出的一个结构化数据交换协议,用于传递自定义的消息格式,可用于同一台机器的进程间、不同设备进程间的数据传递。protobuf是一种语言无关、平台无关、高效、扩展性良好的语言,提供了一种将结构化数据进行序列化和反序列化的方法。

相对于XML,protobuf的体积更小、速度更快、使用更简单。我们仅需要定义一次数据结构,就可以很轻松地使用生成的代码读/写数据,而且这些数据结构是向后兼容的。

官方网站

https://developers.google.com/protocol-buffers/

protobuf的优劣

为什么不使用XML?

相对于XML来说,Protocol buffers在序列化结构化数据上,具有非常明显的优势:

  • 更加简单
  • 体积减小3~10倍
  • 速度提高20~100倍
  • 更清晰
  • 生成的数据结构代码,更容易使用

如果要生成一个具有nameemailperson实例,XML做法如下:

  <person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
  </person>

使用protocol:

# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
  name: "John Doe"
  email: "jdoe@example.com"
}

当此消息被编码为二进制格式时,长度大概是28字节,解析时间为100200ns。在去除所有空格后,XML版本也至少为69字节,解析时间长达500010000ns。

同时,使用protocol buffer更简单:

  cout << "Name: " << person.name() << endl;
  cout << "E-mail: " << person.email() << endl;

使用XML的代码:

  cout << "Name: "
       << person.getElementsByTagName("name")->item(0)->innerText()
       << endl;
  cout << "E-mail: "
       << person.getElementsByTagName("email")->item(0)->innerText()
       << endl;

相对于JSON来说,protobuf也有明显的优势:

  • 更好的前后数据版本兼容性
  • 提供了验证机制,更容易被扩展
  • 不同语言间互操作性更好

protobuf也有一些缺点,并不是适合所有场景。

  • 二进制编码和传输,可读性差
  • 编码和解码依赖额外的库,不能在浏览器、JS中直接使用
  • 缺乏自描述

如何使用protobuf

  • 定义.proto文件
  • 编译protocol buffer
  • 使用Java protocol buffer API 读写数据

下面是通过Java使用protobuf的官方示例:https://developers.google.com/protocol-buffers/docs/javatutorial ,并在此基础上进行了简化。

定义.proto文件

定义需要序列化的数据结构,为message中的每一个变量设置名称和类型。下面

package tutorial;

option java_package = "yano";
option java_outer_classname = "AddressBookProtos";

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];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}
  • package:防止不同工程中的命名冲突;生成Java的proto时,如果没有指定java_package,包名默认为package
  • java_package:包名
  • java_outer_classname:定义生成Java代码的文件名(类名);如果没有指定,会将proto文件变成驼峰形式:默认会将my_proto.proto生成MyProto的类文件。

定义字段时,我们使用了required、optional、repeated三个关键字。这些关键字表示对字段的约束,分别表示:

  • required-非空约束。如果字段值为空,会被认为是uninitialized,并抛出异常。
  • optional-可选。表示字段可以赋值,也可以不赋值。不赋值时,将会使用默认值。
  • repeated-可重复次数。表示字段可以重复使用的次数,重复顺序会被保存在protobuf中,可以将其理解为一个数组。

proto文件中的其它格式,在此不作介绍,详细内容可以参考官方文档。

编译protocol buffer

现在我们有了一个.proto文件,接下来就需要将生成class文件,我们可以通过这个class文件读写AddressBook的消息。

  1. proto编译器下载地址:https://developers.google.com/protocol-buffers/docs/downloads
  2. 运行编译器,指定proto路径、生成路径、.proto文件。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

Protocol Buffer API

通过proto编译器,addressbook.proto生成了Java类AddressBookProtos.java。每个class都有自己的Builder,用以生成该类的实例。

// required string name = 1;
public boolean hasName();
public String getName();

// required int32 id = 2;
public boolean hasId();
public int getId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();

// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);

Person.Builder 除了getters,还有setters方法:

// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();

// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();

// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
public Builder addAllPhone(Iterable<PhoneNumber> value);
public Builder clearPhone();

创建一个 Person 实例:

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

标准消息方法

以下方法能够让我们检查和操作整个message:

  • isInitialized():检查required字段是否全部设置
  • toString():返回可阅读的格式,在debug时非常有用
  • mergeFrom(Message other):将other的内容合并到该message中,会覆盖相同的字段,对repeated字段会添加
  • clear():重置所有字段

解析和序列化

所有的protocol buffer类都有读写二进制的方法:

  • byte[] toByteArray():序列化消息并返回包含其原始字节的字节数组
  • static Person parseFrom(byte[] data):通过给定的字节数组,解析message
  • void writeTo(OutputStream output):序列化消息,并将其写到OutputStream
  • static Person parseFrom(InputStream input):从InputStream中读取并解析message

扩展protobuf

在扩展proto文件时,需要注意以下事项:

  • 绝对不能改变已经存在的字段的tag numbers
  • 绝对不能添加或删除required字段
  • 可以删除optionalrepeated字段
  • 可以添加新的optionalrepeated字段,但是必须使用新的tag numbers

结束语

我的博客:http://www.jianshu.com/users/6835c29fc12a/latest_articles
我的知乎:https://www.zhihu.com/people/liu-jia-yu-58/activities

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容