grpc源码分析之域名解析

环境:

  win7_x64,VS2015、grpc_1.3.1

场景:

  在客户端中使用grpc连接服务器,在多次输入非法的地址后,再次输入正确的地址连出现连接超时的现象。侯捷先生说过“源码面前,了无秘密”,所以开始分析grpc源码

使用GRPC进行连接的例子:

///< 创建通道
std::shared_ptr<grpc::Channel> channel = grpc::CreateChannel("127.0.0.1:8080", grpc::InsecureChannelCredentials());
///< 3秒超时
gpr_timespec tm_out{3, 0, GPR_TIMESPAN};
///< 等待连接
bool connected = channel->WaitForConnected<gpr_timespec>(tm_out);
if(connected) {
    std::cout << "connect success!" << std::endl;
}
else {
    std::cout << "connect fail!" << std::endl;
}

分析GRPC域名解析过程:

一、创建通道

1.1 创建通道证书

  有非安全的InsecureChannelCredentials,还有一个安全的SecureChannelCredentials,这里我们使用非安全的通道

grpc::InsecureChannelCredentials()

1.2 创建通道,在grpc_channel_create_with_builder(channel.c)函数中

grpc_channel *grpc_channel_create_with_builder(
    grpc_exec_ctx *exec_ctx, grpc_channel_stack_builder *builder,
    grpc_channel_stack_type channel_stack_type) {

  ......
  channel->target = target;
  channel->is_client = grpc_channel_stack_type_is_client(channel_stack_type);
  ......

  grpc_compression_options_init(&channel->compression_options);
  for (size_t i = 0; i < args->num_args; i++) {
    if (0 == strcmp(args->args[i].key, GRPC_ARG_DEFAULT_AUTHORITY)) {
      if (args->args[i].type != GRPC_ARG_STRING) {
        gpr_log(GPR_ERROR, "%s ignored: it must be a string",
                GRPC_ARG_DEFAULT_AUTHORITY);
      } else {
        channel->default_authority = grpc_mdelem_from_slices(
            exec_ctx, GRPC_MDSTR_AUTHORITY,
            grpc_slice_intern(
                grpc_slice_from_static_string(args->args[i].value.string)));
      }
    }
    ......
  }

done:
  grpc_channel_args_destroy(exec_ctx, args);
  return channel;
}

  1.2.1 设置通道连接的目标(即服务器地址)

  1.2.2 设置通道类型为客户端通道(GRPC_CLIENT_CHANNEL),有6种通道类型,有一些是负载均衡使用的,在channel_stack_type.h文件中定义。

  1.2.3 设置默认的权限

二、域名解析

2.1 开始解析(dns_resolver.c)

static void dns_start_resolving_locked(grpc_exec_ctx *exec_ctx,
                                       dns_resolver *r) {
  GRPC_RESOLVER_REF(&r->base, "dns-resolving");
  GPR_ASSERT(!r->resolving);
  r->resolving = true;
  r->addresses = NULL;
  grpc_resolve_address(
      exec_ctx, r->name_to_resolve, r->default_port, r->interested_parties,
      grpc_closure_create(dns_on_resolved_locked, r,
                          grpc_combiner_scheduler(r->base.combiner, false)),
      &r->addresses);
}

  grpc_resolve_address就是一个函数指针,在不同的平台下,指向不同的函数;在windows平台下则指向resolve_address_impl函数(resolve_address_windows.c)

2.2 设置解析名称并在另一线程进行请求

static void resolve_address_impl(grpc_exec_ctx *exec_ctx, const char *name,
                                 const char *default_port,
                                 grpc_pollset_set *interested_parties,
                                 grpc_closure *on_done,
                                 grpc_resolved_addresses **addresses) {
  request *r = gpr_malloc(sizeof(request));
  grpc_closure_init(&r->request_closure, do_request_thread, r,
                    grpc_executor_scheduler);
  r->name = gpr_strdup(name);
  r->default_port = gpr_strdup(default_port);
  r->on_done = on_done;
  r->addresses = addresses;
  grpc_closure_sched(exec_ctx, &r->request_closure, GRPC_ERROR_NONE);
}

  解析过程是通过do_request_thread函数完成的,这里进行打包,在另一个地方进行调用

static void executor_push(grpc_exec_ctx *exec_ctx, grpc_closure *closure,
                          grpc_error *error) {
  gpr_mu_lock(&g_executor.mu);
  if (g_executor.shutting_down == 0) {
    grpc_closure_list_append(&g_executor.closures, closure, error);
    maybe_spawn_locked();
  }
  gpr_mu_unlock(&g_executor.mu);
}

  添加解析操作到全局变量g_executor.closures中,然后调用maybe_spawn_locked函数。  

static void maybe_spawn_locked() {
  if (grpc_closure_list_empty(g_executor.closures) == 1) {
    return;
  }
  if (g_executor.shutting_down == 1) {
    return;
  }

  if (g_executor.busy != 0) {
    /* Thread still working. New work will be picked up by already running
     * thread. Not spawning anything. */
    return;
  } else if (g_executor.pending_join != 0) {
    /* Pickup the remains of the previous incarnations of the thread. */
    gpr_thd_join(g_executor.tid);
    g_executor.pending_join = 0;
  }

  /* All previous instances of the thread should have been joined at this point.
   * Spawn time! */
  g_executor.busy = 1;
  GPR_ASSERT(gpr_thd_new(&g_executor.tid, closure_exec_thread_func, NULL,
                         &g_executor.options));
  g_executor.pending_join = 1;
}

  如果队列g_executor.closures为空或者是已经关闭,直接返回。

  如果g_executor.busy !=0 表示解析线程已经被创建,直接返回,解析线程会依次从队列中取出待解析的名称,进行解析。

  如果上面都没有执行,则调用gpr_thd_new函数创建解析线程。

2.3 在线程函数中,依次从队列中取出待解析的对象调用do_request_thread函数

static void closure_exec_thread_func(void *ignored) {
  grpc_exec_ctx exec_ctx = GRPC_EXEC_CTX_INIT;
  while (1) {
    gpr_mu_lock(&g_executor.mu);
    if (g_executor.shutting_down != 0) {
      gpr_mu_unlock(&g_executor.mu);
      break;
    }
    if (grpc_closure_list_empty(g_executor.closures)) {
      /* no more work, time to die */
      GPR_ASSERT(g_executor.busy == 1);
      g_executor.busy = 0;
      gpr_mu_unlock(&g_executor.mu);
      break;
    } else {
      grpc_closure *c = g_executor.closures.head;
      grpc_closure_list_init(&g_executor.closures);
      gpr_mu_unlock(&g_executor.mu);
      while (c != NULL) {
        grpc_closure *next = c->next_data.next;
        grpc_error *error = c->error_data.error;
#ifndef NDEBUG
        c->scheduled = false;
#endif
        c->cb(&exec_ctx, c->cb_arg, error);
        GRPC_ERROR_UNREF(error);
        c = next;
      }
      grpc_exec_ctx_flush(&exec_ctx);
    }
  }
  grpc_exec_ctx_finish(&exec_ctx);
}

  cb函数是在打包时设置的,其实就是do_request_thread函数,详看2.2

2.4 do_request_thread函数直接调用blocking_resolve_address_impl函数进行处理

static grpc_error *blocking_resolve_address_impl(    const char *name, const char *default_port,    grpc_resolved_addresses **addresses) {  struct addrinfo hints;
  struct addrinfo *result = NULL, *resp;
  char *host;
  char *port;
  int s;
  size_t i;
  grpc_error *error = GRPC_ERROR_NONE;

  /* parse name, splitting it into host and port parts */
  gpr_split_host_port(name, &host, &port);
  ......if (port == NULL) {
    ......
    port = gpr_strdup(default_port);
  }

  /* Call getaddrinfo */
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;     /* ipv4 or ipv6 */
  hints.ai_socktype = SOCK_STREAM; /* stream socket */
  hints.ai_flags = AI_PASSIVE;     /* for wildcard IP address */

  GRPC_SCHEDULING_START_BLOCKING_REGION;
  s = getaddrinfo(host, port, &hints, &result);......}

  首先,从名称中获取主机名和端口

    如果名称中没有端口,则使用默认的端口代替

  然后,调用getaddrinfo函数获取IP地址

分析连接超时的过程:

  1. 设置非法(不存在)的名称

  2. 调用getaddrinfo函数进行解析

    因为名称不存在,但是域名解析服务器可能认为名称是"存在"的,只是没有找到而已,所以需要更多的时间来进行查找。

  3. getaddrinfo函数是阻塞的,在函数返回之前其他的名称只是简单添加到了队列之中,并没有立即进行解析。

  4. 多次输入非法的名称之后,导致队列过长,即使设置了合法的名称,但是还没有对它进行解析,最后3秒超时。

解决方案:

  1. 只允许输入合法IP地址,这样不会进行名称解析或解析立即返回,可以利用正则表达式对输入IP进行合法性校验。

  2. 域名解析交给其他的第三方库完成,将获取的合法IP设置给GRPC使用

时间: 2025-01-02 03:00:21

grpc源码分析之域名解析的相关文章

[Abp 源码分析]十二、多租户体系与权限验证

0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限校验的. 1.多租户的概念 多租户系统又被称之为 Saas ,比如阿里云就是一个典型的多租户系统,用户本身就是一个租户,可以在上面购买自己的 ECS 实例,并且自己的数据与其他使用者(租户)所隔绝,两者的数据都是不可见的. 那么 Abp 是如何实现数据隔离的呢? 1.1 单部署-单数据库 如果你的软件系统仅部署一个实例,

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

zg手册 之 python2.7.7源码分析(4)-- pyc字节码文件

什么是字节码 python解释器在执行python脚本文件时,对文件中的python源代码进行编译,编译的结果就是byte code(字节码) python虚拟机执行编译好的字节码,完成程序的运行 python会为导入的模块创建字节码文件 字节码文件的创建过程 当a.py依赖b.py时,如在a.py中import b python先检查是否有b.pyc文件(字节码文件),如果有,并且修改时间比b.py晚,就直接调用b.pyc 否则编译b.py生成b.pyc,然后加载新生成的字节码文件 字节码对象

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实