• protobuf反射详解


    本文主要介绍protobuf里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对repeated/extension字段的处理方法没有说明。

    最初是起源于这样一个问题:
    给定一个pb对象,如何自动遍历该对象的所有字段?

    即是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。 
    使用场景上有很多:
    比如json格式字符串的相互转换,或者bigtable里根据pb对象的字段自动写列名和对应的value。

    例如定义了pb messge类型Person如下:

    1. Person person;
    2. person.set_name("yingshin");
    3. person.set_age(21);

    能否将该对象自动转化为json字符串{"name":"yingshin","age":21},或者自动的写hbase里的多列:

    key column-name column-value
    uid name yingshin
    uid age 21

    如果设置了新的字段,比如person.set_email("izualzhy@163.com"),则自动添加新的一列:

    key column-name column-value
    uid email izualzhy@163.com

    答案就是 pb的反射功能

    我们的目标是提供这样两个接口:

    1. //从给定的message对象序列化为固定格式的字符串
    2. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string);
    3. //从给定的字符串按照固定格式还原为原message对象
    4. void parse_message(const std::string& serialized_string, google::protobuf::Message* message);

    接下来逐步介绍下如何实现。

    1. 反射相关接口

    要介绍pb的反射功能,先看一个相关的UML示例图:

    pb-reflection

    各个类以及接口说明:

    1.1 Message

    Person是自定义的pb类型,继承自Message. MessageLite作为Message基类,更加轻量级一些。
    通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。

    1.2 Descriptor

    Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。
    本文中我们主要关注跟字段描述相关的接口,例如:

    1. 获取所有字段的个数:int field_count() const
    2. 获取单个字段描述类型FieldDescriptor的接口有很多个,例如
    1. const FieldDescriptor* field(int index) const;//根据定义顺序索引获取
    2. const FieldDescriptor* FindFieldByNumber(int number) const;//根据tag值获取
    3. const FieldDescriptor* FindFieldByName(const string& name) const;//根据field name获取
    1.3 FieldDescriptor

    FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。
    对于proto定义里的每种类型,都有一种对应的C++类型,例如:

    1. enum CppType {
    2. CPPTYPE_INT32 = 1, //TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32
    3. }

    获取类型的label属性:

    1. enum Label {
    2. LABEL_OPTIONAL = 1, //optional
    3. LABEL_REQUIRED = 2, //required
    4. LABEL_REPEATED = 3, //repeated
    5. MAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.
    6. }

    获取字段的名称:const string& name() const;

    1.4 Reflection

    Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。
    对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。

    例如对于读操作:

    1. virtual int32 GetInt32 (const Message& message,
    2. const FieldDescriptor* field) const = 0;
    3. virtual int64 GetInt64 (const Message& message,
    4. const FieldDescriptor* field) const = 0;

    特殊的,对于枚举和嵌套的message:

    1. virtual const EnumValueDescriptor* GetEnum(
    2. const Message& message, const FieldDescriptor* field) const = 0;
    3. // See MutableMessage() for the meaning of the "factory" parameter.
    4. virtual const Message& GetMessage(const Message& message,
    5. const FieldDescriptor* field,
    6. MessageFactory* factory = NULL) const = 0;

    对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等。

    2. 反射示例

    示例主要是接收任意类型的message对象,遍历解析其中的每个字段、以及对应的值,按照自定义的格式存储到一个string中。同时重新反序列化该string,读取字段以及value,填充到message对象中。例如:

    其中Person是自定义的protobuf message类型,用于设置一些字段验证我们的程序。
    单纯的序列化/反序列化功能可以通过pb自带的SerializeToString/ParseFromString接口完成。这里主要是为了同时展示自动从pb对象里提取field/value,自动根据field/value来还原pb对象这个功能。

    1. int main() {
    2. std::string serialized_string;
    3. {
    4. tutorial::Person person;
    5. person.set_name("yingshin");
    6. person.set_id(123456789);
    7. person.set_email("zhy198606@gmail.com");
    8. ::tutorial::Person_PhoneNumber* phone = person.mutable_phone();
    9. phone->set_type(tutorial::Person::WORK);
    10. phone->set_number("13266666666");
    11. serialize_message(person, &serialized_string);
    12. }
    13. {
    14. tutorial::Person person;
    15. parse_message(serialized_string, &person);
    16. }
    17. return 0;
    18. }

    其中Person定义是对example里的addressbook.proto做了少许修改(修改的原因是本文没有涉及pb里数组的处理)

    1. package tutorial;
    2. message Person {
    3. required string name = 1;
    4. required int32 id = 2; // Unique ID number for this person.
    5. optional string email = 3;
    6. enum PhoneType {
    7. MOBILE = 0;
    8. HOME = 1;
    9. WORK = 2;
    10. }
    11. message PhoneNumber {
    12. required string number = 1;
    13. optional PhoneType type = 2 [default = HOME];
    14. }
    15. optional PhoneNumber phone = 4;
    16. }

    3. 反射实例实现

    3.1 serialize_message

    serialize_message遍历提取message中各个字段以及对应的值,序列化到string中。
    主要思路就是通过Descriptor得到每个字段的描述符:字段名、字段的cpp类型。
    通过Reflection的GetX接口获取对应的value。

    1. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
    2. const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
    3. const google::protobuf::Reflection* reflection = message.GetReflection();
    4. for (int i = 0; i < descriptor->field_count(); ++i) {
    5. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
    6. bool has_field = reflection->HasField(message, field);
    7. if (has_field) {
    8. //arrays not supported
    9. assert(!field->is_repeated());
    10. switch (field->cpp_type()) {
    11. #define CASE_FIELD_TYPE(cpptype, method, valuetype)
    12. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{
    13. valuetype value = reflection->Get##method(message, field);
    14. int wsize = field->name().size();
    15. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    16. serialized_string->append(field->name().c_str(), field->name().size());
    17. wsize = sizeof(value);
    18. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    19. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
    20. break;
    21. }
    22. CASE_FIELD_TYPE(INT32, Int32, int);
    23. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
    24. CASE_FIELD_TYPE(FLOAT, Float, float);
    25. CASE_FIELD_TYPE(DOUBLE, Double, double);
    26. CASE_FIELD_TYPE(BOOL, Bool, bool);
    27. CASE_FIELD_TYPE(INT64, Int64, int64_t);
    28. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
    29. #undef CASE_FIELD_TYPE
    30. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
    31. int value = reflection->GetEnum(message, field)->number();
    32. int wsize = field->name().size();
    33. //写入name占用字节数
    34. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    35. //写入name
    36. serialized_string->append(field->name().c_str(), field->name().size());
    37. wsize = sizeof(value);
    38. //写入value占用字节数
    39. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    40. //写入value
    41. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
    42. break;
    43. }
    44. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
    45. std::string value = reflection->GetString(message, field);
    46. int wsize = field->name().size();
    47. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    48. serialized_string->append(field->name().c_str(), field->name().size());
    49. wsize = value.size();
    50. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    51. serialized_string->append(value.c_str(), value.size());
    52. break;
    53. }
    54. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
    55. std::string value;
    56. int wsize = field->name().size();
    57. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    58. serialized_string->append(field->name().c_str(), field->name().size());
    59. const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
    60. serialize_message(submessage, &value);
    61. wsize = value.size();
    62. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
    63. serialized_string->append(value.c_str(), value.size());
    64. break;
    65. }
    66. }
    67. }
    68. }
    69. }
    3.2 parse_message

    parse_message通过读取field/value,还原message对象。
    主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。

    1. void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {
    2. const google::protobuf::Descriptor* descriptor = message->GetDescriptor();
    3. const google::protobuf::Reflection* reflection = message->GetReflection();
    4. std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;
    5. for (int i = 0; i < descriptor->field_count(); ++i) {
    6. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
    7. field_map[field->name()] = field;
    8. }
    9. const google::protobuf::FieldDescriptor* field = NULL;
    10. size_t pos = 0;
    11. while (pos < serialized_string.size()) {
    12. int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
    13. pos += sizeof(int);
    14. std::string name = serialized_string.substr(pos, name_size);
    15. pos += name_size;
    16. int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
    17. pos += sizeof(int);
    18. std::string value = serialized_string.substr(pos, value_size);
    19. pos += value_size;
    20. std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
    21. field_map.find(name);
    22. if (iter == field_map.end()) {
    23. fprintf(stderr, "no field found. ");
    24. continue;
    25. } else {
    26. field = iter->second;
    27. }
    28. assert(!field->is_repeated());
    29. switch (field->cpp_type()) {
    30. #define CASE_FIELD_TYPE(cpptype, method, valuetype)
    31. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {
    32. reflection->Set##method(
    33. message,
    34. field,
    35. *(reinterpret_cast<const valuetype*>(value.c_str())));
    36. std::cout << field->name() << " " << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;
    37. break;
    38. }
    39. CASE_FIELD_TYPE(INT32, Int32, int);
    40. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
    41. CASE_FIELD_TYPE(FLOAT, Float, float);
    42. CASE_FIELD_TYPE(DOUBLE, Double, double);
    43. CASE_FIELD_TYPE(BOOL, Bool, bool);
    44. CASE_FIELD_TYPE(INT64, Int64, int64_t);
    45. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
    46. #undef CASE_FIELD_TYPE
    47. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
    48. const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
    49. field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
    50. reflection->SetEnum(message, field, enum_value_descriptor);
    51. std::cout << field->name() << " " << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
    52. break;
    53. }
    54. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
    55. reflection->SetString(message, field, value);
    56. std::cout << field->name() << " " << value << std::endl;
    57. break;
    58. }
    59. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
    60. google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
    61. parse_message(value, submessage);
    62. break;
    63. }
    64. default: {
    65. break;
    66. }
    67. }
    68. }
    69. }
  • 相关阅读:
    用户登陆显示cpu、负载、内存信息
    递归算法总结
    Java算法之递归打破及在真实项目中的使用实例
    史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。
    仿饿了么购物车下单效果
    一起来写个酷炫的水波纹浪啊浪界面
    一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片
    使用 CoordinatorLayout 实现复杂联动效果
    这交互炸了(三) :不看后悔!你一定没见过这样的闪屏
    这交互炸了(二):爱范儿是如何让详情页缩小为横向列表的
  • 原文地址:https://www.cnblogs.com/mtcnn/p/9410043.html
Copyright © 2020-2023  润新知