protobuf(Google Protocol Buffers)是Google提供一個具有高效的協議數據交換格式工具庫(類似Json),但相比於Json,Protobuf有更高的轉化效率,時間效率和空間效率都是JSON的3-5倍。后面將會有簡單的demo對於這兩種格式的數據轉化效率的對比。
簡單設計協議, 通過自帶工具轉換成為對應的語言代碼, 協議是二進制協議, 設計時只需要描述各個類的關系, 簡單明了
Protobuf 提供了C++、java、python語言的支持,提供了windows(proto.exe)和linux平台動態編譯生成proto文件對應的源文件。proto文件定義了協議數據中的實體結構(message ,field)
關鍵字message: 代表了實體結構,由多個消息字段(field)組成。
消息字段(field): 包括數據類型、字段名、字段規則、字段唯一標識、默認值
數據類型:常見的原子類型都支持(在FieldDescriptor::kTypeToName中有定義)
字段規則:(在FieldDescriptor::kLabelToName中定義)
required:必須初始化字段,如果沒有賦值,在數據序列化時會拋出異常
optional:可選字段,可以不必初始化。
repeated:數據可以重復(相當於java 中的Array或List)
字段唯一標識:序列化和反序列化將會使用到。
默認值:在定義消息字段時可以給出默認值。
常用序列化格式介紹
目前JAVA常用的序列化有protobuf,json,xml,Serializable,hessian,kryo。他們的優缺點如下:
-
JSON:不多說了,用途廣泛,序列化方式還衍生了阿里的fastjson,美團的MSON,谷歌的GSON等更加優秀的轉碼工具。
優點:使用方便。
缺點:數據冗長,轉碼性能一般。 -
XML:很久之前的轉碼方法了,現在用的不多。
優點:暫時沒發現。
缺點:數據冗長,轉碼性能一般。 -
Serialzable:JDK自帶的序列化。
優點:使用方便。
缺點:轉碼性能低下。 -
hessian:基於 binary-RPC實現的遠程通訊library,使用二進制傳輸數據。
優點:數據長度小。
缺點:性能低下。
用法:
-
設計協議是在fileName.proto文件中, 其中fileName是自己定義, 在通過protoc轉換成為對應的代碼。
-
關鍵字:
- message 表示一個消息體, 相當於一個類。
- 每個message中都有對應的變量, 每個變量有個對應的編號, 每個message內不同變量的編號不能重復。
- 新版本的protobuf沒有了required, optional等說明關鍵字, 都默認為optional
-
基本語法
//指定版本 使用protobuf3 syntax = "proto3"; message Account { //賬號 uint64 ID = 1; //名字 string name = 2; //密碼 string password = 3; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
語法很簡單, 上面例子就是指定使用protob3, 然后定義了一個Account類, 里面包含ID, name, password, 對應的類型是 uint64, string, string。
所以基本的語法規則跟C++類似。 只不過多了后面變量的編號。
-
生成代碼:
需要生成什么語言的代碼需要直接指定,例如C++
protoc --cpp_out=./ project.proto
protoc
是工具名, 可以直接運行的命令。--cpp_out
是參數, 指定生成C++代碼, =后面指定生成的目錄。project.proto
是定義的文件。一共會生成兩個文件。
project.pb.h
和project.pb.cc
其中有生成的Account類中有這幾個設置屬性的方法
void clear_name(); static const int kNameFieldNumber = 2; const std::string& name() const; void set_name(const std::string& value); void set_name(std::string&& value); void set_name(const char* value); void set_name(const char* value, size_t size); std::string* mutable_name(); std::string* release_name(); void set_allocated_name(std::string* name); // string password = 3; void clear_password(); static const int kPasswordFieldNumber = 3; const std::string& password() const; void set_password(const std::string& value); void set_password(std::string&& value); void set_password(const char* value); void set_password(const char* value, size_t size); std::string* mutable_password(); std::string* release_password(); void set_allocated_password(std::string* password); // uint64 ID = 1; void clear_id(); static const int kIDFieldNumber = 1; ::PROTOBUF_NAMESPACE_ID::uint64 id() const; void set_id(::PROTOBUF_NAMESPACE_ID::uint64 value);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
及get/set方法。
可以直接操作該類的屬性。
-
使用該代碼
-
在要使用的代碼中包含此頭文件
project.pb.h
直接使用該對象即可。編譯的時候需要把
project.pb.cc
編譯, 並在鏈接的時候鏈接動態鏈接庫libprotobuf.so
-
-
demo
#include <iostream> #include <string> #include "project.pb.h" int main() { Account account; account.set_id(1000); account.set_name("name"); account.set_password("password"); //序列化 std::string s = account.SerializeAsString(); if(s.size() == 0) { std::cout << "error in SerializeAsString" << std::endl; } Account nAccount; //反序列化 if(nAccount.ParseFromString(s)) { std::cout << nAccount.id() << std::endl; std::cout << nAccount.name() << std::endl; std::cout << nAccount.password() << std::endl; } else { std::cout << "error in ParseFromString" << std::endl; } return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
-
編譯
project.pb.cc
也需要編譯, 如果是工程中, 可以單獨編譯, 由於只有一個文件所以就放到一塊編譯了g++ demo.cpp project.pb.cc -lprotobuf -o main
輸出結果為:
1000
name
password
更復雜的demo
-
message的嵌套
message就像類一樣, 所以它也是可以嵌套的。
可以直接在message內寫, 也可以在外部寫, 但是要注意的是, 一個message內的編號不能重復。
所以令寫一個message會節省編號。
節省編號的所有是節省空間。 前15號(0~15)用一個字節, 后面以此類推, 兩個三個字節, 所以前15編號比較珍貴。
-
demo
//指定版本 使用protobuf3 syntax = "proto3"; message Account { //賬號 uint64 ID = 1; //名字 string name = 2; //密碼 string password = 3; //寵物狗 Dog dog = 4; } message Dog { string name = 0; bool sex = 1; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
還有幾個比較重要的方法
set_allocated_dog
dog不是必須的, 是類的名字。 可以是其他的名字- 參數是一個對應類型的指針, 調用這個函數, 它會自動釋放這個指針的對象, 所以不需要delete, 否則會段錯誤。
- 再次調用這個參數時, 如果之前已經設置過參數, 那么再次調用的時候會之前設置的屬性刪除, 並且delete越來的對象。 在設置成新的屬性。
除了這些方法之外還有一些clear之類的方法, 需要可以看文檔或者給的頭文件。
oneof
oneof 是設置多個屬性中的一個, 例如, 我的寵物可以是狗, 也可以是貓, 但是每個人只有一個的話。 設置兩個占的空間就有點大。 因此只需要有一個就夠了, 所以這個oneof就像union一樣。
//指定版本 使用protobuf3
syntax = "proto3";
message Account {
//賬號
uint64 ID = 1;
//名字
string name = 2;
//密碼
string password = 3;
//寵物
oneof pet {
Dog dog = 4;
Cat cat = 5;
}
}
message Dog {
string name = 1;
bool sex = 2;
}
message Cat {
string name = 1;
//屬性可以與Dog不同
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 方法
has_cat
或者has_dog
方法用於檢測是否有cat/dog- 添加cat和dog直接用原來的方法添加即可。 但是當添加dog的時候會自動刪除cat, 反之亦然。
protobuf: install:https://blog.csdn.net/hailong0715/article/details/52057873
https://blog.csdn.net/skh2015java/article/details/78404235
參考:其他的協議分析工具:https://www.cnblogs.com/protosec/p/11673374.html
protobuf(Google Protocol Buffers)是Google提供一個具有高效的協議數據交換格式工具庫(類似Json),但相比於Json,Protobuf有更高的轉化效率,時間效率和空間效率都是JSON的3-5倍。后面將會有簡單的demo對於這兩種格式的數據轉化效率的對比。