Jason Pan

gRPC 源代码阅读笔记

潘忠显 / 2020-05-08


gRPC 基本概念已经独立一篇文章介绍,请移步点击《gRPC 基础概念详解》查看。

本文将会讨论 gRPC 核心概念和实现,期间会穿插介绍一些 C++ 的编程技巧。

ServerBuilder 是否可以多次调用 RegisterService 注册多个服务?

BuildAndStart 可以产生一个新的 Server 对象指针。

多个线程可以共用同一个 CompletionQueue 吗?可以,例子中就是。

greeter_server.cc 也不是同步的 server,比如在SayHello中增加一个:

std::this_thread::sleep_for(std::chrono::seconds(1));

一、目录说明

include/grpcpp/channel.h

include/grpcpp/impl/codegen/channel_interface.h

src/core/lib/surface/channel.h

src/cpp/client/channel_cc.cc

include/grpc/impl/codegen/grpc_types.h

C 代码 上边这行

  /// Value is a \a census_context.
  GRPC_CONTEXT_TRACING,

  /// Value is a CallTracer object.
  GRPC_CONTEXT_CALL_TRACER,

二、Channel

(为什么先说 Channel)

因为 greeter_client.cc 的main函数中,最先出现跟 gRPC 相关的就是利用 CreateChannel() 来创建一个Channel 的shared_ptr;另一方面,Channel 是 Client 和 Server最重要的***

include/grpcpp/channel.h 中定义了 Channel:

class Channel final : public ::grpc::ChannelInterface,
                      public ::grpc::internal::CallHook,
                      public std::enable_shared_from_this<Channel>,
                      private ::grpc::GrpcLibraryCodegen {...};

Channel 继承了 4 个类,每个类及其对应的作用为:

涉及到其他 C++ 的技巧:

【技巧 2】利用私有构造函数 + 友元(类或函数),来限定只能在部分作用于中创建对象。

【技巧 3】利用 override 限定析构函数,以保证其父类的析构函数均已声明为虚函数。

【技巧 4】将类模板声明为友元,会对利用该模板实例化出来的所有类都有效。(?)

为更清晰的描述核心概念,这里以及后文中都略掉辅助类或函数的介绍。

Channel 利用了 grpc_channel 类型的成员变量,继承了类 ChannelInterface 并实现了虚函数或纯虚函数。

核心功能是在 struct grpc_channel 及相关函数实现的。

struct grpc_channel 是个结构,没有成员函数,都是通过 grpc_channel_ 开头的一些函数来进行操作的,比如:

为什么 status.h 中 extern “C” 而没有找到对应的符号定义?

impl/codegen


port_platform.h 判断环境,并以此进行一些额外的定义。


atm_gcc_atomic.h 封装类 GCC 编译器的原子性接口 __atomic_* 接口,比如:

#define gpr_atm_acq_load(p) (__atomic_load_n((p), __ATOMIC_ACQUIRE))

比如这里的 __atomic_load_n 只有使用 GCC 才能通过,而这个函数的定义在 GCC 文档中有说明:

Built-in Function: type __atomic_load_n (type *ptr, int memorder)

This built-in function implements an atomic load operation. It returns the contents of *ptr.

The valid memory order variants are __ATOMIC_RELAXED, __ATOMIC_SEQ_CST, __ATOMIC_ACQUIRE, and __ATOMIC_CONSUME.

头文件中使用了 static inline 修饰这些封装的函数,这两个关键字一起用的作用:

https://stackoverflow.com/a/47821267/3801587

??? static受作用域限制,最大是文件作用域,不能被extern

static 修饰独立的函数时的作用

如果两个源文件中定义了两个相同的函数,且均为声明为 static,在 链接阶段 会报错,因为有两个相同的符号冲突了。

报错类似于:

> g++ t.cc m.cc 
/tmp/ccuQpoO7.o: In function `func()':
m.cc:(.text+0x0): multiple definition of `func()'
/tmp/cc5uQjXf.o:t.cc:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

我们先看 static 修饰一个源文件中函数的带来的影响:

// n.cc
int unstatic_func() { return 0; }
static int static_func() { return 0; }

编译后通过 objdump 指令来观察其中的符号:

> g++ -c n.cc
> objdump -t n.o | grep fun
000000000000000b l     F .text	000000000000000b _ZL11static_funcv
0000000000000054 l     F .text	0000000000000015 _GLOBAL__sub_I__Z13unstatic_funcv
0000000000000000 g     F .text	000000000000000b _Z13unstatic_funcv

可以看到 static_func() 是一个本地符号,而 unstatic_func() 是个全局符号,符号均位于代码区 text (code) section。第二列字符的含义可以参考 objdump 命令的说明,其中提到:

“l” “g” “u” “!” The symbol is a local (l), global (g), unique global (u), neither global nor local (a space) or both global and local (!). A symbol can be neither local or global for a variety of reasons, e.g., because it is used for debugging, but it is probably an indication of a bug if it is ever both local and global. Unique global symbols are a GNU extension to the standard set of ELF symbol bindings. For such a symbol the dynamic linker will make sure that in the entire process there is just one symbol with this name and type in use.

如果 .o 文件中出现了两个相同的全局符号,就会造成以上重复定义的错误。《Symbol Processing》文档详细的介绍了链接器处理符号的过程,以及中间遇到的问题。

https://docs.oracle.com/cd/E26505_01/html/E26506/chapter2-90421.html#gluek

可重定位对象

弱符号

强符号

It is declared static to avoid multiple definitions (at link time) in case the compiler did not inline it (e.g. when you use its address).

为什么有些使用宏定义,有些使用静态内联函数?


atm_gcc_sync.h

atm_windows.h

作用与 atm_gcc_atomic.h 相同,只是在不同环境中的封装。


atm.h

封装了 gpr_atm_no_barrier_clamped_add 函数,作用是

Adds delta to value, clamping the result to the range specified by min and max. Returns the new value.


sync_generic.h

同步相关的泛型,平台无关(平台相关内容被 atm.h 封装)。定义了 gpr_eventgpr_refcountgpr_stats_counter 等结构。


sync.h -> 根据平台选择一个对应的 sync_*.h 文件,“Synchronization primitives for GPR”,GPR 的同步原语(类型):gpr_mugpr_cvgpr_oncegpr_eventgpr_refcountgpr_stats_counter


status.h 定义了 gRPC 状态码的枚举值


grpc_types.h 定义或暴露了 gRPC 中低层使用的数据结构。分成几类:

  1. 就在该头文件中定义的,比如:grpc_byte_buffergrpc_arg_pointer_vtablegrpc_metadatagrpc_event 等。
  2. 不透明的类型,比如 grpc_completion_queue 是定义在 completion_queue.cc 中的,外边可以通过 API 进行创建和操作,所以这里需要将类型暴露出来。类似的还有 grpc_call
  3. 透明的类型,比如 grpc_channel 是定义在 “src/core/lib/surface/channel.h” 的头文件中的,外部是知道该结构的定义的。类似的还有:grpc_servergrpc_socket_factory

gpr_types.h 定义了 GPR 使用的时钟和时间


byte_buffer.h 对 grpc_slice 进行操作的若干函数,没有结构体封装;前面提到了,grpc_byte_buffer 结构是定义在 grpc_types.h 头文件中的。

byte_buffer_reader.h 定义了 struct grpc_byte_buffer_reader

这两个文件有着紧密的联系,在 byte_buffer.h 中声明的 grpc_byte_buffer_reader_init 实现是在 byte_buffer_reader.cc 中。

拆成两个文件的作用是什么?使用者(gRPC Core 的开发者)需要自己去 #include 两个头文件?


/** Reader for byte buffers. Iterates over slices in the byte buffer */
struct grpc_byte_buffer_reader;
typedef struct grpc_byte_buffer_reader grpc_byte_buffer_reader;

自己 #include 的头文件中没有该结构体的定义。为什么不自己 #include 而是要使用者自己去 #include

阅读代码过程中,发现有符号没有定义,提 Pull Request 删除掉。


struct grpc_byte_buffer_reader {
  struct grpc_byte_buffer* buffer_in;
  struct grpc_byte_buffer* buffer_out;
  /** Different current objects correspond to different types of byte buffers */
  union grpc_byte_buffer_reader_current {
    /** Index into a slice buffer's array of slices */
    unsigned index;
  } current;
};

为什么只有一个变量,还要使用 Union?为了更清晰?

使用过程:

int grpc_byte_buffer_reader_init(grpc_byte_buffer_reader* reader,
                                 grpc_byte_buffer* buffer) {
  reader->buffer_in = buffer;
  switch (reader->buffer_in->type) {
    case GRPC_BB_RAW:
      reader->buffer_out = reader->buffer_in;
      reader->current.index = 0;
      break;
  }
  return 1;
}

compression_types.h 压缩的算法、等级、选项(算法+选项)


connectivity_state.h 连接的状态,在 《》 中有介绍。


propagation_bits.h propagation 相关的比特位开关,可以或操作, 默认是4个比特都打开。


log.h 定义了 gpr_loggpr_log_funcgpr_log_func_args

log.cc 中有一个 g_log_func 的静态变量,可以通过 log.h 中的 gpr_set_log_function 将日志函数设置进去。

另外,在 log.h 中定义了宏 GPR_ASSERT,因为 GPR_ASSERT 的同时,会调用 gpr_log 打印日志。


fork.h


support 目录下是干嘛用的?

grpc 下边的头文件不全是 C 标准的头文件,比如 include/grpc/event_engine/slice_allocator.h 中有一个 SliceBuffer 的类,可能不合规范,因为注释写道:

stubbed out here, to be replaced with a real version in next PR.

src/core 是已经改成 C++ 写的了

gRPC 中 channelz 的含义

https://grpc.io/blog/a-short-introduction-to-channelz/

https://github.com/grpc/proposal/blob/master/A14-channelz.md

https://github.com/grpc/grpc/issues/15340

grpc_context

struct grpc_call_context_element {
  void* value = nullptr;
  void (*destroy)(void*) = nullptr;
};
/* Contexts for various subsystems (security, tracing, ...). */
grpc_call_context_element context[GRPC_CONTEXT_COUNT] = {};

以 census 为例,看 gRPC filter 的实现

grpc::CensusContext 是利用 opencensus 的 Span 和 TagMap 封装的是上下文。这个类的使用方式跟 Span 的创建方式很类似。

grpc::OpenCensusCallTracer 又对 grpc::CensusContext 做了封装(中间还通过一个内部的类)

grpc::OpenCensusCallTracer::OpenCensusCallAttemptTracer

grpc_core::CallTracer
grpc::OpenCensusCallTracer

grpc.h 中有 声明(弱符号)

struct census_context;

但是这个符号是在哪里定义的呢?在整个项目中没有被定义。

所有 census_text 的操作,都是通过指针来进行的:

census_context* grpc_census_call_get_context(grpc_call* call);
void grpc_census_call_set_context(grpc_call* call, census_context* context);

且上述函数的都是按照 C 代码来进行编译的,这产生的结果是在编译的结果中的符号就是grpc_census_call_get_context

> objdump -t greeter_server| grep census
000000000046462a g   F .text  0000000000000064  grpc_census_call_get_context
00000000004645aa g   F .text  0000000000000080  grpc_census_call_set_context

src/cpp/ext/filters/census/grpc_plugin.h

比如编译下边的单对文件

// t.h
#ifdef __cplusplus
extern "C" {
#endif

struct T;
T* func(T* t); 

#ifdef __cplusplus
}
#endif
// t.cc 
#include "t.h"

T* func(T* t) { return t; };

int main() {
  T* t = nullptr;
  func(t);
  return 0;
}

结构 T 没有定义也能够成功并正常运行。

const struct census_context* ServerContextBase::census_context() const {
  return call_.call == nullptr ? nullptr
                               : grpc_census_call_get_context(call_.call);
}
::opencensus::trace::Span GetSpanFromServerContext(
    grpc::ServerContext* context) {
  if (context == nullptr) return opencensus::trace::Span::BlankSpan();

  return reinterpret_cast<const grpc::CensusContext*>(context->census_context())
      ->Span();
}

grpc_trace

struct FilterRecord {
  grpc_channel_stack_type stack_type;
  int priority;
  std::function<bool(const grpc_channel_args&)> include_filter;
  grpc_channel_filter filter;
};

通过 docker 执行所有单元测试

https://github.com/panzhongxian/grpc/tree/master/tools/run_tests#unit-tests-run_testspy

slice.h grcp_slice 的定义和 API。

#ifndef GPRAPI
#define GPRAPI
#endif
std::vector<FilterRecord>* channel_filters;
FilterType::InitCallElement
  internal::FilterRecord filter_record = {
      stack_type,
      priority,
      include_filter,
      {FilterType::StartTransportStreamOpBatch, FilterType::StartTransportOp,
       FilterType::call_data_size, FilterType::InitCallElement,
       FilterType::SetPollsetOrPollsetSet, FilterType::DestroyCallElement,
       FilterType::channel_data_size, FilterType::InitChannelElement,
       FilterType::DestroyChannelElement, FilterType::GetChannelInfo, name}};
  internal::channel_filters->push_back(filter_record);
}
  static grpc_error_handle InitCallElement(grpc_call_element* elem,
                                           const grpc_call_element_args* args) {
    // Construct the object in the already-allocated memory.
    CallDataType* call_data = new (elem->call_data) CallDataType();
    std::cout << "InitCallElement()" << std::endl;
    return call_data->Init(elem, args);
  }

/// Value is a \a census_context.

GRPC_CONTEXT_TRACING,

/// Value is a CallTracer object.

GRPC_CONTEXT_CALL_TRACER,


protoc 是怎么来的 protoc 的插件是怎么来的


什么是 memory barrier?

空定义 GPRAPI 的作用是什么?


以用户不会直接用到的 Slice 为例,说明 gRPC 各个目录之间的关系。

README.md 文档中也有说明,最后 slice.h 文件放在 include/grpcpp/impl/codegen/ 目录下而不是放在 include/grpcpp 的原因是:

实际上,gRPC 库也基本上没有直接使用 Slice,而是通过 ByteBuffer 类又进行了一次封装,文件位置在:include/grpcpp/impl/codegen/byte_buffer.h

用户**会直接用到的 Status **与 Slice 的文件组织并没有太多的不同。其实,直接 #include <grpcpp/grpcpp.h> 之后,两种结构都可以直接使用:

#include <grpcpp/grpcpp.h>

using grpc::Slice;
using grpc::Status;

int main() {
  Status status;
  Slice slice;
  return 0;
}

什么情况下会使用到 grpc/support 目录下边的文件?

GPRAPI 修饰函数的作用是什么?

不再管 include/grpc++ 目录下的文件了.

Channel = Connection + Registered Method

grpc_core::channelz::ChannelNode

grpc::Channel 是了封装,

1. State

Channel 是有状态的,在 connectivity_state.h 中有定义 grpc_connectivity_state,[TODO]gRPC 的 enum 命名风格。

省去前缀 GRPC_CHANNEL_,5 种类型分别是:IDLE、CONNECTING、READY、TRANSIENT_FAILURE、SHUTDOWN,(这五种关系其实是描述的连接的状态?)

有点奇怪,为什么没有成员变量来维护这个状态呢?

每次获取状态,都会通过 grpc_channel_check_connectivity_state 来检查一下链接状态,在再Channel 或者 ChannelInterface 中则没有对应的成员变量来维护这个状态。

获取状态的逻辑

GetState
  grpc_channel_check_connectivity_state
   grpc_core::ClientChannel::GetFromChannel   grpc_channel* -> ClientChannel*
    grpc_channel_get_channel_stack       grpc_channel* -> grpc_channel_stack*
    grpc_channel_stack_last_element      grpc_channel_stack* -> grpc_channel_element*
   grpc_core::ClientChannel::CheckConnectivityState
    从成员变量 state_tracker_ 中获取状态,如果空闲则尝试连接
    grpc_core::ClientChannel::TryToConnectLocked  // 虽然TryToConnect,但返回的状态仍未之前的状态
     lb_policy_  resolver_
     grpc_stream_unref

ClientChannel 的几个

work_serializer_

lb_policy_

resolver_

owning_stack_

ConnectivityStateTracker 维护连接状态和 watchers_ (map)

只有ClientChannel 没有 ServerChannel

不是一个channel 而是一个channel的stack?

grpc_channel* const c_channel_;  // owned
  virtual grpc_connectivity_state GetState(bool try_to_connect) = 0;
  virtual internal::Call CreateCall(const internal::RpcMethod& method,
                                    ::grpc::ClientContext* context,
                                    ::grpc::CompletionQueue* cq) = 0;
  virtual void PerformOpsOnCall(internal::CallOpSetInterface* ops,
                                internal::Call* call) = 0;
  virtual void* RegisterMethod(const char* method) = 0;
  virtual void NotifyOnStateChangeImpl(grpc_connectivity_state last_observed,
                                       gpr_timespec deadline,
                                       ::grpc::CompletionQueue* cq,
                                       void* tag) = 0;
  virtual bool WaitForStateChangeImpl(grpc_connectivity_state last_observed,
                                      gpr_timespec deadline) = 0;
  virtual ::grpc::CompletionQueue* CallbackCQ() { return nullptr; }

grpc_channel_create_registered_call
    grpc_timespec_to_millis_round_up
    grpc_channel_create_call_internal
      grpc_call_create
    

codegen

/// Codegen interface for \a grpc::Channel.

namespace internal

Greeter::NewStub(channel)

include/grpcpp/impl/codegen/channel_interface.h
include/grpcpp/channel.h
class Channel final : public ::grpc::ChannelInterface,
                      public ::grpc::internal::CallHook,
                      public std::enable_shared_from_this<Channel>,
                      private ::grpc::GrpcLibraryCodegen {
  template <class R>
  friend class ::grpc::ClientReader;

意味着 ClientReader 类模板产生的类 都是Channel 的友类?

template <class R>
class ClientReader;

实现三个接口。

intercepted_channel.h

An InterceptedChannel is available to client Interceptors.

class ClientReaderWriterInterface : public internal::ClientStreamingInterface,
                  public internal::WriterInterface<W>,

                  public internal::ReaderInterface<R> {

CreateCallInternalCallbackCQ

class CallHook: This is an interface that Channel and Server implement to allow them to hook performing ops.

“performing ops”

Classes that require gRPC to be initialized should inherit from this class.

class GrpcLibraryCodegen

channel_factory

构造函数是 private + 包含友元类/友元函数 =》 只有在友类中才可以创建对象。

ChannelArguments


以下内容为待整理内容…

terminology

IDL

EOS (end-of-stream)

Payload message

Synchronous

Asynchronous

Mandatory

GPR - Google Portable Runtime for C

gRFC - gRPC 的 RFC,即 https://github.com/grpc/proposal 中的提案

grpc::ServerBuilder::HostString

Experimental, to be deprecated.

Best performance is typically obtained by using one thread per polling completion queue.

Caller is required to shutdown the server prior to shutting down the returned completion queue

SyncServerOption 用于设置同步 server 的不同的属性

grpc_transport 是个什么概念:

由 grpc_transport_vtable 定义操作

grpc_transport <= grpc_create_cronet_transport(engine, target, new_args, reserved);

grpc_channel <= grpc_channel_create_internal(target, new_args, GRPC_CLIENT_DIRECT_CHANNEL, ct, nullptr);

引擎

000

typedef struct grpc_transport_vtable {
  /* implementation of grpc_transport_init_stream */
  int (*init_stream)(grpc_transport* self, grpc_stream* stream,
                     grpc_stream_refcount* refcount, const void* server_data,
                     grpc_core::Arena* arena);

grpc::InsecureChannelCredentials() –> ChannelCredentials

grpc_transport

grpc_transport_vtable 只有两个普通变量,一个是stream的size,另外一个是名称,其他的全是 需要的函数指针。

GrpcLibraryCodegen

ChannelCredentials

InsecureChannelCredential -> std::shared_ptr

gpr_once_init 为了初始化什么?

std::atomic

template <typename Callable, typename... Args>
void call_once(absl::once_flag& flag, Callable&& fn, Args&&... args) {
  std::atomic<uint32_t>* once = base_internal::ControlWord(&flag);
  uint32_t s = once->load(std::memory_order_acquire);
  if (ABSL_PREDICT_FALSE(s != base_internal::kOnceDone)) {
    base_internal::CallOnceImpl(
        once, base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL,
        std::forward<Callable>(fn), std::forward<Args>(args)...);
  }
}

channel_factory

call_once

gpr_mu_init 全局变量控制 gpr_mu_init 只调用一次,创建一个全局的锁

grpc_channel_credentials

ArenaPromise

不同的鉴权方式参考:grpc_security.h

    std::shared_ptr<Channel> channel = grpc::CreateChannelInternal(
        "", grpc_channel_create(target.c_str(), creds, &channel_args),
        std::move(interceptor_creators));

grpc_channel* grpc_channel_create 干了什么

void gpr_mu_init(gpr_mu* mu) {
  static_assert(sizeof(gpr_mu) == sizeof(absl::Mutex),
                "gpr_mu and Mutex must be the same size");
  new (mu) absl::Mutex;
}

grpc_shutdown()

有两套同步的代码,主要是根据 下边三个标签来判断:

#if defined(GPR_POSIX_SYNC) && !defined(GPR_ABSEIL_SYNC) && !defined(GPR_CUSTOM_SYNC)
image-20220407220016127

好多地方会调用 grpc_init(),但是实际上内部分成两部分:

gpr_once_init(),如果已经初始化过之后,就不会在进入了。

直接判断 ++g_initializations == 1 会不会有益处的风险。如果 grpc_shutdown 之后,会 –g_initializations。

通过 gpr_once_init 来

使用 absl::MutexLock 在作用域内都加锁,出了作用域释放:

The class uses RAII to acquire the mutex and automatically releases it when the class goes out of scope.

GRPC Core API & C++ API

GRPC Core通过底层API实现素有gRPC的核心功能的library,以便被其他库封装,API在 include/grpc.h 文件中,文档在这。GRPC C++ 文档入口在这,安全相关的在grpc_security.h

提议L6-core-allow-cpp.md中有描述。**最初的GRPC Core是用C89写的,后来改用C99,再后来又改用C++。**从代码中,我们也能看到一些蛛丝马迹:

以grpc::CreateChannel为例,说明Core和C++库的关系

很多客户端示例代码中有这样一个创建Channel的代码:

grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());

我们以此为例,来说明一下Core和C++的关系,顺便看一下文件组织的规则。

再以最初传入的grpc::InsecureChannelCredentials为例,看看接下来的调用过程。

这里的grpc_insecure_channel_create就是gRPC core中的C API。再看回insecure_credentials.cc 文件的include:

#include <grpc/grpc.h>

这就是gRPC C++ 和 Core的关联。grpc.h中也有 extern “C” 以便 C 中调用 C++ 函数。

基类

ServerContextBase

派生类

ServerContext

CallbackServerContext

class CallbackServerContext : public ServerContextBase {
 public:
  /// Public constructors are for direct use only by mocking tests. In practice,
  /// these objects will be owned by the library.
  CallbackServerContext() {}
  // ...
  using ServerContextBase::context_allocator;
  using ServerContextBase::set_context_allocator;
  // ...
  // CallbackServerContext only
  using ServerContextBase::DefaultReactor;

 private:
  // Sync/CQ-based Async ServerContext only
  using ServerContextBase::AsyncNotifyWhenDone;

  /// Prevent copying.
  CallbackServerContext(const CallbackServerContext&) = delete;
  CallbackServerContext& operator=(const CallbackServerContext&) = delete;
};

grpc_slice

A grpc_slice s, if initialized, represents the byte range s.bytes[0..s.length-1].

两种情况:

TODO: 如何实现的grpc_slice

grpc library

Operations:

grpc_call

A Call represents an RPC. When created, it is in a configuration state allowing properties to be set until it is invoked. After invoke, the Call can have messages written to it and read from it.

创建:

TODO: grpc_call不用显式销毁吗?

其他操作:

grpc_channel

A gRPC channel provides a connection to a gRPC server on a specified host and port. It is used when creating a client stub. Clients can specify channel arguments to modify gRPC’s default behaviour, such as switching message compression on or off. A channel has state, including connected and idle.

一个 Channel 对应一个连接。真实情况是这样吗?

channel 有状态:

创建与销毁:

连接相关:

TODO: What is a channel watcher? How to add a channel watcher?

Call 相关:

lame channel (one on which all operations fail)

grpc_call_details

一个 RPC 的详细信息,包括method、host、deadline、flags

grpc_call_error

grpc_completion_queue

其他

cencus-context

gRPC Channel

疑问:什么时候会用到server side channel?

疑问:stack对于channel是一个什么概念

疑问:filter 初始化完之后消失掉了,以后如何使用?

疑问:什么时候建立连接?

grpc_insecure_channel_create

channel_create.cc中的grpc_core::CreateChannel

channel.cc中的grpc_channel_create

builder起到了传递参数的作用,也有其他的(什么)功能。

grpc_channel_create

grpc_channel_create_with_builder

grpc_channel_stack_builder_finish

grpc_channel_stack_builder_finish的过程:

init_channel_elem

grpc_error* error = elems[i].filter->init_channel_elem(&elems[i], &args);

一个Channel有一个Stack,有多个Filter。

创建Channel的时候,会对每个Filter进行初始化,会用到Stack的指针。

  grpc_arg arg = grpc_core::ClientChannelFactory::CreateChannelArg(g_factory);

连接就是在工厂类的CreateSubChannel建立起来的。

class Chttp2InsecureClientChannelFactory : public ClientChannelFactory

Filter

Interpretor

Credentials

Stats

gRPC中有使用到内部的统计

if (channel_stack_type == GRPC_SERVER_CHANNEL) {
    GRPC_STATS_INC_SERVER_CHANNELS_CREATED();
} 

使用 grpc_stats_per_cpu_storage 数组来存储。

grpc_stats_data[CPU_index]

每个CPU对应的grpc_stats_data中又分为计数器直方图,根据对计数器分类和直方图桶个数的枚举,预先创建好数组。

通过使用宏对该变量进行操作,计数或统计分布。

在哪里汇总和展示?

mutator

https://grpc.github.io/grpc/core/pages.html

https://grpc.io/blog/

疑问与解释

名词解释

实现技巧

C++

类可以delete this。

如果能在一定会调用一次的成员函数中:

只是为了说明的空 define

#define GPRAPI
#define GRPCAPI GPRAPI
#define CENSUSAPI GRPCAPI

同时定义两个头文件的作用

为了屏蔽大部分用户不会用到的细节,grpcpp大部分头文件都是这样组织的:

#ifndef GRPCPP_ALARM_H
#define GRPCPP_ALARM_H

#include <grpcpp/alarm_impl.h>

namespace grpc {

typedef ::grpc_impl::Alarm Alarm;
}

#endif  // GRPCPP_ALARM_H

实际上实现Alarm的详细声明仍然在文件中,只是使用另外的命名空间,存放在单独的一个文件中。

std::enable_shared_from_this

std::enable_shared_from_this 能让其一个对象(假设其名为 t ,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1, pt2, … ) ,它们与 pt 共享对象 t 的所有权。

若一个类 T 继承 std::enable_shared_from_this<T> ,则会为该类 T 提供成员函数: shared_from_this 。 当 T 类型对象 t 被一个为名为 pt 的 std::shared_ptr<T> 类对象管理时,调用 T::shared_from_this 成员函数,将会返回一个新的 std::shared_ptr<T> 对象,它与 pt 共享 t 的所有权。

Internal、Impl 后缀的作用和关系

Extern “C”

#ifndef GRPC_GRPC_H
#define GRPC_GRPC_H

#include ...

#ifdef __cplusplus
extern "C" {
#endif

模板类的实例化

 private:
  template <class InputMessage, class OutputMessage>
  friend class ::grpc::internal::BlockingUnaryCallImpl;

CreateCall是Channel的private函数,因此文档中也没有说明。

代码构造

include/create_channel.h include/create_channel_impl.h
cpp/client/create_channel.cc
include/create_channel_impl.h
cpp/client/create_channel.cc
grpc::CreateChannel
-call->
grpc_impl::CreateChannelImpl
-call->
grpc_impl::CreateCustomChannelImpl

grpc_impl::CreateChannelImpl

grpc_impl::CreateCustomChannelImpl

grpc_impl::ChannelCredentials::CreateChannelImpl = 0 or grpc::CreateChannelInternal (lame)

InsecureChannelCredentials为例,看看是如何实现CreateChannelImpl的:

grpc::InsecureChannelCredentials()返回一个ChannelCredentials的共享指针。实现的声明是在credentials_impl.h文件中,实现的实际定义是在src/cpp/client/insecure_credentials.cc中,new了一个InsecureChannelCredentioalsImpl对象,返回其共享指针。

InsecureChannelCredentialsImpl 类定义在相同cc文件的 unnamed/anonymous namespace 中。该类(当然地)继承自ChannelCredentials类,需要实现后者的所有纯虚函数,包括CreateChannelImpl

InsecureChannelCredentialsImpl::CreateChannelImpl()–>CreateChannelWithInterceptors()–> ::grpc::CreateChannelInternal + grpc_insecure_channel_create()

这里的grpc_insecure_channel_create就是gRPC core中的C API。

Init函数

应该内部好正只能初始化一次,而不是通过外部判断。

初始化过程中应该加锁、

工厂类

ClientChannelFactory

3/5/0 rule

TODO:

InterceptedChannel

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md

Appendix

Words

concrete

agnostic

a.k.a. also known as.

grainted

eager

infrastructure

retrieve

invoke

advent

coarse-grained

pitfalls of distributed objects

the fallacies of ignoring the network

viable

facilitate

impede

traversal

facet

revision

lameduck

consensus 一致性

安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。

该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。

传输层安全性协议(英语:Transport Layer Security,缩写作TLS),及其前身安全套接层(Secure Sockets Layer,缩写作SSL)是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。网景公司(Netscape)在1994年推出首版网页浏览器,网景导航者时,推出HTTPS协议,以SSL进行加密,这是SSL的起源。IETF将SSL进行标准化,1999年公布第一版TLS标准文件。随后又公布RFC 5246 (2008年8月)与RFC 6176(2011年3月)。在浏览器、邮箱、即时通信、VoIP、网络传真等应用程序中,广泛支持这个协议。主要的网站,如Google、Facebook等也以这个协议来创建安全连线,发送数据。目前已成为互联网上保密通信的工业标准。

gRPC

三种方式 同步、基于 CQ 的异步、Callback方式

一个宏定义判断,该宏定义默认是定义上去,所以都走的 #ifdef GRPC_CALLBACK_API_NONEXPERIMENTAL 的分支

GRPC_CALLBACK_API_NONEXPERIMENTAL

生成的代码和 gRPC 库的联系

grpc
├── experimental
├── internal
├── protobuf
│   ├── io
│   └── util
└── testing
grpc_event_engine
└── experimental

override 说明符确保该函数为虚函数并覆盖某个基类中的虚函数

explicit 的构造函数不能用于隐式转换和复制初始化

non-static data member initializers

成员变量初始化的几种方式

  1. 构造函数成员函数初始化列表 S() : n(7) {}

Greeter 中几个类:

技巧

限制类模板形参必须为某种类

template <class BaseClass>
class WithAsyncMethod_SayHello : public BaseClass {
 private:
  void BaseClassMustBeDerivedFromService(const Service* /*service*/) {}

 public:
  ~WithAsyncMethod_SayHello() override {
    BaseClassMustBeDerivedFromService(this);
  }
}                                                                    

ChannelCredentials

所有使用 gRPC 的类都需要继承这个类,构造函数中会初始化一次 gRPC 的 g_glip。

/// Classes that require gRPC to be initialized should inherit from this class.

GrpcLibraryCodegen

多线程的情况会如何处理呢?会不会被初始化多次?

g_glip 指向的内容是

两个全局变量

grpc::CoreCodegenInterface* grpc::g_core_codegen_interface;
grpc::GrpcLibraryInterface* grpc::g_glip;
class ChannelCredentials : private grpc::GrpcLibraryCodegen 

继承 ChannelCredentials 需要实现纯虚函数 CreateChannelImpl 调用 CreateChannelWithInterceptors 创建的 Channel

将 ChannelArguments 类转成 grpc_channel_args

调用 CreateChannelInternal 创建,但是其中使用 grpc_insecure_channel_create(target.c_str(), &channel_args, nullptr) 创建了一个grpc_channel

异步的进行:

new_args

grpc_channel_args 和 grpc_arg 的关系是什么?

创建之后,args会被主动的销毁

因为 interceptor_creators 用的是形参,所以直接使用了move不会对外边造成影响。

grpc_channel* grpc_insecure_channel_create

还会调用 grpc_core::CreateChannel 是个内部函数(gcore 之内的匿名命名空间,位于同一个文件中)

grpc_channel_arg_string_create

最底层调用: grpc_channel_create,位于 src/core/lib/surface/channel.cc 中,其逻辑是:

grpc_channel_create_with_builder 中逻辑:

channel的 registration_table 是干嘛用的?

gpr_atm_no_barrier_store 是干嘛用的。空间分配限制。

builder中维护着一堆filter

grpc_channel

grpc_channel_stack_builder_finish

filter 的概念

grpc_channel_element

call 由两部分组成:grpc_call_stack + grpc_call_element(s) + 对应不同filter的 call_data

grpc ref

channel_stack 有说明

#include "src/cpp/client/create_channel_internal.h"

Channel 类是对 grpc_channel 的一个封装,创建不是在Channel中进行的。而是在CreateChannel 中调用 CreateChannelImpl 进行,本质上是调用 grpc_insecure_channel_create。

调用的 grpc_core::CreateChannel 只是内部的一个函数,外部也不能用。

实际上是在include/grpc/grpc.h 中定义了。

src/core/ext/transport/chttp2/client/insecure/channel_create.cc

grpc_channel_args_copy_and_add_and_remove

grpc_channel_create

​ grpc_channel_stack_builder_create

​ CreateChannelzNode

​ grpc_channel_create_with_builder

先忽略channelz相关的内容

channel_stack channel_stack_builder

channelz_node

创建一个 grpc_call 需要的内容,都要在 grpc_call_create_args,包括 XXXX

创建 call 以及 所需要的参数

关联一个 call 和一个CompletionQueue

grpc_call_internal_ref

从 call中获得参数,比如arena、call_stack、context键、是否为 client类型的call

对 call 进行参数设置,比如context

grpc_call 的结构是在 call.cc 中定义的,所以无法直接创建,只能通过 grpc_call_create() 来创建,其成员也无法通过指针直接访问,只能通过对外提供的函数来操作。

缩写含义

mdelem Metadata Element

pollent Polling Entity