文章目錄
- 引言
- 筑路之備:快速上手ProtoBuf
- 步驟一:創建.proto文件
- ==?件規范==
- ==添加注釋==
- 指定 proto3 語法
- package 聲明符
- 定義消息(message)
- 定義消息字段
- 【定義聯系人 message】
- 字段唯一編號的范圍
- 步驟2:編譯 contacts.proto ?件,?成 C++ ?件
- 編譯命令
- 編譯命令?格式為:
- 編譯 `contacts.proto` ?件命令如下 :
- 【-I 指定搜索目錄】
- 編譯 contact.proto 文件效果
- 【自動生成方法】:
- 【序列化和反序列化方法】:
- 步驟3:序列化與反序列化的使?
- 編譯指令:
- 小結

引言
為何用 “數據秘語” 編織通訊錄?
- 傳統通訊錄數據的 “困境”:文本格式(如
JSON/CSV
)的冗余與解析慢,恰似 “聯絡信箋” 的冗長絮語 ProtoBuf
的 “秘語優勢”:二進制壓縮、跨語言兼容,為通訊錄搭建 “高效聯絡通道”- 本實戰 1.0 的核心目標:用
ProtoBuf
實現 “聯系人數據的序列化存儲與反序列化讀取”,完成基礎聯絡功能
ProtoBuf
的后續學習,將通過項目實戰——書寫通訊錄的方式進行
筑路之備:快速上手ProtoBuf
需求:在快速上?中,會編寫第?版本的通訊錄 1.0。在通訊錄 1.0 版本中,將實現:
- 對?個聯系?的信息使? PB 進?序列化,并將結果打印出來。
- 對序列化后的內容使? PB 進?反序列,解析出聯系?信息并打印出來。聯系?包含以下信息: 姓名、年齡。
通過通訊錄 1.0,我們便能了解使?
ProtoBuf
初步要掌握的內容,以及體驗到ProtoBuf
的完整使?流程
步驟一:創建.proto文件
?件規范
? 創建 .proto ?件時,?件命名應該使?全?寫字?命名,多個字?之間? _ 連接。 例如lower_snake_case.proto 。
? 書寫 .proto ?件代碼時,應使? 2 個空格的縮進。
我們為通訊錄 1.0 新建?件: contacts.proto
添加注釋
向?件添加注釋,可使? // 或者 /* … */
指定 proto3 語法
Protocol Buffers
語?版本3,簡稱proto3
,是.proto
?件最新的語法版本。proto3
簡化了
Protocol Buffers
語?,既易于使?,?可以在更?泛的編程語?中使?。它允許你使?Java,C++,Python
等多種語??成protocol buffer
代碼。
在.proto
?件中,要使?syntax = "proto3";
來指定?件語法為 proto3
,并且必須寫在除去注釋內容的第??。
注意:如果沒有指定,編譯器會使?proto2語法。 在通訊錄 1.0 的 contacts.proto ?件中,可以為?件指定 proto3 語法
syntax = "proto3";
package 聲明符
package
是?個可選的聲明符,能表?.proto
?件的命名空間,在項?中要有唯?性。它的作?是為了避免我們定義的消息出現沖突。
可以理解為這個C++,命令空間,避免多個message
沖突。在通訊錄 1.0 的 contacts.proto ?件中,可以聲明其命名空間,內容如下:
syntax = "proto3";
package contacts;
定義消息(message)
消息(message): 要定義的結構化對象,我們可以給這個結構化對象中定義其對應的屬性內容。
【問題】:為什么要定義消息?
-
在?絡傳輸中,我們需要為傳輸雙?定制協議。定制協議說?了就是定義結構體或者結構化數據,?如,
tcp,udp
報?就是結構化的。 再?如將數據持久化存儲到數據庫時,會將?系列元數據統??對象組織起來,再進?存儲。 -
所以
ProtoBuf
就是以message
的?式來?持我們定制協議字段,后期幫助我們形成類和?法來使?。在通訊錄 1.0 中我們就需要為聯系?
定義?個message。
proto
?件中定義?個消息類型的格式為 :
message 消息類型名{
}
消息類型命令規范:使用駝峰命名法,首字母大寫
定義消息字段
在 message 中我們可以定義其屬性字段,字段定義格式為:字段類型 字段名 = 字段唯?編號
- 字段名稱命名規范:全?寫字?,多個字?之間? _ 連接
- 字段類型分為:標量數據類型 和 特殊類型(包括枚舉、其他消息類型等)
- 字段唯?編號:?來標識字段,不能使用相同的字段編號
【定義聯系人 message】
其中,我們必須要把這個字段編號帶上,跟PB特點:高效,序列化,結果更小有關系。跟Protobuf
編譯原理有關。
message PeopleInfo{ //建議駝峰string name = 1;//姓名 標識字段編號int32 age = 2; //年齡
}
該表格展?了定義于消息體中的標量數據類型,以及編譯.proto
?件之后?動?成的類中與之對應的字段類型。在這?展?了與 C++ 語?對應的類型。 (了解即可)
字段唯一編號的范圍
1 ~ 536,870,911 (2^29 - 1) ,其中 19000 ~ 19999 不可?
19000 ~ 19999 不可?是因為:在 Protobuf 協議的實現中,對這些數進?了預留。如果?要在.proto
?件中使?這些預留標識號,例如將 name
字段的編號設置為19000,編譯時就會報警:
// 消息中定義了如下編號,代碼會告警:
// Field numbers 19,000 through 19,999 are reserved for the protobuf
implementation
string name = 19000;
值得?提的是,范圍為 1 ~ 15
的字段編號需要?個字節進?編碼, 16 ~ 2047
內的數字需要兩個字節進?編碼。編碼后的字節不僅只包含了編號,還包含了字段類型。
注意:所以 1 ~ 15 要?來標記出現?常頻繁的字段,要為將來有可能添加的、頻繁出現的字段預留?些出來
步驟2:編譯 contacts.proto ?件,?成 C++ ?件
注意:在使用過程中,如果沒有"顏色亮光",需要下載Protobuf
插件
編譯命令
編譯命令?格式為:
protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc
是 Protocol Buffer 提供的命令?編譯?具。--proto_path
指定 被編譯的.proto?件所在?錄,可多次指定。可簡寫成 -IIMPORT_PATH 。如不指
定該參數,則在當前?錄進?搜索。當某個.proto ?件 import 其他.proto ?件時,或需要編譯的 .proto ?件不在當前?錄下,這時就要?-I來指定搜索?錄。--cpp_out
= 指編譯后的?件為 C++ ?件。OUT_DIR
編譯后?成?件的?標路徑。path/to/file.proto
要編譯的.proto?件。
編譯 contacts.proto
?件命令如下 :
protoc --cpp_out=. contacts.proto
【-I 指定搜索目錄】
場景:查找contacts.ptoto(文件名)
編譯 contact.proto 文件效果
編譯
contacts.proto
?件后,會?成所選擇語?的代碼,我們選擇的是C++,所以編譯后?成了兩個?件:
contacts.pb.h
contacts.pb.cc
對于編譯?成的 C++ 代碼,包含了以下內容 :
- 對于每個 message ,都會?成?個對應的消息類。
- 在消息類中,編譯器為每個字段提供了獲取和設置?法以及?下其他能夠操作字段的?法。
- 編輯器會針對于每個
.proto
?件?成.h
和.cc
?件,分別?來存放類的聲明與類的實現
【自動生成方法】:
上述的例?中:
- 每個字段都有設置和獲取的?法,
getter
的名稱與?寫字段完全相同,setter
?法以set_
開頭。 - 每個字段都有?個
clear_
?法,可以將字段重新設置回empty
狀態
【序列化和反序列化方法】:
序列化和反序列化方法,不在Message
類中,在消息類的父類MessageLite
中,提供了讀寫消息實例的方法,包括了序列化方法和反序列化方法。Message類繼承MessageLite父類的屬性和方法
注意:
- 序列化的結果為?進制字節序列,???本格式。
- 以上三種序列化的?法沒有本質上的區別,只是序列化后輸出的格式不同,可以供不同的應?場景使?。
- 序列化的 API 函數均為
const
成員函數,因為序列化不會改變類對象的內容, ?是將序列化的結果保存到函數?參指定的地址中。 - 詳細
message API
可以參?完整列表
步驟3:序列化與反序列化的使?
創建?個測試?件 main.cc
,?法中我們實現:
- 對?個聯系?的信息使?
PB
進?序列化
,并將結果打印出來。 - 對序列化后的內容使?
PB
進?反序列
,解析出聯系?信息并打印出來。
main.cc
#include<iostream>
#include"contacts.pb.h"int main()
{std::string people_str;{// 對一個聯系人使用 PB 進行序列化,并將結果打印出來contacts::PeopleInfo people; // 定義對象people.set_name("張三"); // 設置姓名people.set_age(20); // 設置年齡// 使用正確的序化方法if(!people.SerializeToString(&people_str)){std::cerr << "Failed to serialize people." << std::endl;return -1;}std::cout << "Serialized data: " << people_str << std::endl;}{// 對一個聯系人使用 PB 進行反序列化,并將結果打印出來contacts::PeopleInfo people; // 定義對象// 使用正確的反序列化方法if(!people.ParseFromString(people_str)){std::cerr << "Failed to parse people." << std::endl;return -1;}std::cout << "Name: " << people.name() << std::endl; // 獲取姓名std::cout << "Age: " << people.age() << std::endl; // 獲取年齡}return 0;}
編譯指令:
g++ main.cc contacts.pb.cc -o TestPb -std=c++11 -lprotobuf
- -lprotobuf:必加,不然會有鏈接錯誤。
- -std=c++11:必加,使?C++11語法。
執? TestPb
,可以看? people
經過序列化和反序列化后的結果:
由于ProtoBuf
是把聯系?對象序列化成了?進制序列,這?? string
來作為接收?進制序列的容器。所以在終端打印的時候會有換?等?些亂碼顯?。
所以相對于 xml
和 JSON
來說,因為被編碼成?進制,破解成本增?,ProtoBuf
編碼是相對安全的。
- 編寫 .proto ?件,?的是為了定義結構對象(message)及屬性內容。
- 使? protoc 編譯器編譯 .proto ?件,?成?系列接?代碼,存放在新?成頭?件和源?件中。
- 依賴?成的接?,將編譯?成的頭?件包含進我們的代碼中,實現對
.proto
?件中定義的字段進?設置和獲取,和對message
對象進?序列化和反序列化。
小結
當你用 ProtoBuf
的 “數據秘語” 織就通訊錄的存儲與讀取,這座精簡高效的聯絡小筑,便已在你手中落成。
1.0 的啟步札記雖止于此,卻為你推開了數據通信的門,更精彩的 “秘語續篇”,正待后續續寫。