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爲何更快,更小,後面會詳細介紹其原理

相關文章
相關標籤/搜索