protobuf基本用法詳解


首先看下下面這個proto文件,我們後面的proto基本用法都是基於這個proto進行講解

package pkgName;

option java_package = "test1.test2";
option java_outer_classname = "TestClass";

message mmData {
	optional int32 num = 1;
	optional int32 def_num = 2 [default=10];
	required string str = 3;
	repeated string rep_str = 4;
}

1、包名package

​ proto文件使用關鍵字package指定當前包名,它類似於java中的包名或者C++中的命名空間,主要是用來防止不同消息類型的命名衝突。使用protobuf編譯器將proto文件編譯成C++代碼之後,當前proto文件中的所有聲明都將位於命名空間pkgName::下。

2、option

在消息定義之前,可以通過option來進行配置,常用的兩個option:

  • option java_package=“xxx/xxx” 該選項指定了java文件生成的路徑
  • option java_outer_classname=“xxx” 該選項制定了生成的java類名

3、消息類型

3.1 message

​ Protobuf中定義一個消息類型是通過關鍵字message字段指定的,這個關鍵字類似於C++/Java中的class關鍵字,使用protobuf編譯器將proto編譯成C++代碼之後,每個message都會生成一個名字與之對應的C++類。

​ 如上面的message 將生成一個名字爲mmData的類,該類公開的繼承google::protobuf::Message。

3.2 字段規則

message中的字段規則有三種。

  • required : 字段屬性爲必填字段。若不設置,則會導致編解碼異常,導致消息被丟棄。

  • optional : 字段屬性爲可選字段。發送方可以選擇性根據需要進行設置;對於optional屬性的字段,可以通過default關鍵字爲字段設置默認值,即當發送方沒有對該字段進行設置的時候,將使用默認值。如果沒有對字段設置默認值,就會根據特定的類型給字段賦予特定的默認值。對於bool類型,默認值爲false;對於string類型,默認值爲空字符串;對於數值類型,默認值爲0;對於枚舉類型,默認值是枚舉類型中的第一個值。

  • repeated : 字段屬性爲可重複字段,該字段可以包含[0,n]個元素,字段中的元素順序被保留。

注意:

(1)在proto3版本中,字段規則上移除了required,並把optional字段改名爲singular。所有沒有指定字段規則的字段默認爲optional,對於爲什麼刪除了require規則,參考:爲什麼 proto3 移除了 required 和 optional?

(2)在proto2版本中,默認配置下,一個optional沒有被設置或者被顯示的設置爲默認值,在序列化二進制格式的時候,這個字段將會被去掉,導致反序列化之後,無法區分當初沒有設置還是設置了默認值,即使使用hasXXX()方法,對於設置的默認值的字段,也是返回false。解決方法:區分 Protobuf 中缺失值和默認值

3.3 標識號

​ 在消息體的定義中,每個字段都必須要有一個唯一的標識號。這些標識號是用來在消息的二進制格式中識別各個字段的,一旦使用就不能再改變,否則會導致原有消息編解碼出現異常。

​ 標識號是[0,2^29 - 1]範圍內的一個整數,其中**[19000,19999)之間的標識號在protobuf協議的實現中被預留了**,所以特寫注意不要使用這個範圍內的標識號,若使用進行編譯的時候也會告警.

Field numbers 19000 through 19999 are reserved for the protocol buffer library implementation.

注意:[1,15]內的標識號在編碼的時候佔用一個字節,[16,2047]之內的標識符佔用兩個字節,所以儘量爲頻繁使用的字段分配[1,15]內的標識號,另外預留出來一部分給未來可能頻繁使用的字段

3.4 數據類型

3.4.1 基本數據類型

​ 消息體中的每個字段都必須指定字段類型,可選的字段類型和語=與其對應的C++/Java中的類型如下圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KTrq0uIB-1588870734147)(/Users/wingwei/workspace/wx_workspace/文檔/image/proto數據類型.png)]

圖片資源來源:https://blog.csdn.net/m15927408113/article/details/79976528

3.4.2 枚舉類型

​ 字段類型除了上述基本的字段類型之外,也可以是枚舉類型。protobuf中的枚舉類型使用方法與C++中的枚舉類型類似,在將proto文件編譯成C++代碼後,其對應的類型也是C++中的枚舉類型。

package pkgName;
// 定義枚舉類型
enum DayName {
        Sun = 0;
        Mon = 1;
        Tues = 2;
        Wed = 3;
        Thur = 4;
        Fri = 5;
        Sat = 6;
}

message workDay {
				// 消息類型使用枚舉類型
        optional DayName day = 1;
}

​ 枚舉常量的值必須在32位整數範圍內,因爲enum值是使用可編碼方式存儲的,對負數存儲不夠高效,因此不推薦在enum中使用負數。

​ 枚舉類型可以定義在message內,也可以定義在message外,若定義在message內,其他message要使用則需要通過messageType.enumType來進行引用。

​ 默認情況下,枚舉類型中的字段值不可重複,但是通過對enum添加**option allow_alias = true;**來達到對同一個枚舉值起一個別名的目的,若不添加allow_alise並且有重複的枚舉值編譯的時候會報錯。

package pkgName;
enum DayName {
				// 若不添加該option,會報錯:
				// "pkgName.Test" uses the same enum value as "pkgName.Sat". If this is intended, set 'option allow_alias = true;' to the enum definition.
			  option allow_alias = true;
        Sun = 0;
        Mon = 1;
        Tues = 2;
        Wed = 3;
        Thur = 4;
        Fri = 5;
        Sat = 6;
        Test = 6;	// Test與Sat字段值重名
}

3.4.3 map數據類型

​ 除了上述類型之外,message還支持map<Type,Type>類型。

package pkgName;

message Tdata {
        map<int32,string> data = 1;
}

注意:

(1) protobuf中的map實質上是unordered_map

(2) proto中map類型不能用optional/required/repeated任何類型修飾。

3.4.4 message類型

​ protobuf允許將其他消息類型用作字段類型。如下面userData中存在一個workDay類型的數據。

package pkgName;

message workDay {
        optional int day = 1;
}

message userData {
        optional workDay userDays = 1;
}

3.4.5 嵌套消息類型

​ 與C++中的類可以嵌套類似,消息也可以嵌套。即允許你在一個messageType中定義另一個messageType,然後使用它,

package pkgName;

message OutterData {
				// 嵌套消息定義,在生成的C++代碼中,該message被組織爲類:outterData_Tdata
        message Tdata {
                optional int32 a = 1;
        }
				
				// 引用嵌套消息
        optional Tdata data = 1;
}

message嵌套在生成C++代碼之後,實際上內部類生成的類名爲OutterData_Tdata

4、import導入其他proto文件

4.1 import

​ 我們可以通過import導入其他proto文件,並使用該proto文件中的定義的消息類型。與c++中的include或者Java中的import類似。

如:

// a.proto文件
package Ap;

message A{
        optional int32 a = 1;
}
// b.proto文件
import "a.proto"; // 導入a.proto文件,這樣就可以直接使用a.proto中定義的消息類型了。
package Bp;

message B{
        optional Ap.A a = 1;
}

4.2 import public

​ 默認情況下,proto只允許引用直接import的文件中定義的數據類型。如b.proto中導入了a.proto,c.proto中導入了b.proto;默認情況下,c.proto中只能引用b.proto中定義的數據類型,而引用不到a.proto中的數據類型。若c.proto要使用a.proto中定義的數據類型,則b.proto引用a.proto的時候要使用import public。

// a.proto文件
package Ap;

message A{
        optional int32 a = 1;
}
// b.proto文件,使用import public 允許a.proto對b.proto的引用者(c.proto)可見
import public "a.proto";

package Bp;

message B{
        optional Ap.A a = 1;
}
// c.proto
import "b.proto";

package Cp;

message C{
        optional Ap.A a = 1;
}

這種用法在遷移proto文件到新的位置的時候十分有用,如Message類要從old.proto遷移到new.proto文件中,這個時候如果要在不修改對old.proto的文件的情況下,直接將Message移動到new.proto中,然後在old.proto中import public new.proto即可。

5、更新Message消息類型原則

​ 爲了達到前後消息類型兼容的目的,擴展Message消息類型的時候需要注意一下幾點:

1、不要更改任何已有的字段的數值標識。

2、所添加的字段屬性必須是optional 或者repeated類型,如果擴展required類型,會導致舊的消息解析異常

3、非required字段可以移除。要保證它們的標示在新的消息類型中不再使用

4、一個非required的字段可以轉換爲一個擴展,反之亦然——只要它的類型和標識號保持不變。

5、int32, uint32, int64, uint64,和bool是全部兼容的,這意味着可以將這些類型中的一個轉換爲另外一個,而不會破壞向前、 向後的兼容性。如果解析出來的數字與對應的類型不相符,那麼結果就像在C++中對它進行了強制類型轉換一樣(例如,如果把一個64位數字當作int32來 讀取,那麼它就會被截斷爲32位的數字)。

6、sint32和sint64是互相兼容的,但是它們與其他整數類型不兼容。

7、string和bytes是兼容的——只要bytes是有效的UTF-8編碼。

8、嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。

9、fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。

6、protobuf擴展

6.1 extensions&extend

​ protobuf允許Message中預留出一個標識號段用作給第三方擴展使用。當其他人需要在message中擴展新的字段的時候,就不需要直接修改原文件,直接在自己的proto文件中定義該Message的擴展字段即可。(注意:一定要保證不同文件中擴展的標識號不能重複,否則會導致數據不一致的現象)

// base.proto
package base;
message BaseProto{
	optional int32 id = 1;
	extensions 1000 to 2000;
}
// 擴展BaseProto
extend base.BaseProto{
	optional string name = 1000;
}

​ 注意訪問擴展字段的方式與訪問普通字段的方式有所不同,如在C++中對擴展字段設置爲:

base::BaseProto test;
test.SetExtension(name, "wing");

同時還提供了一些其他的訪問操作,如:HasExtension(),ClearExtension(),GetExtension(),MutableExtension(),以及 AddExtension()等。

6.2 嵌套擴展

​ 嵌套擴展即爲可以在另外的類中添加擴展。

// base.proto
package base;
message BaseProto{
	optional int32 id = 1;
	extensions 1000 to 2000;
}

message OtherProto {
	extend BaseProto {
		optional string name = 1000;
	}
}

嵌套擴展在c++中的訪問方式類似,即在訪問擴展字段的時候在字段前加上作用域空間。

base::BaseProto test;
test.SetExtension(OtherProto::name, "wing");

參考鏈接:

protobuf語法詳解

Protobuf3語法詳解

爲什麼 proto3 移除了 required 和 optional?

[譯]Protobuf 語法指南

基於protobuf的RPC實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章