什么是.pb.h 和 .pb.cc 文件?
protobuf的核心是一個.proto
文件,我們自定義一個.proto來創建我們的協議數據,然后使用protocol buffer 編譯器工具編譯生成兩個"文件名.pb.cc"
和"文件名.pb.h"
的文件。
Protocol Buffers,是Google公司開發的一種數據格式,類似于XML和json,是一種用于數據傳輸時將數據序列化和反序列化的一個跨平臺(支持目前主流的各種語言)工具庫,可用于數據存儲、通信協議等方面。它不依賴于語言和平臺并且可擴展性極強。
作用:
- Protobuf支持多種編程語言,如Java、C++、Python等。你可以使用protobuf編譯器將
.proto
文件編譯成對應語言的源代碼,從而實現跨平臺、跨語言的數據交換。 - 生成的代碼提供了序列化和反序列化的功能,可以很容易地將數據結構轉換為字節流,或將字節流轉換為數據結構。
- 使用protobuf編譯器從
.proto
文件生成代碼,可以省去手動編寫序列化和反序列化代碼的繁瑣工作,減少出錯的可能性。 - 生成的代碼通常包含數據結構的訪問器(accessor)和修改器(mutator),使得操作數據結構更加方便。
??????? 總結:使用protobuf并通過.proto
文件定義數據協議,然后編譯生成對應的源代碼文件,可以帶來高效性、靈活性、跨平臺性、跨語言支持、自動生成代碼、兼容性、可擴展性以及減少網絡帶寬和存儲空間消耗等好處。這些優點使得protobuf在分布式系統、網絡通信、數據存儲等領域得到了廣泛的應用。并且Protobuf支持向后兼容性和向前兼容性。向后兼容性意味著新版本的程序可以讀取舊版本的數據;向前兼容性意味著舊版本的程序可以選擇性地忽略新版本數據中的新增字段。
??????? 通俗來說:想象一下,你正在開發一個應用,這個應用需要在不同的設備或不同的軟件之間傳輸數據。這些數據可能包含用戶的信息、訂單詳情、圖片描述等等。但是,每臺設備或每種軟件都有它自己的方式來理解和存儲這些數據,這就好像大家都說不同的語言,無法直接溝通。現在,你希望有一種方式,能讓所有的設備或軟件都理解并使用同一種數據格式。這就是protobuf的作用了。
?? .proto
文件就是protobuf的“藍圖”或“模板”。你可以在這個文件里,用簡單明了的語法定義你的數據結構,比如一個用戶信息可能包括姓名、年齡、地址等字段。然后,你可以使用protobuf的編譯器(就像是一個翻譯官)將這個.proto
文件“翻譯”成各種編程語言都可以理解的代碼(比如C++的.pb.cc
和.pb.h
文件)。
????????這些生成的代碼就像是一個“說明書”,它告訴每種語言的程序,如何理解這個數據結構,如何把這個數據結構轉換成可以傳輸的數據(這個過程叫做序列化),以及如何把接收到的數據轉換回原來的數據結構(這個過程叫做反序列化)。
使用protobuf的好處有很多:
- 大家都懂:所有的設備或軟件都可以使用同一種數據格式,就像大家都說同一種語言,溝通起來就方便了。
- 節省資源:protobuf使用了一種緊湊的二進制格式,所以傳輸的數據會更小,節省網絡帶寬,也節省存儲空間。
- 容易修改:如果以后需要添加或修改數據字段,你只需要修改
.proto
文件,然后重新“翻譯”一下就可以了,不需要改動已經寫好的代碼。 - 安全可靠:protobuf在序列化和反序列化的過程中,會對數據進行校驗,確保數據的完整性和安全性。
所以,.proto
文件和protobuf就是幫助我們實現不同設備或軟件之間高效、安全、方便的數據交換的工具。
模板
編寫 Protocol Buffers(protobuf)的 .proto 文件時,通常遵循以下步驟和最佳實踐:
1.指定語法版本:
??? 首先,指定你正在使用的 protobuf 語法版本。目前主要有 proto2 和 proto3 兩種版本。
syntax = "proto3"; // 或者 "proto2"
2.定義包名:
為你的 .proto 文件定義一個包名,這有助于防止不同項目中的消息類型名稱沖突。
package your.package.name;
3.導入其他 .proto 文件:
如果你的 .proto 文件需要使用在其他 .proto 文件中定義的消息類型,你需要使用 import 語句導入它們。
import "other_protos/other.proto";
4.定義消息類型:
在 .proto 文件中定義你的消息類型。消息類型由字段組成,每個字段都有名稱、類型和唯一的標識符(標簽)。
message Person { ?
??? string name = 1; ?
??? int32 id = 2; ?
??? string email = 3; ?
?
??? enum PhoneType { ?
??????? MOBILE = 0; ?
??????? HOME = 1; ?
??????? WORK = 2; ?
??? } ?
?
??? message PhoneNumber { ?
??????? string number = 1; ?
??????? PhoneType type = 2; ?
??? } ?
?
??? repeated PhoneNumber phones = 4; ?
}
5.定義字段規則:
在 proto3 中,字段規則只有 required(已被移除)、optional(也被移除,所有字段默認為可選)和 repeated(表示字段可以重復,即數組)。但在 proto3 中,通常不需要顯式指定 optional,因為所有字段默認就是可選的。
在 proto2 中,你需要明確指定字段是 required、optional 還是 repeated。但請注意,required 字段在 proto3 中已被移除。
6.定義服務(可選):
如果你的 .proto 文件用于定義 gRPC 服務,你可以在其中定義服務及其方法。
service Greeter { ?
??? rpc SayHello (HelloRequest) returns (HelloReply) {} ?
}?
message HelloRequest { ?
??? string name = 1; ?
}?
message HelloReply { ?
??? string message = 1; ?
}
7.使用保留字段:
如果你刪除了某個字段,并且之后可能會重用該字段的標識符,你應該使用 reserved 關鍵字來保留該標識符,以確保不會在未來發生字段標識符沖突。
message Foo { ?
??? reserved 2, 15, 9 to 11; ?
??? reserved "foo", "bar"; ?
?
??? int32 baz = 1; ?
}
8.注釋:
使用 // 添加單行注釋,或使用 /* ... */ 添加多行注釋。
9.編譯 .proto 文件:
使用 Protocol Buffers 編譯器 protoc 將 .proto 文件編譯成你需要的編程語言的源代碼文件。
//bash
protoc --python_out=. your_file.proto
上面的命令將 your_file.proto 編譯成 Python 代碼。根據你的需要,你可以使用不同的插件(如 --cpp_out=、--java_out= 等)來生成不同編程語言的代碼。
測試和驗證:
在將生成的代碼集成到你的項目中之前,確保通過編寫單元測試和集成測試來驗證 .proto 文件的定義和生成的代碼的正確性。
例子:
syntax = "proto2";package InterVariable;
//這定義了一個名為 InterVariable 的包。在生成的 C++ 代碼中,所有消息類都將被放置在這個命名空間內。message VariableList{
required string name = 1;
required int32 row = 2;
required int32 col = 3;
required int32 type = 4;
repeated string value = 5;//把變量的值都用string類型存儲起來,使用stringstream將string類型轉換成需要的類型
}
message VariableResquest{
required int32 option = 1;//0 -> exit 1 -> modify 2-> select 3 -> modify + select
repeated VariableList variable_resquest_modify = 2;
repeated VariableList variable_resquest_select = 3;
}message VariableResponse{
optional string res = 1;//如果是修改的請求,用res返回“Successful!”或者“Failed!"給客戶端
repeated VariableList variable_response = 2;//如果是查詢請求,返回的select的信息即查詢的所有變量組成的鏈表給客戶端
}
消息類型:VariableResquest
option
:一個必需的32位整數字段,用于表示請求的選項。注釋中說明了該字段的可能值及其含義(0 表示退出,1 表示修改,2 表示選擇,3 表示修改和選擇)。variable_resquest_modify
:一個可重復的VariableList
字段,用于包含需要修改的變量列表。variable_resquest_select
:一個可重復的VariableList
字段,用于包含需要選擇的變量列表。
消息類型:VariableResponse
res
:一個可選的字符串字段,用于在修改請求時返回成功或失敗的消息。variable_response
:一個可重復的VariableList
字段,用于在查詢請求時返回查詢到的變量列表。
總結:
????????定義了一個用于處理變量請求和響應的消息結構。VariableList
消息用于表示一個變量,包括其名稱、位置(行和列)、類型以及值。VariableResquest
消息用于表示一個請求,包括請求的類型(修改、選擇等)以及相關的變量列表。VariableResponse
消息用于表示對請求的響應,包括響應消息和(在查詢請求時)返回的變量列表。
相關解釋和說明
在 Protocol Buffers(protobuf)中,字段的修飾符決定了字段的特性。在 proto3
語法中,required
已經被移除,而 optional
字段也沒有明確的修飾符(因為所有字段默認都是可選的)。但在 proto2
語法中,這三個修飾符都存在,并具有以下含義:
-
required(僅在
proto2
中):required
字段表示該字段在序列化消息時必須存在,并且在解析消息時也必須存在。如果消息缺少required
字段,解析將失敗并拋出異常。由于這個嚴格的要求,required
字段在proto3
語法中被移除了,因為proto3
的設計更偏向于簡單性和向后兼容性。 -
optional(在
proto2
中,但在proto3
中默認):optional
字段表示該字段在序列化消息時可以存在,也可以不存在。在解析消息時,如果該字段不存在,它的值將被設置為默認值(對于基本類型,默認值通常是零或空字符串)。在proto3
語法中,所有字段默認都是optional
的,即使沒有顯式指定。 -
repeated:
repeated
字段表示該字段可以包含任意數量的元素(包括零個)。在序列化消息時,這些元素會被重復地寫入。在解析消息時,你可以通過迭代這個字段來獲取所有的元素。repeated
字段常用于表示數組或列表。
三種修飾符的使用場景示例:
1.required
使用場景:當某個字段對于消息來說是必不可少的,如果缺少這個字段,消息就不完整或無效。
例子:假設你正在設計一個表示“人”的消息格式,其中“名字”是一個必須存在的字段。
message Person { required string name = 1; // 名字是必須的 int32 age = 2; // 年齡是可選的 }
在這個例子中,如果序列化一個 Person 消息時沒有包含 name 字段,那么序列化將失敗。同樣,如果嘗試解析一個不包含 name 字段的 Person 消息,解析也會失敗。
2.optional
使用場景:當某個字段對于消息來說是可選的,即使缺少這個字段,消息仍然是完整和有效的。
例子:在上面的 Person
消息中,age
字段是可選的。這意味著你可以序列化一個只有 name
字段的 Person
消息,而不包含 age
字段。
在 proto3
中,由于所有字段默認都是 optional
的,所以你不需要顯式地使用 optional
修飾符。
3.repeated
使用場景:當某個字段可以包含多個值時,例如一個人的多個電話號碼或一個項目的多個任務。
例子:假設你正在設計一個表示“聯系人”的消息格式,其中一個人可以有多個電話號碼。
message PhoneNumber { string number = 1; enum Type { HOME = 0; WORK = 1; MOBILE = 2; } Type type = 2;
} message Person { required string name = 1; repeated PhoneNumber phones = 2; // 一個人可以有多個電話號碼
}