Protobuf和GRPC(一)

简介: 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/feilengcui008/article/details/60475459 数据交互协议和RPC框架对于分布式系统来说是必不可少的组件,这个系列主要用来分析Protobuf和GRPC的实现原理,本文主要介绍Protobuf生成代码的流程以及Protobuf与GRPC之间的交互方式。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/feilengcui008/article/details/60475459

数据交互协议和RPC框架对于分布式系统来说是必不可少的组件,这个系列主要用来分析Protobuf和GRPC的实现原理,本文主要介绍Protobuf生成代码的流程以及Protobuf与GRPC之间的交互方式。


简要描述

  • Protobuf
    Protobuf主要由三大部分构成:

    1. Core: 包括核心的数据结构比如Message和Service等等
    2. Compiler: proto文件的Tokenizer和Parser; 代码生成器接口以及不同语言的具体实现, 并提供插件机制; protoc的主程序
    3. Runtime: 支撑不同语言的基础数据结构,通常和Core的主要数据结构对应,Ruby和PHP等直接以扩展的形式封装使用Core中的数据结构,而Go和Java则重新实现了一套对应的数据结构
  • GRPC
    GRPC也可以看做三大部分构成:

    1. Core: C语言实现的channel, http, transport等核心组件
    2. Compiler: 各个语言的Protobuf插件,主要作用是解析proto文件中的service并生成对应的server和client代码接口
    3. Runtime: 支撑不同语言的通信框架,通常是封装Core中的C实现,但是Go和Java是完全重新实现的整个框架(grpc-go和grpc-java)
  • 基本流程

    proto files -> tokenizer and parser -> FileDescriptor -> CodeGenerator(内部注册的生成器实现或者外部插件比如grpc插件) -> code


代码生成主要流程的源码分析

  • 入口
//  protobuf/src/google/protobuf/compiler/main.cc
int main(int argc, char* argv[]) {
  google::protobuf::compiler::CommandLineInterface cli;

  // 注册插件的前缀,当使用protoc --name_out=xx生成代码时,如果name对应的插件
  // 没有在内部注册那么默认当做插件,会查找protoc-gen-name的程序是否存在,如
  // 果指定了--plugin=protoc-gen-name=/path/to/bin参数,则优先使用此参数设置
  // 的路径这是grpc的protobuf插件以及go的protobuf实现与protoc命令交互的机制。
  cli.AllowPlugins("protoc-");

  // 注册内部代码生成器插件
  google::protobuf::compiler::cpp::CppGenerator cpp_generator;
  cli.RegisterGenerator("--cpp_out", "--cpp_opt", &cpp_generator,
"Generate C++ header and source.");

  /* ... */

  return cli.Run(argc, argv);
}
  • 参数和proto文件解析
// protobuf/src/google/protobuf/compiler/command_line_interface.cc

int CommandLineInterface::Run(int argc, const char* const argv[]) {
  /* ... */
  // 1. 解析参数,核心参数是--plugin, --name_out, -I, --import_path等
  // --plugin被解析成<name, path>的KV形式,--name_out可以通过--name_out=k=v:out_dir
  // 的形式指定k=v的参数,这个参数会被传递给代码生成器(插件),这个参数有时很有用,
  // 比如go的protobuf实现中,使用protoc --go_out=plugins=grpc:. file.proto来传递
  // plugins=grpc的参数给protoc-gen-go,从而在生成的时候会一并生成service的代码
  switch (ParseArguments(argc, argv)) { /* ... */ }

  // 2. Tokenizer和Parser解析proto文件,生成FileDescriptor
  Importer importer(&source_tree, &error_collector);
  for (int i = 0; i < input_files_.size(); i++) {
    /* ...  */
    // 词法和语法分析
    const FileDescriptor* parsed_file = importer.Import(input_files_[i])
    /* ...  */
  }

  // 3. 调用CodeGenerator生成代码
  for (int i = 0; i < output_directives_.size(); i++) {
    /* ... */
    // 按照命令行的--name1_out=xx, --name2_out=xx先后顺序多次调用,生成代码
    if (!GenerateOutput(parsed_files, output_directives_[i], *map_slot)) {
      STLDeleteValues(&output_directories);
      return 1;
    }
  }
}
  • 代码生成
bool CommandLineInterface::GenerateOutput(
    const std::vector<const FileDescriptor*>& parsed_files,
    const OutputDirective& output_directive,
GeneratorContext* generator_context) {

 // 不是内部注册的CodeGenerator,而是插件
 if (output_directive.generator == NULL) {
  /* ... */
  // 插件的可执行文件全名protoc-gen-name
  string plugin_name = PluginName(plugin_prefix_ , output_directive.name);
    // 传递给插件的参数
    string parameters = output_directive.parameter;
    if (!plugin_parameters_[plugin_name].empty()) {
      if (!parameters.empty()) {
        parameters.append(",");
      }
      parameters.append(plugin_parameters_[plugin_name]);
    }
    // 开子进程执行插件返回生成的代码数据
    if (!GeneratePluginOutput(parsed_files, plugin_name,
                              parameters,
                              generator_context, &error)) {
      std::cerr << output_directive.name << ": " << error << std::endl;
      return false;
}

} else {
    // 内部已经注册过的CodeGenerator,直接调用 
    // 传递的参数
    string parameters = output_directive.parameter;
    if (!generator_parameters_[output_directive.name].empty()) {
      if (!parameters.empty()) {
        parameters.append(",");
      }
      parameters.append(generator_parameters_[output_directive.name]);
    }
    // 生成
    if (!output_directive.generator->GenerateAll(
        parsed_files, parameters, generator_context, &error)) {
    /* ... */
}

}

}
  • GRPC的protobuf插件实现
// GRPC的service相关的生成器位于grpc/src/compiler目录下,
// 主要实现grpc::protobuf::compiler::CodeGenerator接口,
// 这里以C++为例
// grpc/src/compiler/cpp_plugin.cc

class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
  /* ...  */
  virtual bool Generate(const grpc::protobuf::FileDescriptor *file,
                        const grpc::string &parameter,
                        grpc::protobuf::compiler::GeneratorContext *context,
  grpc::string *error) const {

    // 生成头文件相关代码(.grpc.pb.h)
    grpc::string header_code =
        // 版权声明,宏,include
        grpc_cpp_generator::GetHeaderPrologue(&pbfile, generator_parameters) +
        // 导入grpc内部头文件,核心类的前向声明
        grpc_cpp_generator::GetHeaderIncludes(&pbfile, generator_parameters) +
        // Service, StubInterface接口相关
        grpc_cpp_generator::GetHeaderServices(&pbfile, generator_parameters) +
        // namespace和宏的结束标识
        grpc_cpp_generator::GetHeaderEpilogue(&pbfile, generator_parameters);
    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> header_output(
        context->Open(file_name + ".grpc.pb.h"));
    grpc::protobuf::io::CodedOutputStream header_coded_out(header_output.get());
    header_coded_out.WriteRaw(header_code.data(), header_code.size());

    // 生成源码(.grpc.pg.cc)
    grpc::string source_code =
        grpc_cpp_generator::GetSourcePrologue(&pbfile, generator_parameters) +
        grpc_cpp_generator::GetSourceIncludes(&pbfile, generator_parameters) +
        grpc_cpp_generator::GetSourceServices(&pbfile, generator_parameters) +
        grpc_cpp_generator::GetSourceEpilogue(&pbfile, generator_parameters);
    std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> source_output(
        context->Open(file_name + ".grpc.pb.cc"));
    grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get());
    source_coded_out.WriteRaw(source_code.data(), source_code.size());  

    /* ... */
  }
}
相关文章
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
【4月更文挑战第6天】Java的`java.util.concurrent.atomic`包提供了一系列原子类,如`AtomicInteger`和`AtomicLong`,利用CPU原子指令保证无锁情况下变量更新的原子性,从而实现线程安全。这些类在高并发场景下能避免线程阻塞,提高性能。`AtomicInteger`和`AtomicLong`支持原子地增加、减少和设置值,而`AtomicReference`则适用于原子更新引用对象。尽管原子类具有非阻塞、线程安全和易用等优点,但它们仅保证单个变量的原子性,复杂操作可能仍需传统同步机制。了解其工作原理和局限性,有助于提升并发应用性能。
273 0
|
5月前
|
人工智能 安全 数据安全/隐私保护
|
7月前
|
Linux 虚拟化 Docker
win11怎么安装docker的必要设置自学软硬件工程师778天
win11怎么安装docker的必要设置自学软硬件工程师778天
win11怎么安装docker的必要设置自学软硬件工程师778天
|
SQL 存储 NoSQL
SQL、NoSQL还是NewSQL
【7月更文挑战第5天】SQL、NoSQL还是NewSQL
357 1
|
传感器 物联网 数据安全/隐私保护
低功耗蓝牙和 Wi-Fi 相比有什么优缺点
低功耗蓝牙(BLE)与Wi-Fi相比,功耗更低、成本更少,适用于短距离、小数据量传输,如智能手环等;但传输速度和距离不如Wi-Fi,适合对实时性和带宽要求不高的场景。
1019 4
|
存储 分布式计算 NoSQL
什么是 MongoDB ?
10月更文挑战第10天
168 0
|
12月前
|
存储 监控 数据可视化
双十一线上服务调用链路追踪SkyWalking实战分析
【11月更文挑战第27天】随着电商行业的飞速发展,双十一购物节已成为全球最大的购物狂欢节之一。在双十一期间,电商平台需要处理海量的用户请求和订单,这对系统的稳定性和性能提出了极高的要求。为了确保系统在高并发环境下的稳定运行,对线上服务的调用链路进行追踪和分析显得尤为重要。本文将通过实战案例,详细介绍如何在双十一期间使用SkyWalking对线上服务进行调用链路追踪,并结合Seata实现分布式事务管理,从而保障系统的稳定性和性能。
353 6
|
敏捷开发 数据可视化 持续交付
敏捷开发方法:理论与实践
【8月更文第22天】随着信息技术的发展,软件项目的复杂度不断提高,传统的瀑布式开发模式越来越难以适应快速变化的市场需求。为了解决这些问题,敏捷开发方法应运而生。本文将探讨敏捷开发的核心理念、敏捷宣言与原则、Scrum框架、Kanban方法以及相关的敏捷实践与工具。
1596 2
|
芯片
EDA设计:探索电子设计的自动化之路
EDA设计:探索电子设计的自动化之路
958 0
|
存储 Linux 数据处理
Linux中的raw命令:深入解析与实用指南
Linux的`raw`命令详解:用于直接访问硬件设备,绕过文件系统,提供高灵活性和性能。适用于数据处理,如直接复制文件或设备数据。使用时需谨慎,注意设备理解、数据备份及正确选项选择。结合其他工具可实现更多功能。示例:`raw file1 file2`复制文件,`raw -s 1024 file1 file2`跳过字节复制。