Protobuf 自动反射消息
1、Protobuf 自动反射消息
参考
这篇文章要解决的问题是:在接收到 protobuf 数据之后,如何自动创建具体的 Protobuf Message 对象,再做的反序列化。
-
“自动”
的意思是:当程序中新增一个protobuf Message
类型时,这部分代码不需要修改,不需要自己去注册消息类型。 -
其实,Google Protobuf 本身具有很强的
反射(reflection)
功能,可以根据type name
创建具体类型的Message 对象
,我们直接利用即可。
2、网络编程中使用 protobuf 的两个问题
Google Protocol Buffers (Protobuf) 是一款非常优秀的库,它定义了一种紧凑的可扩展二进制消息格式,特别适合网络数据传输。它为多种语言提供 binding,大大方便了分布式程序的开发,让系统不再局限于用某一种语言来编写。
在网络编程中使用 protobuf 需要解决两个问题:
-
长度
,protobuf 打包的数据没有自带长度信息
或终结符
,需要由应用程序自己在发生和接收的时候做正确的切分; -
类型
,protobuf打包的数据没有自带类型信息
,需要由发送方把类型信息传给给接收方,接收方创建具体的Protobuf Message
对象,再做的反序列化。
3、根据 type name 反射自动创建 Message 对象
Google Protobuf 本身具有很强的反射(reflection)功能,可以根据 type name 创建具体类型的 Message 对象。
我估计大家通常关心和使用的是图的左半部分:MessageLite、Message、Generated Message Types (Person, AddressBook) 等,而较少注意到图的右半部分:Descriptor, DescriptorPool, MessageFactory。
上图中,其关键作用的是 Descriptor class,每个具体 Message Type 对应一个 Descriptor 对象。尽管我们没有直接调用它的函数,但是Descriptor在“根据 type name 创建具体类型的 Message 对象”中扮演了重要的角色,起了桥梁作用。上图的红色箭头描述了根据 type name 创建具体 Message 对象的过程,后文会详细介绍。
4、原理简述
Protobuf Message class 采用了 prototype pattern,Message class 定义了 New() 虚函数,用以返回本对象的一份新实例,类型与本对象的真实类型相同。也就是说,拿到 Message* 指针
,不用知道它的具体类型
,就能创建和它类型一样的具体 Message Type 的对象
。
每个具体 Message Type 都有一个 default instance
,可以通过 ConcreteMessage::default_instance()
获得,也可以通过 MessageFactory::GetPrototype(const Descriptor*) 来获得。所以,现在问题转变为
- 如何拿到
MessageFactory
; - 如何拿到
Descriptor*
。
当然,ConcreteMessage::descriptor()
返回了我们想要的 Descriptor*
,但是,在不知道 ConcreteMessage
的时候,如何调用它的静态成员函数呢?这似乎是个鸡与蛋的问题。
我们的英雄是 DescriptorPool
,它可以根据 type name
查到 Descriptor*
,只要找到合适的 DescriptorPool
,再调用 DescriptorPool::FindMessageTypeByName(const string& type_name)
即可。眼前一亮?
5、根据 type name 自动创建 Message 的关键代码
- 用
DescriptorPool::generated_pool()
找到一个DescriptorPool对象
,它包含了程序编译的时候所链接的全部protobuf Message types
。 - 用
DescriptorPool::FindMessageTypeByName()
根据type name
查找Descriptor
。 - 再用
MessageFactory::generated_factory()
找到MessageFactory对象
,它能创建程序编译的时候所链接的全部protobuf Message types
。 - 然后,用
MessageFactory::GetPrototype()
找到具体Message Type
的default instance
。 - 最后,用
prototype->New()
创建对象。
inline google::protobuf::Message* createMessage(const std::string& type_name)
{
google::protobuf::Message* message = NULL;
const google::protobuf::Descriptor* descriptor =
google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name);
if (descriptor)
{
const google::protobuf::Message* prototype =
google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
if (prototype)
{
message = prototype->New();
}
}
return message;
}
参考
1、https://www.cnblogs.com/Solstice/archive/2011/04/03/2004458.html
2、http://www.cppblog.com/sunicdavy/archive/2011/12/14/162115.html