由于项目需要,最近在研究protobuf消息协议,关于protobuf协议,基础使用教程这里我就不想多说;度娘,谷哥都能找到大把,就不做太多解释。而关于protobuf动态自动反射消息的使用,这里可以参考陈硕的实现:
这里主要介绍一种在项目上使用的protobuf自己定义描述消息,FileDescriptorSet的使用,搜了好多文章大家只是一笔带过,至于怎么使用并没有给出详细说明。
项目场景:
由于开发时在通信接口协议层面中的.proto文件,可能会在后续扩展时更改,而按常规的使用方法,proto文件一旦更改后,整个程序又需要重新编译。那有没一种方法能在将proto文件像配置文件一样使用,与整个程序剥离开来。当接口层更新直接更新配置文件而应用程序不用再次编译更新? 答案当然是有的,就是使用protobuf的FileDescriptorSet来实现。
实现
首先,定义一个自己定义描述消息的config.proto文件,也就是动态自定义的关键,实例实现如下:
message SelfDescribingMessage {
// Set of .proto files which define the type.
required FileDescriptorSet proto_files = 1;
// Name of the message type. Must be defined by one of the files in
// proto_files.
required string type_name = 2;
// The message data.
required bytes message_data = 3;
}
required FileDescriptorSet proto_files = 1; 这个是一定不能少的,也是实现的关键,至于SelfDescribingMessage里面其它的数据成员,可以根据自己的需求来加。
第二,实现自己通信协议接口的messages.proto文件,这个文件就是所有可扩展通信数据消息体,这里简单的写几个示例:
message ApplySettings {
optional string language = 1;
optional string label = 2;
optional bool use_passphrase = 3;
optional bytes homescreen = 4;
}
message Success {
optional string message = 1;
}
message Failure {
optional FailureType code = 1;
optional string message = 2;
}
message XXX {
XXXX //这里是以后可能扩展的消息
}
第三,用户protoc工具,将通信协议接口messages.proto生成配置文件。这里大家用的最多的是用protoc –cpp_out、–java_out、–python_out生成C++、Java或者Python相关的代码,但大家很少研究–descriptor_set_out=FILE这个参数–descriptor_set_out=FILE Writes a FileDescriptorSet (a protocol buffer,defined in descriptor.proto) containing all of the input files to FILE。
使用此参数时请配合google/protobuf/descriptor.proto文件一起使用protoc (path)/descriptor.proto messages.proto –descriptor_set_out=messages.cfg
到这一步就会将上面的messages.proto文件生成配置文件,这里也需要将config.proto文件按你本人需要生成C++、Java或者Python相关的代码。
最后,万事具备,只差东风了。现在我们来使用怎么用。
// load config file
std::string cpath("./messages.cfg");
std::ifstream config(cpath, std::ios::in | std::ios::binary);
// parse to FileDescriptorSet
protobuf::pb::FileDescriptorSet descriptor_set;
descriptor_set.ParseFromIstream(&config);
DescriptorPool descriptor_pool;
for (int i = 0; i < descriptor_set.file_size(); i++) {
descriptor_pool.BuildFile(descriptor_set.file(i));
}
上面是运用的核心代码,至于后面的应用就不在详细写了,需要用反射或者其它方法就自己行发挥了。