描述一种message类型(不是一个单独的message对象)的meta信息。构造函数是private类型,必须通过DescriptorPool(friend类)来构造。
const FileDescriptor* file_
: 描述message所在的.proto文件信息const Descriptor* containing_type_
:如果在proto定义中,这个message是被其它message所包含,那么这个字段是上一级message的descriptor*
;如果没有被包含,那么是NULLconst MessageOptions* options_
: 定义在descriptor.proto,从注释看是用来和老版本proto1中MessageSet做拓展,可以先不去关注涉及extension的部分。int field_count_
:当前field包含的field的个数FieldDescriptor* fields_
: 以连续数组方式保存的所有的fiedsint nested_type_count_
: 嵌套类型数量Descriptor* nested_types_
: message中嵌套messageint enum_type_count_
: 内部enum的个数EnumDescriptor* enum_types_
: enum类型的连续内存起始地址描述整个.proto文件信息,其中包含:
依赖.proto文件信息:
int dependency_count_;
const FileDescriptor** dependencies_;
当前.proto文件包含的message信息:
int message_type_count_;
Descriptor* message_types_;
当前.proto文件包含的所有symbol (各种descriptor)的tables:
const FileDescriptorTables* tables_;
描述一个单独的field,构造函数为private,也必须由DescriptorPool
(friend类)构造。通过包含这个field的message的descriptor的函数(Descriptor::FindFieldByName()
)获得。
enum Type : field类型;
enum CppType: cpp中field类型,CppType和Type类型映射关系是固定的;
enum Label :标记field的存在性类型(optional/required/repeated);
const Descriptor* containing_type_;
const Descriptor* extension_scope_;
const Descriptor* message_type_;
const EnumDescriptor* enum_type_;
const FieldDescriptor* experimental_map_key_;
const FieldOptions* options_;
static const CppType kTypeToCppTypeMap[MAX_TYPE + 1];
static const char * const kTypeToName[MAX_TYPE + 1];
static const char * const kLabelToName[MAX_LABEL + 1];
在descriptor.cc中,实现对外暴露数据的函数时,为了提高代码可读性,使用了如下宏的方式:
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, default_value_int32 , int32 )
PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, has_default_value, bool)
PROTOBUF_DEFINE_ACCESSOR
的定义如下:
// These macros makes this repetitive code more readable.
#define PROTOBUF_DEFINE_ACCESSOR(CLASS, FIELD, TYPE) \
inline TYPE CLASS::FIELD() const { return FIELD##_; }
因为FieldDescriptor自己包含如下union数据成员,用来表示不同TYPE类型数据的default值:
private:
bool has_default_value_;
union {
int32 default_value_int32_;
int64 default_value_int64_;
uint32 default_value_uint32_;
uint64 default_value_uint64_;
float default_value_float_;
double default_value_double_;
bool default_value_bool_;
const EnumValueDescriptor* default_value_enum_;
const string* default_value_string_;
};
描述在.proto文件中定义的enum类型
针对protobuf中7种类型的descriptor的一个封装。
编程上,也适用union来适配不同类型的descriptor:
Type type;
union {
const Descriptor* descriptor;
const FieldDescriptor* field_descriptor;
const EnumDescriptor* enum_descriptor;
const EnumValueDescriptor* enum_value_descriptor;
const ServiceDescriptor* service_descriptor;
const MethodDescriptor* method_descriptor;
const FileDescriptor* package_file_descriptor;
};
提高代码可读性上,使用宏的方式:
#define CONSTRUCTOR(TYPE, TYPE_CONSTANT, FIELD) \
inline explicit Symbol(const TYPE* value) { \
type = TYPE_CONSTANT; \
this->FIELD = value; \
}
CONSTRUCTOR(Descriptor , MESSAGE , descriptor )
CONSTRUCTOR(FieldDescriptor , FIELD , field_descriptor )
CONSTRUCTOR(EnumDescriptor , ENUM , enum_descriptor )
CONSTRUCTOR(EnumValueDescriptor, ENUM_VALUE, enum_value_descriptor )
CONSTRUCTOR(ServiceDescriptor , SERVICE , service_descriptor )
CONSTRUCTOR(MethodDescriptor , METHOD , method_descriptor )
CONSTRUCTOR(FileDescriptor , PACKAGE , package_file_descriptor)
#undef CONSTRUCTOR
各种数据表的集合,封装 了一系列的hashmap结构。
注意这个类是descriptor.h文件中在类DescriptorPool
的private成员中声明的,所以是类DescriptorPool
内部的数据结构,
封装的一系列的hashmap:
typedef pair<const void*, const char*> PointerStringPair;
//这里是将message对应的descriptor地址和int组合在一起,指定descriptor中的某一个field
typedef pair<const Descriptor*, int> DescriptorIntPair;
typedef pair<const EnumDescriptor*, int> EnumIntPair;
typedef hash_map<const char*, Symbol,
hash<const char*>, streq>
SymbolsByNameMap;
typedef hash_map
SymbolsByParentMap;
typedef hash_map<const char*, const FileDescriptor*,
hash<const char*>, streq>
FilesByNameMap;
typedef hash_mapconst FieldDescriptor*,
PointerStringPairHash, PointerStringPairEqual>
FieldsByNameMap;
typedef hash_mapconst FieldDescriptor*,
PointerIntegerPairHash >
FieldsByNumberMap;
typedef hash_mapconst EnumValueDescriptor*,
PointerIntegerPairHash >
EnumValuesByNumberMap;
parent的含义
从BUILD_ARRAY
的定义和使用,可以理解parent
的含义,有如下3种情况:
field/nested_message/enum/extension
), 这个message的Descriptor*
就是它成员的parent。 DescriptorBuilder::BuildMessage()
中的宏BUILD_ARRAY
定义可以看出这一点。enum_value
是parent(函数DescriptorBuilder::BuildEnum()
中体现)DescriptorBuilder::BuildService()
中体现)具体数据成员
vector strings_; // All strings in the pool.
vector messages_; // All messages in the pool.
vector file_tables_; // All file tables in the pool.
vector allocations_; // All other memory allocated in the pool.
SymbolsByNameMap symbols_by_name_;
FilesByNameMap files_by_name_;
ExtensionsGroupedByDescriptorMap extensions_;
和rollback相关的数据成员
int strings_before_checkpoint_;
int messages_before_checkpoint_;
int file_tables_before_checkpoint_;
int allocations_before_checkpoint_;
vector symbols_after_checkpoint_;
vector files_after_checkpoint_;
vector extensions_after_checkpoint_;
其它数据成员
vector pending_files_ // stack方式保存的文件名,用来检测文件的循环依赖错误
和数据库事务处理中的概念一样,在确保数据正常时,生成一个检查点(checkpoint),针对当前状态做一个快照;如果在后续处理过程中,发生问题,做回滚(rollback),数据恢复到上一个checkpoint,保证基础服务可以继续,提高系统的可用性。
生成checkpoint的点只有2个,都在函数DescriptorBuilder::BuildFile()
中:
DescriptorPool::Tables* tables_
内容之前;DescriptorPool::Tables::Checkpoint():
void DescriptorPool::Tables::Checkpoint() {
// 记录下当前4个vector的size
strings_before_checkpoint_ = strings_.size();
messages_before_checkpoint_ = messages_.size();
file_tables_before_checkpoint_ = file_tables_.size();
allocations_before_checkpoint_ = allocations_.size();
// clear掉3个`***_after_checkpoint_`的vector
symbols_after_checkpoint_.clear();
files_after_checkpoint_.clear();
extensions_after_checkpoint_.clear();
}
DescriptorPool::Tables::Rollback():
after_checkpoint_[]
的数据;after_checkpoint_[]
数据;Checkpoint()
记录下来的size,删除vector尾部数据,并且完成resize(),释放掉不再占有的内存空间;DescriptorPool::Tables中的各个表中的数据是如何注册进来的呢?
对外接口是DescriptorPool::Tables::AddSymbol()
,在DescriptorBuilder
类的DescriptorBuilder::AddSymbol()
和DescriptorBuilder::AddPackage()
被调用。
负责构造和管理*所有的、各种类型的***descriptor,并且帮助管理互相cross-linke
d的descriptor之间的关系,以及他们之间的数据依赖。可以通过name来从DescriptorPool找到对应descriptor。
按照singleton方式提供服务,全局数据包含:
EncodedDescriptorDatabase* generated_database_ = NULL;
DescriptorPool* generated_pool_ = NULL;
GOOGLE_PROTOBUF_DECLARE_ONCE(generated_pool_init_);
使用google::protobuf::GoogleOnceInit
(本质是pthread_once)来控制仅仅被init一次。
虽然类DescriptorPool提供了3种构造函数,但从函数InitGeneratedPool()
看,仅仅使用了配置DescriptorDatabase*
的版本,其余2个并没有使用。在这种情况下,其实 const DescriptorPool* underlay_
是为NULL的。
void InitGeneratedPool() {
generated_database_ = new EncodedDescriptorDatabase;
generated_pool_ = new DescriptorPool(generated_database_);
internal::OnShutdown(&DeleteGeneratedPool);
}
DescriptorDatabase* fallback_database_
作用:
DescriptorPool::BuildFile()
来处理原database中的每一个proto文件是低效的。为了提升效率,使用DescriptorPool来封装DescriptorDatabase,并且只建立正真需要的descriptor。说明:
fallback_database_
之后,不能调用BuildFile*()
方法来构建pool,只能使用Find*By*()
方法Find*By*()
因为上锁,所以即使没有去访问fallback_database_
的请求也会变慢const DescriptorPool* underlay_
的作用Underlay的作用(从注释中得到):
仅在内部使用,并且可能存在诡异的问题(many subtle gotchas),建议使用DescriptorDatabases来解决问题。
应用场景:
需要runtime方式使用DynamicMessage来解析一个.proto文件,已知这个.proto文件的依赖已经按照静态编译方式包含。
generated_pool()
产生的DescriptorPool作为新的pool的underlay。DescriptorPool::Find*By*()
系列函数作用:
通过name来从DescriptorPool找到对应descriptor时,查找时先上锁(MutexLockMaybe),代码上看是分3个层级数据来查找的:
DescriptorPool::Tables tables_
中找,没找到继续第2层中找;DescriptorPool* underlay_
中找,没找到继续第3层中找;DescriptorDatabase* fallback_database_
中找对应proto,并且调用临时构造的DescriptorBuilder::Build*()
系列接口把生成的descriptor添加到tables_
中,然后再从tables_
中找;Q: 这里为什么要临时构造一个DescriptorBuilder来使用呢?
答案是:锁是针对第3层DescriptorDatabase* fallback_database_
的,因为这个可能被同时读/写
封装了DescriptorPool,对外提供descriptor的构造。对外最主要的接口是DescriptorBuilder::BuildFile()
,通过FileDescriptorProto来构建FileDescriptor。
DescriptorProto系列类,在descriptor.proto文件中定义,用来描述由protobuf产生类型的类型原型(proto)。
一共有如下7种proto
FileDescriptorProto | 用来描述 文件 |
---|---|
DescriptorProto | 用来描述 消息(message) |
FieldDescriptorProto | 用来描述 字段 |
EnumDescriptorProto | 用来描述 枚举 |
EnumValueDescriptorProto | 用来描述 枚举值 |
ServiceDescriptorProto | 用来描述 服务器 |
MethodDescriptorProto | 用来描述 服务器方法 |
单个proto文件中包含的tables,这些tables在文件加载时就固化下来,所以无需使用mutex保护,所以使得依赖单个文件的操作(例如Descriptor::FindFieldByName()
)是lock-free的。
类FileDescriptorTables
和类 DescriptorPool::Tables
过去是在同一个类中定义的。
原来Google也有类似的注释:// For historical reasons,xxxxxx。
它所包含的数据结构如下:
SymbolsByParentMap symbols_by_parent_;
FieldsByNameMap fields_by_lowercase_name_;
FieldsByNameMap fields_by_camelcase_name_;
FieldsByNumberMap fields_by_number_; // Not including extensions.
EnumValuesByNumberMap enum_values_by_number_;
接口类,用于定制地(on-demand)从某种”大”的database加载产生DescriptorPool。因为database太大,逐个调用DescriptorPool::BuildFile()
来处理原database中的每一个proto文件是低效的。
为了提升效率,使用DescriptorPool来封装DescriptorDatabase,并且只建立正真需要的descriptor。
包含了4个子类,提供通过name查询file_descriptor_proto 的接口(注意这里是file_descriptor_proto,而不是file_descriptor)。
索引file_name-> file_descriptor_proto*
,拥有被它索引的 file_descriptor_proto*
的ownership,并提供add()/find()
接口。类SimpleDescriptorDatabase在protobuf中并没有被使用。
内部实现:
SimpleDescriptorDatabase::DescriptorIndex index_
管理索引结构;vector files_to_delete_
管理”深拷贝”的部分;内部实现:
map
管理从name->Value
的映射关系;map
管理file所包含的symbol_name->Value
的映射关系,这里的symbol可以是file包含的message/enum/service;map
管理file所包含的extension_name->Value
的映射关系;应用:
protobuf中仅在如下2个地方被应用:
DescriptorIndex index_
SimpleDescriptorDatabase::DescriptorIndex > index_
功能说明:
索引file_name->pair
,结构pair
中的const void*
指的是encoded_file_descriptor
字符串的地址,int
指的是encoded_file_descriptor
字符串的长度。被管理的encoded_file_descriptor
有两类ownership:
EncodedDescriptorDatabase::AddCopy()
实现;EncodedDescriptorDatabase::Add()
实现;具体应用:
每个proto生成的pb.cc中,都包含将本proto文件encoded之后的string添加到EncodedDescriptorDatabase中的函数。例如protobuf/compiler/plugin.pb.cc
中的
void protobuf_AddDesc_google_2fprotobuf_2fcompiler_2fplugin_2eproto() {
…… //省略函数
::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
"\n%google/protobuf/compiler/plugin.proto\022"
"\030google.protobuf.compiler\032 google/protob"
"uf/descriptor.proto\"}\n\024CodeGeneratorRequ"
"est\022\030\n\020file_to_generate\030\001 \003(\t\022\021\n\tparamet"
"er\030\002 \001(\t\0228\n\nproto_file\030\017 \003(\0132$.google.pr"
"otobuf.FileDescriptorProto\"\252\001\n\025CodeGener"
"atorResponse\022\r\n\005error\030\001 \001(\t\022B\n\004file\030\017 \003("
"\01324.google.protobuf.compiler.CodeGenerat"
"orResponse.File\032>\n\004File\022\014\n\004name\030\001 \001(\t\022\027\n"
"\017insertion_point\030\002 \001(\t\022\017\n\007content\030\017 \001(\t", 399);
…… //省略函数
}
::google::protobuf::DescriptorPool::InternalAddGeneratedFile()
定义如下:
void DescriptorPool::InternalAddGeneratedFile(
const void* encoded_file_descriptor, int size) {
// 每个protobuf产出的.pb.cc文件中都会包含InternalAddGeneratedFile(),在进程启动时会调用这个函数,注册.proto文件对应FileDescriptorProto的raw bytes
// Q:进程启动时会调用这个函数,上一级的入口在哪里呢?每个编译依赖(被include)的proto文件都会注册么?
// 针对编译依赖的每个proto文件,并不是在进程启动时,直接构建出proto中所包含的所有descriptor,而是hang on,直到某个descriptor真的被需要:
// (1) 用户调用例如descriptor(), GetDescriptor(), GetReflection()的方法,需要返回descriptor;
// (2) 用户从DescriptorPool::generated_pool()中查找descriptor;
//
// 上述2类请求发生时,DescriptorPool先获得并解析FileDescriptorProto,然后根据它产生对应的FileDescriptor(以及它所包含的descriptor)
//
// 因为FileDescriptorProto类型本身也是由protobuf通过protobuf/descriptor.proto文件产生的,所以当解析的时候,需要注意避免使用任何descriptor-based 的操作,避免死锁和死循环。
InitGeneratedPoolOnce();
GOOGLE_CHECK(generated_database_->Add(encoded_file_descriptor, size));
}
内部实现:
vector files_to_delete_:记录拥有ownership的encoded_file_descriptor字符串的地址,
针对单一DescriptorPool的封装,查询时先调用内置的DescriptorPool接口,从name查找到对应的file_descriptor, 再调用FileDescriptor::CopyTo()
,获得file_descriptor_proto
.
类DescriptorDatabase的子类,封装多个descriptor_database
,本身结构简单,用vector
保存,逐个遍历查询。