Protocol Buffers淺出指南

什麼是socket

在瞭解pb協議以前,首先要明確socket是什麼,socket是網絡套接字,即IP+端口號的形式,更準確的說套接字(socket)是一個抽象層,應用程序能夠經過它發送或接收數據,可對其進行像對文件同樣的打開、讀寫和關閉等操做。套接字容許應用程序將I/O插入到網絡中,並與網絡中的其餘應用程序進行通訊。網絡套接字是IP地址與端口的組合。前端

總而言之:
一、socket是系統底層提供的一個被應用程序調用的通用接口
二、socket的形式:IP地址+端口號
三、每個socket都會有一個應用程序與之對應。node

Q&A?

一、Protocol Buffers是什麼?web

Protocol Buffers是一種普遍使用結構化數據存儲格式,能夠用於結構化數據的序列化/反序列化,也是不少rpc框架的基礎之一,和json、xml相似。json

二、pb協議是哪一層的協議?安全

Protocol Buffers是數據存儲格式,基於此咱們能夠對請求和響應包結構進行再定義,就有了pb協議,直接處理的是字節流,再也不須要基於http協議解析和傳輸。因此pb協議是應用層協議。服務器

三、pb協議須要考慮數據安全問題嗎?http有https加密,那pb協議呢? websocket

pb協議暫時適用於內網服務器通訊,並無進行數據加密。網絡

四、pb協議沒有區分get post,甚至尚未狀態碼的概念?網絡鏈接出錯怎麼辦?服務端沒有響應數據又怎麼辦呢?數據結構

pb協議和http協議相似,get、post區分自己不影響請求,而對於其餘所描述的狀況,都要根據socket接口再封裝,作錯誤處理、超時檢測等。app

五、相比JSON和XML 有什麼優點呢?

JSON和XML都基於HTTP協議進行數據傳輸,同時須要對JSON和XML字符串進行解析,相比pb協議的方式,pb協議基於字節流進行處理解析,pb協議傳輸的數據量更小,處理速度更快捷。

六、這種數據傳輸方式僅適用於服務端通訊嗎?客戶端可使用嗎?

並非,其餘客戶端也可使用。在前端web頁面中,能夠經過websocket傳輸ArrayBuffer的形式,直接傳輸字節流到達服務端,以後從服務端拿到傳輸回來的二進制流,進行解析;或者基於formdata也能夠傳輸二進制文件對象,經過把Arraybuffer放進blob對象中,傳輸給後臺;固然也能夠經過charCode的方式把buffer轉換成字符串,可是這樣會更加耗時。可是要注意的一點是,客戶端和服務端都要同步更新proto文件。

protocol buffers 的使用

proto文件中的數據定義方式以下:
Message 消息名{

字段規則 字段類型 字段名 = 分配標識號 [default=xxx];

}

一、字段規則:required(必須設置) 、 optional(能夠有0或1個) 、repeated(能夠有0或多個)
required:實例中必須包含的字段
optional:實例中能夠選擇性包含的字段,若實例沒有指定,則爲默認值,若沒有設置該字段的默認值,其值是該類型的默認值。如string默認值爲」」,bool默認值爲false,整數默認值爲0。
repeated: 能夠有多個值的字段,這類變量相似於vector,能夠存儲此類型的多個值。

二、字段類型:能夠是標準類型、枚舉類型、自定義message類型

三、分配標識名:一、二、3……
在proto數據結構中,每個變量都有惟一的數字標識。這些標識符的做用是在二進制格式中識別各個字段的,一旦開始使用就不可再改變。
此處須要注意的是1-15以內的標號在存儲的時候只佔一個字節,而大於15到162047之間的須要佔兩個字符,因此咱們儘可能爲頻繁使用的字段分配1-15內的標識號
。另外19000-19999以內的標識號已經被預留,不可用。最大標識號爲2^29-1。

舉個簡單的栗子:

Package MYPACKAGE;
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;
}

能夠看到咱們定義了一個包,報名爲MYPACKGE;

而且定義告終構化消息Person以及AddressBook。在定義過程當中還使用了枚舉類型以及嵌套結構體消息。

目前pb所支持的標準數據類型以下:

clipboard.png

說說編解碼

從一個簡單的官方示例開始看編解碼:

message Test1 {
      optional int32 a = 1;
}

Test1數據結構中存在一個key爲a,假如咱們將a賦值爲150,編碼出來的結果爲:
08 96 01
以上爲16進制編碼後的結果,08爲一個字節,表示爲00001000,因爲第一位保留不使用,因此實際爲0001000,pb協議規定後三位表示數據類型,即爲0,表示爲int32或int64等數據類型。
(注:數據類型映射表以下: )

clipboard.png

前四位0001,即爲1,表示鍵a所對應的標識號,爲1。
雖然說a鍵所定義的數據類型爲int32,但並不意味着咱們須要用4個字節才存儲這個value,pb編碼中,在讀取varint類型數據時,保留第一位來判斷是否還有後續的字節須要讀取。
則96表示 1001 0110,第一位爲1,表示還須要讀取下一個字節,01 表示 00000001,首位爲0,不須要讀取下一個字節,則int到這裏讀取結束。
最後只須要將讀取到的結果拼接,即爲咱們須要的int的最終數值
96 01 = 1001 0110 0000 0001
→ 000 0001 ++ 001 0110
→ 10010110
→ 128 + 16 + 4 + 2 = 150
那麼假如是字符串呢?

message Test2 {
      optional string b = 2;
}

將b的value值設置爲testing,這時候須要編碼的是字符串,結果會是這樣:
12 07 74 65 73 74 69 6e 67
12解碼出來 表示爲2號標識號,數據類型爲2。
07表示後續須要讀取7個字節。
後面的7個字節分別對應testing字符串的ascii編碼。
聰明的你可能已經發現了,不管string、byte仍是自定義結構體message,repeated,都歸屬於數據類型2,length-delimited,他們都有同一個特性,就是長度不肯定,不可限制,因此他們的存儲方式和字符串也是相似的。

message Test3 {
      optional Test1 c = 3;
}

假如我要存儲最初定義的test1結構,那麼這個時候對應的編碼結果是:
1a 03 08 96 01
1a表示 數據類型爲2,標識號爲3,
03表示有3個字節,
08 96 01 其實就是咱們最初test1的編碼結果。

nodejs中的使用

在nodejs中,只須要引入protobufjs模塊,即可以開心快樂地使用pb協議了。

package MY_NAMESPACE;

message Person {
    optional string name = 1; 
    optional int32 money = 2;
}
const protobuf = require('protobufjs')
protobuf.load("mytest.proto", function(err, appProto) {
  if (err)
      throw err;

  var Person = appProto.lookupType("MY_NAMESPACE.Person");
  var payload = { 
    name: "王二狗",
    money: 12
  };

  var errMsg = Person.verify(payload);
  if (errMsg)
      throw Error(errMsg);

  var message = Person.create(payload); 
  var buffer = Person.encode(payload).finish();
  console.log(buffer)

  var message = Person.decode(buffer);
  var object = Person.toObject(message, {
      longs: String,
      enums: String,
      bytes: String,
  });
  console.log(object)
});

能夠在命令行看到相應的輸出結果以下:

clipboard.png

若是你仔細閱讀了剛纔所介紹的編解碼,想必你也看懂了這個buffer!

定義包結構、投入使用

在上述案例中,能夠看到咱們已經把要傳輸的數據轉換成了buffer,可是問題是還須要指定包名(命名空間)以及命令字,那麼做爲請求方怎麼讓服務端知道咱們的請求是對應哪個proto文件、哪個命令字呢?
咱們須要在額外傳輸一份數據來告訴服務端namespace和cmdname,這就須要咱們額外定義一個頭部信息。
若是接口要鑑權呢?又有其餘額外信息要傳輸呢?也是同樣的,定義一個通用的頭部信息proto文件,在發送請求時將頭部buffer和內容buffer一塊兒傳輸。
而頭部buffer和內容buffer通常都是非定長的,須要咱們提供額外的長度信息,因此你的包結構能夠設計成這樣:

clipboard.png

總結

學完了pb協議,知道了編解碼原理,學習瞭如何使用,也知道pb協議包要怎麼設計,本節課到此結束!同窗們,下課!

更多問題以及新特性請戳官網:https://developers.google.com...

clipboard.png

相關文章
相關標籤/搜索