Protobuf_動態訊息-反射

辰令發表於2024-04-12

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

相關文章