欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++開(kāi)發(fā)protobuf動(dòng)態(tài)解析工具

 更新時(shí)間:2023年01月03日 15:32:58   作者:碼小方  
這篇文章主要為大家介紹了C++開(kāi)發(fā)protobuf動(dòng)態(tài)解析工具實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

為什么需要這個(gè)工具

數(shù)據(jù)庫(kù)中存儲(chǔ)的protobuf序列化的內(nèi)容,有時(shí)候查問(wèn)題想直接解析查看內(nèi)容。很多編碼在網(wǎng)上很容易找到編解碼工具,但protobuf沒(méi)有找到編解碼工具,可能這樣的需求比較少吧,那就自己用C++實(shí)現(xiàn)一個(gè)。

需求描述

我們知道,要解析protobuf,需要有proto定義,所以我們的輸入?yún)?shù)需要包含序列化的數(shù)據(jù)以及proto定義,如果proto中包含多個(gè)message,還需要指定解析到哪個(gè)message。所以一共是三個(gè)輸入?yún)?shù)。

此外,為了方便使用,我們的工具不要求給出完整的proto定義,如果有嵌套的message沒(méi)有定義,不應(yīng)影響其他字段解析。

搜索現(xiàn)成方案

網(wǎng)上搜索了一圈,找到的類(lèi)似方案大多需要導(dǎo)入完整的proto文件:

int DynamicParseFromPBFile(const std::string& file, const std::string& classname, 
      const std::string& pb_str) {
  // ...
  // 導(dǎo)入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);
  // 動(dòng)態(tài)創(chuàng)建message對(duì)象
  auto msg = message->New();
  msg->ParseFromString(pb_str);
  // msg即為解析到的結(jié)構(gòu)
}

這樣可以實(shí)現(xiàn)動(dòng)態(tài)解析,但仍不滿(mǎn)足我們的需求——即使proto不完整,也希望能解析。

舉個(gè)例子:

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

MyMsg中包含OtherMsg類(lèi)型,但并沒(méi)有給出OtherMsg的定義,所以無(wú)法正常解析。

AST在哪里

事實(shí)上,在解析proto文件時(shí),肯定需要先將其解析為抽象語(yǔ)法樹(shù)(AST),在AST中,我們可以很容易修改proto的定義,例如將other字段刪掉,或者將其類(lèi)型改為bytes,這樣就可以正常解析了。

那么,proto文件解析成的AST結(jié)構(gòu)在哪里呢?只能從源碼中尋找答案了。

一番查找后,終于看到了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();
}

從這段代碼中可以看到,F(xiàn)ileDescriptorProto就是我們要找的AST結(jié)構(gòu)。那么這到底是個(gè)什么結(jié)構(gòu)呢?

其實(shí),F(xiàn)ileDescriptorProto本身也是一個(gè)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;
  // ...
}

從它的字段中可以看到,其代表的是整個(gè)proto文件,包括文件中的所有message、enum等定義。

開(kāi)始寫(xiě)代碼

第一步

仿照上面的源碼,將輸入的proto定義解析為FileDescriptorProto對(duì)象:

// 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對(duì)象,將沒(méi)有給定義的字段類(lèi)型都改成bytes,保證proto可以正常解析:

int ConvertUnknownType2Bytes(FileDescriptorProto& file_descriptor_proto) {
  // 找出所有給出定義的message類(lèi)型名
  set<string> typename_set;
  for (auto const& msgtype : file_descriptor_proto.message_type()) {
    typename_set.insert(msgtype.name());
    // message內(nèi)嵌套定義的message也要包含在內(nèi)
    for (auto const& subtype : msgtype.nested_type()) {
      typename_set.insert(subtype.name());
    }
  }
  // 遍歷所有field,檢查其類(lèi)型是否存在定義
  for (auto& msgtype : *file_descriptor_proto.mutable_message_type()) {
    for (auto& field : *msgtype.mutable_field()) {
      auto type_name = field.type_name();
      // 基本類(lèi)型的type_name是空的
      if (!type_name.empty()) {
        // 如果typename_set中找不到該類(lèi)型名,則轉(zhuǎn)為bytes類(lèi)型
        if (typename_set.find(type_name) == typename_set.end()) {
          field.clear_type_name();
          field.set_type(FieldDescriptorProto_Type_TYPE_BYTES);
        }
      }
    }
  }
  return 0;
}

第3步

解析修改后的FileDescriptorProto對(duì)象,創(chuàng)建指定message類(lèi)型對(duì)象。

// 解析proto并檢查錯(cuò)誤
SimpleDescriptorDatabase db;
db.Add(output);
DescriptorPool pool(&db);
auto descriptor = pool.FindMessageTypeByName(msg_type_name);
if (descriptor == nullptr) {
  // proto結(jié)構(gòu)有錯(cuò)
  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步

將序列化的數(shù)據(jù)解析到msg中:

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

這樣,我們就成功實(shí)現(xiàn)了動(dòng)態(tài)解析,也成功將不可讀的二進(jìn)制數(shù)據(jù)serilized_pb以可讀的形式打印出來(lái)了。

總結(jié)

我們?yōu)榱藢?shí)現(xiàn)動(dòng)態(tài)解析不完整的proto,我們首先從源碼中找到了將proto定義轉(zhuǎn)化為AST——也就是FileDescriptorProto——的方法。

接著,我們將AST對(duì)象進(jìn)行修改,將不合法的proto改成合法的。

最后,我們?cè)倮眯薷暮蟮腇ileDescriptorProto構(gòu)造出需要的message對(duì)象,解析序列化的數(shù)據(jù)。

以上就是C++開(kāi)發(fā)protobuf動(dòng)態(tài)解析工具的詳細(xì)內(nèi)容,更多關(guān)于C++ protobuf動(dòng)態(tài)解析工具的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語(yǔ)言中的sscanf()函數(shù)使用

    C語(yǔ)言中的sscanf()函數(shù)使用

    本文主要介紹了C語(yǔ)言中的sscanf()函數(shù)使用,sscanf通常被用來(lái)解析并轉(zhuǎn)換字符串,可以實(shí)現(xiàn)很強(qiáng)大的字符串解析功能,下面就一起來(lái)了解一下
    2023-05-05
  • C++?Boost?Format超詳細(xì)講解

    C++?Boost?Format超詳細(xì)講解

    Boost是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)。Boost庫(kù)是一個(gè)可移植、提供源代碼的C++庫(kù),作為標(biāo)準(zhǔn)庫(kù)的后備,是C++標(biāo)準(zhǔn)化進(jìn)程的開(kāi)發(fā)引擎之一,是為C++語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供擴(kuò)展的一些C++程序庫(kù)的總稱(chēng)
    2022-11-11
  • C語(yǔ)言實(shí)現(xiàn)繪制可愛(ài)的橘子鐘表

    C語(yǔ)言實(shí)現(xiàn)繪制可愛(ài)的橘子鐘表

    這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)繪制可愛(ài)的橘子鐘表,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下
    2022-12-12
  • C++詳細(xì)分析lambda表達(dá)式的本質(zhì)

    C++詳細(xì)分析lambda表達(dá)式的本質(zhì)

    Lambda表達(dá)式是現(xiàn)代C++在C ++ 11和更高版本中的一個(gè)新的語(yǔ)法糖 ,在C++11、C++14、C++17和C++20中Lambda表達(dá)的內(nèi)容還在不斷更新。 lambda表達(dá)式(也稱(chēng)為lambda函數(shù))是在調(diào)用或作為函數(shù)參數(shù)傳遞的位置處定義匿名函數(shù)對(duì)象的便捷方法
    2022-06-06
  • C++連接mysql數(shù)據(jù)庫(kù)的兩種方法小結(jié)

    C++連接mysql數(shù)據(jù)庫(kù)的兩種方法小結(jié)

    這篇文章主要介紹了C++連接mysql數(shù)據(jù)庫(kù)的兩種方法小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • C語(yǔ)言簡(jiǎn)明講解歸并排序的應(yīng)用

    C語(yǔ)言簡(jiǎn)明講解歸并排序的應(yīng)用

    這篇文章主要介紹了 c語(yǔ)言排序之歸并排序,歸并就是把兩個(gè)或多個(gè)序列合并,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • C語(yǔ)言之直接插入排序算法的方法

    C語(yǔ)言之直接插入排序算法的方法

    這篇文章主要為大家介紹了C語(yǔ)言直接插入排序算法的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2021-12-12
  • QT實(shí)現(xiàn)將兩個(gè)時(shí)間相加的算法[hh:?mm?+?hh:?mm]的示例代碼

    QT實(shí)現(xiàn)將兩個(gè)時(shí)間相加的算法[hh:?mm?+?hh:?mm]的示例代碼

    本文主要介紹了QT實(shí)現(xiàn)將兩個(gè)時(shí)間相加的算法[hh:?mm?+?hh:?mm]的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • C語(yǔ)言中位域的使用詳解

    C語(yǔ)言中位域的使用詳解

    位域是C語(yǔ)言中的一種高級(jí)功能,允許程序員為結(jié)構(gòu)體的成員分配特定數(shù)量的位,本文主要為大家介紹了位域的使用以及優(yōu)缺點(diǎn),希望對(duì)大家有所幫助
    2023-07-07
  • C/C++中指針的深入理解

    C/C++中指針的深入理解

    指針在 C\C++ 語(yǔ)言中是很重要的內(nèi)容,并且和指針有關(guān)的內(nèi)容一向令初學(xué)者頭大,這篇文章主要給大家介紹了關(guān)于C/C++中指針的相關(guān)資料,需要的朋友可以參考下
    2021-07-07

最新評(píng)論