我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++开发protobuf动态解析工具

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++开发protobuf动态解析工具

为什么需要这个工具

数据库中存储的protobuf序列化的内容,有时候查问题想直接解析查看内容。很多编码在网上很容易找到编解码工具,但protobuf没有找到编解码工具,可能这样的需求比较少吧,那就自己用C++实现一个。

需求描述

我们知道,要解析protobuf,需要有proto定义,所以我们的输入参数需要包含序列化的数据以及proto定义,如果proto中包含多个message,还需要指定解析到哪个message。所以一共是三个输入参数。

此外,为了方便使用,我们的工具不要求给出完整的proto定义,如果有嵌套的message没有定义,不应影响其他字段解析。

搜索现成方案

网上搜索了一圈,找到的类似方案大多需要导入完整的proto文件:

int DynamicParseFromPBFile(const std::string& file, const std::string& classname, 
      const std::string& pb_str) {
  // ...
  // 导入proto文件
  ::google::protobuf::compiler::Importer importer(&sourceTree, NULL);
  importer.Import(file);
  // 找到要解析的message
  auto descriptor = importer.pool()->FindMessageTypeByName(classname);
  ::google::protobuf::DynamicMessageFactory factory;
  auto message = factory.GetPrototype(descriptor);
  // 动态创建message对象
  auto msg = message->New();
  msg->ParseFromString(pb_str);
  // msg即为解析到的结构
}

这样可以实现动态解析,但仍不满足我们的需求——即使proto不完整,也希望能解析。

举个例子:

message MyMsg {
  optional uint64 id = 1;
  optional OtherMsg other = 2;
}

MyMsg中包含OtherMsg类型,但并没有给出OtherMsg的定义,所以无法正常解析。

AST在哪里

事实上,在解析proto文件时,肯定需要先将其解析为抽象语法树(AST),在AST中,我们可以很容易修改proto的定义,例如将other字段删掉,或者将其类型改为bytes,这样就可以正常解析了。

那么,proto文件解析成的AST结构在哪里呢?只能从源码中寻找答案了。

一番查找后,终于看到了FindFileByName方法的这段代码:

bool SourceTreeDescriptorDatabase::FindFileByName(const std::string& filename,
                                                  FileDescriptorProto* output) {
  // ...
  io::Tokenizer tokenizer(input.get(), &file_error_collector);
  Parser parser;
  // Parse it.
  output->set_name(filename);
  return parser.Parse(&tokenizer, output) && !file_error_collector.had_errors();
}

从这段代码中可以看到,FileDescriptorProto就是我们要找的AST结构。那么这到底是个什么结构呢?

其实,FileDescriptorProto本身也是一个proto定义的message:

message FileDescriptorProto {
  optional string name = 1;     // file name, relative to root of source tree
  optional string package = 2;  // e.g. "foo", "foo.bar", etc.
  // All top-level definitions in this file.
  repeated DescriptorProto message_type = 4;
  repeated EnumDescriptorProto enum_type = 5;
  repeated ServiceDescriptorProto service = 6;
  repeated FieldDescriptorProto extension = 7;
  // ...
}

从它的字段中可以看到,其代表的是整个proto文件,包括文件中的所有message、enum等定义。

开始写代码

第一步

仿照上面的源码,将输入的proto定义解析为FileDescriptorProto对象:

// proto输入
istringstream ss(proto);
istream* is = &ss;
io::IstreamInputStream input(is);
// 解析到FileDescriptorProto AST
io::Tokenizer tokenizer(&input, nullptr);
FileDescriptorProto output;
compiler::Parser parser;
if (!parser.Parse(&tokenizer, &output)) {
  err_msg = "parse proto failed";
  return -1;
}
output.set_name("proto");
output.clear_source_code_info();
printf("MSG: proto parsed output: %s\n", output.DebugString().c_str());

第2步

处理FileDescriptorProto对象,将没有给定义的字段类型都改成bytes,保证proto可以正常解析:

int ConvertUnknownType2Bytes(FileDescriptorProto& file_descriptor_proto) {
  // 找出所有给出定义的message类型名
  set<string> typename_set;
  for (auto const& msgtype : file_descriptor_proto.message_type()) {
    typename_set.insert(msgtype.name());
    // message内嵌套定义的message也要包含在内
    for (auto const& subtype : msgtype.nested_type()) {
      typename_set.insert(subtype.name());
    }
  }
  // 遍历所有field,检查其类型是否存在定义
  for (auto& msgtype : *file_descriptor_proto.mutable_message_type()) {
    for (auto& field : *msgtype.mutable_field()) {
      auto type_name = field.type_name();
      // 基本类型的type_name是空的
      if (!type_name.empty()) {
        // 如果typename_set中找不到该类型名,则转为bytes类型
        if (typename_set.find(type_name) == typename_set.end()) {
          field.clear_type_name();
          field.set_type(FieldDescriptorProto_Type_TYPE_BYTES);
        }
      }
    }
  }
  return 0;
}

第3步

解析修改后的FileDescriptorProto对象,创建指定message类型对象。

// 解析proto并检查错误
SimpleDescriptorDatabase db;
db.Add(output);
DescriptorPool pool(&db);
auto descriptor = pool.FindMessageTypeByName(msg_type_name);
if (descriptor == nullptr) {
  // proto结构有错
  err_msg = "parse proto failed. FindMessageTypeByName result is null";
  return -1;
}
DynamicMessageFactory factory;
auto message = factory.GetPrototype(descriptor);
unique_ptr<Message> msg(message->New());

第4步

将序列化的数据解析到msg中:

msg->ParseFromString(serilized_pb);
cout << "proto msg: " << msg->ShortDebugString().c_str() << endl;

这样,我们就成功实现了动态解析,也成功将不可读的二进制数据serilized_pb以可读的形式打印出来了。

总结

我们为了实现动态解析不完整的proto,我们首先从源码中找到了将proto定义转化为AST——也就是FileDescriptorProto——的方法。

接着,我们将AST对象进行修改,将不合法的proto改成合法的。

最后,我们再利用修改后的FileDescriptorProto构造出需要的message对象,解析序列化的数据。

以上就是C++开发protobuf动态解析工具的详细内容,更多关于C++ protobuf动态解析工具的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++开发protobuf动态解析工具

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++开发protobuf动态解析工具

这篇文章主要为大家介绍了C++开发protobuf动态解析工具实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-01-03

利用C++开发一个protobuf动态解析工具

数据库中存储的protobuf序列化的内容,有时候查问题想直接解析查看内容。很多编码在网上很容易找到编解码工具,但protobuf没有找到编解码工具,可能这样的需求比较少吧,那就自己用C++实现一个,感兴趣的可以了解一下
2023-01-03

动态域名解析工具有哪些

动态域名解析工具包括但不限于以下几种:1. DynDNS:DynDNS是一种基于云的动态DNS服务,它可以让用户通过一个固定的域名来访问他们的动态IP地址。DynDNS提供了一个简单的界面,让用户可以轻松地管理他们的动态IP地址。2. No
2023-06-11

免费动态域名解析工具有哪些

以下是一些免费的动态域名解析工具:1. Dynu DNS:提供免费的动态 DNS 服务,支持多种操作系统和设备。2. No-IP:提供免费的动态 DNS 服务,支持多种操作系统和设备。3. Duck DNS:提供免费的动态 DNS 服务,支
2023-06-12

探索Go语言生态:流行开发工具分析

Go语言作为一种高效、简洁的编程语言,其在近年来越来越受到开发者的关注和青睐。随着Go语言的流行,其生态圈也日益丰富,涵盖了许多优秀的开发工具和框架,为开发者提供了更多便利和支持。本文将带领读者深入了解Go语言生态圈中的一些热门开发工具,并
探索Go语言生态:流行开发工具分析
2024-02-25

解决使用idea,phpstorm等开发工具git自动变为修改状态

windows下默认idea换行符linux下默认默认idea换行符解决跨平台默认idea换行符问题提交时转换为 LF 检出时转换为 CRLF (推荐windo
解决使用idea,phpstorm等开发工具git自动变为修改状态
2022-06-15

CMS插件开发 的自动化工具:解放开发者双手

CMS 插件开发自动化工具旨在节省开发人员的时间和精力,通过自动执行重复性任务和提供智能见解,提升插件开发效率。
CMS插件开发 的自动化工具:解放开发者双手
2024-02-15

python开发的自动化运维工具ansible详解

目录ansible 简介ansible 是什么?ansible 特点ansible 架构图ansible 任务执行ansible 任务执行模式ansible 执行流程ansible 命令执行过程ansible 配置详解ansible 安装方
2022-06-02

如何解析移动应用的跨平台开发工具Xamarin和React Native

这篇文章跟大家分析一下“如何解析移动应用的跨平台开发工具Xamarin和React Native”。内容详细易懂,对“如何解析移动应用的跨平台开发工具Xamarin和React Native”感兴趣的朋友可以跟着小编的思路慢慢深入来阅读一下
2023-06-17

C++移动应用程序开发中常用的工具和资源

在 c++++ 中开发移动应用程序需要工具和资源,包括 qt framework、cmake、ndk、xcode,以及第三方库。本文提供了实战案例,展示了如何使用这些工具和资源创建一个 android 应用程序,并在代码中使用第三方库。C+
C++移动应用程序开发中常用的工具和资源
2024-05-10

C#开发中如何使用远程调试和性能分析工具

C#开发中如何使用远程调试和性能分析工具引言:在C#开发过程中,远程调试和性能分析工具可以帮助我们解决一些难以调试的问题和优化程序性能。本文将详细介绍如何使用远程调试工具和性能分析工具,并提供具体的代码示例。一、远程调试工具远程调试工具允许
2023-10-22

C语言驱动开发内核枚举IoTimer定时器解析

这篇文章主要为大家介绍了C语言驱动开发内核枚举IoTimer定时器解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

打开微信web开发者工具后卡在加载项目的界面不动如何解决

今天小编给大家分享一下打开微信web开发者工具后卡在加载项目的界面不动如何解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
2023-06-26

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录