Protocol Buffer语法解析(proto3)

背景

Protocol Buffer是google自定义的数据传输协议,目前已经被普遍用于服务端和客户端间的数据传输,清晰理解Protocol Buffer的使用以及语法就显得很重要,本文对Protocol Buffer语法分析是基于proto3.java

传输协议对比

目前使用最普遍的数据传输协议为JSON,JSON是一种轻量级的数据交换格式并且层次和结构比较简单和清晰,这里主要对比一下Protocol Buffer和JSON的对比,给出优点和劣势:python

优点安全

  • 传输数据更小
  • 序列化和反序列化更快
  • 因为传输的过程当中使用的是二进制,没有结构描述文件,没法解析内容,安全性更高

劣势ruby

  • 因为传输过程使用的是二进制,自解释性较差,须要原有的结构描述文件才能解析

实际数据对比bash

  • 序列化速度:比JSON快20-100倍
  • 数据大小:序列化后体积小3倍

使用流程

Protocol Buffer的使用流程整体能够分为三步,以下图所示: 工具

image

  1. 根据业务建立并定义proto文件
  2. 使用Google Protocol Buffer 提供的工具生成对应语言的源文件
  3. 将源文件拷贝到工程中,使用Protocol Buffer提供的库序列化或反序列化数据

语法解析

在使用Protocol Buffer以前须要清楚理解其语法定义,本文对Protocol Buffer的语法解析是基于proto3版本优化

简单示例

首先建立一个.proto文件,而且在文件中声明以下内容:ui

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
复制代码

其中第一行标明当前proto使用的版本为proto3,后面定义了一个结构体SearchRequest,结构体中共3个属性。google

字段

在整个proto文件中分为基本类型和结构类型,其中结构类型主要为:编码

  • message
  • enum
  • map

下面分别介绍一下不一样结构的做用及规定:

message

message声明

message表示一个结构,相似于java中类,一个proto文件中能够声明多个message结构:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}
复制代码
message引用

message能够引用不一样proto文件中的message,只要在proto文件中的最上面声明import便可,以下所示:

import "myproject/other_protos.proto";
复制代码
message继承

meesage 可使用extend来继承另一个message,而且使用其中的属性,这里注意一下因为每一个message中须要对属性进行编号,在继承的时候须要注意编号,防止重复使用

enum

enum使用

enum使用很简单,直接在message中声明enum结构体而且将属性声明为对应的enum便可:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}
复制代码

上面代码中Corpus就是一个enum

enum规定

在proto3中,enum第一个值必须为0,主要是为了和基础类型的默认值保持一致

map

map是proto3新加的,使用也很简单:

map<key_type, value_type> map_field = N;
复制代码

在proto2中可使用repeated和message结构自定义map,以下所示

message Person {
    string key = 1;
    string value = 2;
  }
repeated Person  person = 4;
复制代码

基础类型

proto中基础类型有不少,下面给出不一样的基础类型对应的java中的类型,及其特色:

proto 类型 java类型 备注
double double
float float
int32 int 可变长度编码,若是有负值,可使用sint32修饰
int64 long 可变长度编码,若是有负值,可使用sint64修饰
uint32 int 可变长度编码
uint64 int 可变长度编码
sint32 int 可变长度编码,用来表示负值时效率比int32更高
sint64 long 可变长度编码,用来表示负值时效率比int64更高
fixed32 int 4个字节,当数值>2^28时效率比uint32高
fixed64 long 8个字节,当数值>2^56时效率比uint64高
sfixed32 int 4个字节
fixed64 long 8个字节
bool boolean
string String UTF-8 encoded or 7-bit ASCII text, 长度不能超过2^32
bytes ByteString 长度不超过2^32,任意顺序的字节数据

其中部分基本类型修饰符长度不肯定,主要采用了可变长度编码,这也是为何Prorocol Buffer序列化后的数据字节更少,这个后面原理篇会介绍。

默认值

基础类型的默认值以下:

  • string:空串
  • bytes:空字节
  • bool:false
  • 数字类型:0
  • enum:默认值是第一个元素,且值必须为0

字段修饰符

  • singular:一个格式良好的消息应该有0个或者1个这种字段(可是不能超过1个)
  • repeated:在一个格式良好的消息中,这种字段能够重复任意屡次(包括0次),重复的值的顺序会被保留,相似于java中的list

在proto3中,repeated的标量域默认状况下使用packed,也就是可变长度编码

字段编号

先看下一个简单的proto文件:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}
复制代码

message SearchRequest 中的全部字段都声明了字段编号,这里须要注意:

  1. 字段编号从1开始,不可重复定义
  2. 字段编号1-15尽可能保持常常访问的字段使用,由于1-15编号在传输的过程当中只占用1个字节

字段扩充

平常开发过程当中,因为需求的变动,每每须要增长字段,这就涉及到字段的扩充,字段扩充须要达到一个目的:兼容

因此Protocol Buffer在字段扩充中定义了以下规则:

  1. 不要修改已经存在的字段标号
  2. 不用的字段,能够删除,可是编号必定不能够再次使用,建议将字段标为废弃,如加前缀:"OBSOLETE_"

只要记住上述规则,就能完成字段扩充且老版本也能兼容

生成对应语言文件

当proto文件编写后,就须要生成对应语言的源文件,生成操做以下:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
复制代码
  • IMPORT_PATH: proto文件中import的proto文件地址
  • XX_out:对应源文件输出地址
  • proto Path:源proto文件

原理简介

Protocol Buffer 更快更小的主要缘由以下:

  1. 数据在序列化的时候不会传输字段名,只会传输字段标号,而且没有被设置值的字段是不会序列化和传输
  2. 采用可变长度编码,优化数据占用

总结

以上基于proto3讲述了Protocol Buffer的语法和使用流程,其中简单说明了Protocol Buffer为何更快,更小,后面会详细介绍其原理

相关文章
相关标签/搜索