使用Google Protocol Bufffers進行通訊(Ruby & C)

zyhmz發表於2018-06-09

最近專案中需要用到Google 的 proto buffer作為一種跨平臺通訊的協議。首先感謝 的部落格,讓我對這個協議有了一些初步的理解。

協議的由來
在後臺需要與多種終端如iPhone,Android,Web或者WinPhone之類的不同平臺作通訊的時候,常常需要使用一種中間的通訊協議,並且使用通用資料型別如XML。現在專案中的裝置底層是用c語言完成的,我們服務端的語言是ruby,中間層是IOS或者Andriod的裝置。

Protocol Buffers(以下簡稱protobuf)就是類似於XML這樣的東西,可以在後臺與多終端間進行通訊,但是比它要遠強大的多。

Protobuf由Google出品,08年的時候Google把這個專案開源了,截至發稿已發展到2.5.0版本,官方支援C++,Java和Python三種語言,但是由於其設計得很簡單,所以衍生出很多第三方的支援,基本上常用的PHP,C,ruby等多種語言都已有第三方的庫。

Protobuf的優勢
首先我們來比較和XML來比較一下,其實最主要的優勢就是它更簡單了。

一個XML檔案我們編寫的時候需要定義各種各種的節點,而Proto檔案則使用Google定義的有點像Java的語言來編寫,簡潔很多。

XML長得像這樣,看起來很冗餘,因為要加很多標籤:

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

而我們的proto檔案則長這樣:

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

其次是快了。proto檔案是給程式猿閱讀的時候用的,真正傳輸的時候是序列化的二進位制資料,比起XML要小得多,解析起來也快得多。

第三是可以直接生成供程式使用的類。XML檔案接收後我們還得手工去解析然後轉化為可以使用的物件,但是PB檔案接收後,PB的庫就已經幫我們轉化為對應的資料類了。

Protobuf主要分為兩個部分,一是編譯器protoc,一是分包組包所用到的庫。
編譯器是用來編譯proto檔案為目標語言的,比如一個上面那個 Person.proto 檔案,我可以用 protoc 直接編譯成C++類 Person,用的時候就很方便了:

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

使用
我們現在的後臺開發中,主要用到的是Ruby on Rails, 所以我們先來著重看一看protobuf在Rails中的使用方法。

  1. 定義好proto的檔案
package Tutorial;

message Source {
    required string title = 1;
    required string description = 2;
    optional int id = 3;
}

message SourceAllResponse {
    required uint32 count = 1;
    repeated Source source_list = 2;
}
  1. 編譯成對應語言

定義完proto檔案後,使用官方的 protoc 可以對其進行編譯。下載地址在: https://code.google.com/p/protobuf/downloads/list

如果是 Mac OS X 或者 Linux ,需要下載官方的原始碼,解壓後根據官方的 README.txt 裡的說明:

$ ./configure
$ make
$ make check
$ make install
  1. Ruby的用法:
    待補充

Protobuf 語法定義
首先我們再來定義一個proto檔案,結構如下:

message Article {
required int32 article_id=1;
optional string article_excerpt=2;
repeated string article_picture=3;
}

上面我們主要定義了一個訊息,這個訊息包括文章ID,文章摘要,文章圖片。下面給出訊息定義的相關說明:

名稱 定義
message 是訊息定義的關鍵字
required 表示這個欄位必須的,必須在序列化的時候被賦值
optional 代表這個欄位是可選的,可以為0個或1個但不能大於1個
repeated 則代表此欄位可以被重複任意多次包括0次
int32和string 欄位的型別,後面是我們定義的欄位名

最後的1,2,3則是代表每個欄位的一個唯一的編號標籤,在同一個訊息裡不可以重複。這些編號標籤用與在訊息二進位制格式中標識你的欄位,並且訊息一旦定義就不能更改。需要說明的是標籤在1到15範圍的採用一個位元組進行編碼。所以通常將標籤1到15用於頻繁發生的訊息欄位。編號標籤大小的範圍是1到229,此外不能使用protobuf系統預留的編號標籤(19000 -19999)。當然protobuf支援更多的型別,比如bool, double, float, emun, 也可以是其他定義過的訊息型別譬如前面的訊息Article。

下面讓我們定義一個資料比較多的article.proto檔案來再次說明下proto語法的相關內容,起碼通過列子可以更直觀的感受:

syntax = "proto3";
package zji.proto;
message Article {
  required int32 article_id = 1;
  optional string article_excerpt = 2;
  repeated string article_picture = 3;
  optional int32  article_pagecount = 4 [default = 0];
  enum ArticleType {
    NOVEL = 0;
    PROSE = 1;
    PAPER = 2;
    POETRY = 3;
  }
  optional ArticleType article_type = 5 [default = NOVEL];
  message Author {
    required string name = 1; //作者的名字
    optional string phone = 2;
  }
  optional Author author = 6;
  repeated int32 article_numberofwords = 7 [packed=true];
  reserved  9, 10, 12 to 15;
  extensions 100 to 1000;
}
extend Article {
  optional int32 followers_count = 101;
  optional int32 likes_count= 102;
}
message Other {
  optional string other_info = 1;
  oneof test_oneof {
    string code1 = 2;
    string code2 = 3;
  }
}

reserved關鍵字: 主要用於保留相關編號標籤。這麼做的目的就是防止更新proto檔案刪除了某些欄位,而未來的使用者定義新的欄位時重新使用了該編號標籤。這會引起一些問題,比如在獲取老版本的訊息時,會引起譬如資料衝突等一些隱藏的bug,所以一定要用reserved標記這些編號標籤以保證不會被使用。其實對這個關鍵字的用法理解得並不是很深刻,後面有機會用到我們再補充。

extent關鍵字:當我們需要對訊息進行擴充套件的時候,我們可以用extensions關鍵字來定義一些編號標籤供第三方擴充套件。這樣的好處是不需要修改原來的訊息格式。就像上面proto檔案,我們用extend關鍵字來擴充套件。只要擴充套件的欄位編號標籤在extensions定義的範圍裡。

oneof關鍵字: 待補充

packed=true的作用: 對於基本數值型別,由於歷史原因,不能被protobuf更有效的encode。所以在新的程式碼中使用packed=true可以更加有效率地encode。注意packed只能用於repeated 數值型別的欄位。不能用於string型別的欄位。

相關文章