Protobuf_动态消息-反射

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

Python

解析 pb 的数据
 def load_protobuf_from_file(container, filename)
     load_form_text_file()      from google.protobuf import text_format  text_format.Parse
	 load_from_binary_file()   obj.ParseFromString()
	 
	google.protobuf.text_format.PrintMessage()
    google.protobuf.text_format.Merge		
参考: https://github.com/microsoft/MMdnn/blob/master/mmdnn/conversion/common/IR/IR_graph.py  	 
def load_protobuf_from_file(container, filename):
    with open(filename, 'rb') as fin:
        file_content = fin.read()
    # First try to read it as a binary file.
    try:
        container.ParseFromString(file_content)
        return container
    except Exception as e:  # pylint: disable=broad-except
        print ("Info: Trying to parse file [%s] with binary format but failed with error [%s]." % (filename, str(e)))
    # Next try to read it as a text file.
    try:
        from google.protobuf import text_format
        text_format.Parse(file_content.decode('UTF-8'), container, allow_unknown_extension=True)
    except text_format.ParseError as e:
        raise IOError("Cannot parse file %s: %s." % (filename, str(e)))
    return container
###使用Python脚本自动编译proto文件	
     protoc --python_out=. example.proto
	
   方式一:		
	    根据proto文件编译生成的类来反序列化消息
	方式二:
	     ProtoBuf提供了动态解析机制来
           通过proto文件生成的descriptor来构造动态消息类,然后反序列化(解析)消息
	
   https://blog.51cto.com/u_16175463/9923630	

C++

proto::object.SerializeToString(&rspBuf) proto::object.ParseFromString(buf)  一对关系
proto::object.Utf8DebugString()  google::protobuf::TextFormat::ParseFromString
proto::object.SerializeToOstream() proto::object.ParseFromIstream()
google::protobuf::TextFormat::Print()  google::protobuf::TextFormat::Parser()
                            PrintToString  ParseFromString
  ParsePartialFromFileDescriptor
  ParsePartialFromIstream
参考: Protobuf读写Prototxt  https://zhuanlan.zhihu.com/p/423086300 

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
posted @ 2024-04-12 18:09  辰令  阅读(139)  评论(0编辑  收藏  举报