protobuf
protoc 版本
協議檔案 版本
message 訊息中承載的資料分別對應於每一個欄位都有一個名字和一種型別
optional
repeated :在格式正確的訊息中,此欄位型別可以重複零次或多次。系統會保留重複值的順序
欄位規則 欄位型別 欄位名稱=欄位編號[default=0];
欄位型別:
基本資料型別:bool、int32、int64、uint32、uint64、float、double、string、bytes。
標量型別(int、string等),也可以是複合型別(enum等),也可以是其他message
欄位編碼
Protobuf編碼 是透過成員的唯一編號來繫結對應的資料
message成員編號,可以不從1開始,但是不能重複,不能使用19000 - 19999
標識號是[0,2^29-1]範圍內的一個整數
解析資料時:
解析資料時 不同型別的預設值不同
對於列舉,預設值是第一個定義的列舉值,該值必須為0
repeated欄位預設值是空列表
// enum為關鍵字,作用為定義一種列舉型別
enum 定義訊息型別時,可能會希望其中一個欄位有一個預定義的值列表
可以透過enum在訊息定義中新增每個可能值的常量來非常簡單的執行此操作
enum的第一個常量對映為0,
每個列舉定義必須包含一個對映到零的常量作為其第一個元素
不同的列舉常量指定相同的值來定義別名。如果想要使用這個功能必須將allow_alias選項設定為true,負責編譯器將報錯
oneof關鍵字
如果有一個包含許多欄位的訊息,並且最多隻能同時設定其中的一個欄位,則可以使用oneof功能
ProtoBuf編碼和解析
大量已經定義好的proto檔案,其實這些檔案是Protobuf的描述檔案,類似後設資料。
用本身的語法描述本身,同時透過這些檔案生成對應的語言的後設資料類等程式碼
01.描述檔案中最重要的檔案 就是descriptor.proto 這個檔案,
這個檔案是整個proto語法的描述類,描述了實際Protobuf各層次語法的結構
02.其他proto檔案
解析器:
編碼時
解析時:
ParseFromString(serialized_data)
SerializeToString()
###機制
DescriptorPool類根據 type name 拿到一個 Descriptor的物件指標,
在透過MessageFactory工廠類根據Descriptor例項構造出具體的Message物件。
一個當前 Message 物件的 Descriptor 例項,
這個 Descriptor 例項主要儲存 Message 的原始檔 Descriptor 和每個 field 的 Descriptor,
然後透過迴圈的方式對 Message 的每個 field 進行賦值
gRPC(gRPC Remote Procedure Calls)
gRPC預設使用protocol buffers,這是google開源的一套成熟的結構資料序列化機制
服務發現-註冊中心 服務和服務之間呼叫需要使用RPC
protoc生成程式碼時加上引數–descriptor_set_out,輸出型別資訊到一個檔案 (即SelfDescribingMessage的第一個欄位內容)
ProtoBuf
01.靜態訊息 與 動態訊息
動態訊息的使用方式
有兩種方式:
一種是執行時設定需要的欄位名、欄位型別等;
另外一種是執行前定義一個proto檔案設定需要的欄位名、欄位型別等,線上動態編譯這個proto檔案,
並呼叫相關的訊息描述子(Descriptor)、欄位描述子(FieldDescriptor)、反射器(Reflection)去讀、寫相應的欄位
02.Protobuf 動態載入 .proto 檔案並操作 Message
03.Protobuf反射 )google::protobuf::Reflection 反射物件, 透過它 + FieldDescriptor, 能set/get filed物件的值
反射的核心要點是:獲取程式元資訊。
反射機制的關鍵類為Descriptor類
解析proto檔案時,肯定需要先將其解析為抽象語法樹(AST)
FileDescriptorProto 就是我們要找的AST結構
/* 反射建立例項 */
auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("Dog");
auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
auto instance = prototype->New();
/* 反射相關介面 */
auto reflecter = instance.GetReflection();
auto field = descriptor->FindFieldByName("name");
reflecter->SetString(&instance, field, "美") ;
// 獲取屬性的值.
std::cout<<reflecter->GetString(instance , field)<< std::endl ;
return 0 ;
04.非 .proto 檔案
從遠端讀取,如將資料與資料元資訊一同進行 protobuf 編碼並傳輸
05. 利用了
DescriptorPool 從 FileDescriptorProto 解析出 FileDescriptor(描述 .proto 檔案中所有的 messages)。
然後用 DynamicMessageFactory 從 FileDescriptor 裡找到我們關注的那個 message 的MessageDescriptor。
接下來,我們利用 DynamicMessageFactory 根據 MessageDescriptor 得到一個prototype message instance。
proto_desc.proto
apollo/cyber/message/protobuf_factory.cc
RegisterMessage()
apollo/cyber/message.protobuf_factory.h
FileDescriptor (google::protobuf::FileDescriptor) FileDescriptorProto Descriptor DescriptorPool DynamicMessageFactory
ProtoDesc (appllo::cyber::proto::ProtoDesc)
python
from google.protobuf import message_factory ,descriptor_pb2, descriptor_pool
CyberRT
核心類是 Component 和 TimerComponent;
支撐 component 的是 Node、Scheduler、Timer、DataVisitor;
module(模組)和component(元件),在Cyber RT中,一個module可以由多個component組成。
程式碼解析
自身的訊息描述descriptor和它依賴的所有訊息的descriptor,都放入 descriptor_pool,之後就可以根據訊息型別來建立訊息了。
1.record.proto
SingIndex oneof cache{ ChannekCache ChunkBodyCache ChunkHeaderCache}
proto_desc.proto
Channel中的proto_desc反序列化為 Chatter 物件
2.proto_desc_pb2
record_pb2
record_pb2.ChunkHeader() .begin_time .end_time message_number raw_size
record_pb2.ChunkBody() SingleMessage
SingleMessage
message.channel_name
message.time
message.content SerializeToString()
3.chunk <---- cyber.proto
proto_chunk_header
proto_chunk_body
4.common <--- Enum
class Compression
Class Section
Header_length Section_length
chunk_interval chunk_raw_size segment_interval segment_raw_size
5. ---> Reader
google.protobuf
common
cyber.proto
file_object.chunk
record_exception ---> Reader
6.---> Write
google.protobuf
common
cyber.proto
file_object.chunk ---> Write
7. ---> Record
google.protobuf
common
cyber.proto
writer
reader ---> Record
8.---> main
cyber.proto
Record ---> main
反射
01.構造DescriptorPool
02.獲取Descriptor: const Descriptor *descriptor = importer.pool()->FindMessageTypeByName("Pair");
03.透過Descriptor 獲取Message: const Message *message = factory.GetPrototype(descriptor);
參考
cyber record包解析工具 https://zhuanlan.zhihu.com/p/499516617
https://github.com/daohu527/cyber_record/tree/main
Protobuf動態解析那些事兒 https://www.cnblogs.com/jacksu-tencent/p/3447310.html