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 = ; i < args->num_args; i++) {
if ( == 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)。
dns_on_resolved_locked函数用于处理dns解析的结果,如果解析失败,则会在一段时间后再次发送一个解析请求。
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 == ) {
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) == ) {
return;
}
if (g_executor.shutting_down == ) {
return;
}
if (g_executor.busy != ) {
/* Thread still working. New work will be picked up by already running
* thread. Not spawning anything. */
return;
} else if (g_executor.pending_join != ) {
/* Pickup the remains of the previous incarnations of the thread. */
gpr_thd_join(g_executor.tid);
g_executor.pending_join = ;
}
/* All previous instances of the thread should have been joined at this point.
* Spawn time! */
g_executor.busy = ;
GPR_ASSERT(gpr_thd_new(&g_executor.tid, closure_exec_thread_func, NULL,
&g_executor.options));
g_executor.pending_join = ;
}
如果队列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 () {
gpr_mu_lock(&g_executor.mu);
if (g_executor.shutting_down != ) {
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 == );
g_executor.busy = ;
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, , 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地址
2.5 dns解析结果的处理dns_on_resolved_locked
static void dns_on_resolved_locked(grpc_exec_ctx *exec_ctx, void *arg,
grpc_error *error) {
if (r->addresses != NULL) {
grpc_lb_addresses *addresses = grpc_lb_addresses_create(
r->addresses->naddrs, NULL /* user_data_vtable */);
for (size_t i = ; i < r->addresses->naddrs; ++i) {
grpc_lb_addresses_set_address(
addresses, i, &r->addresses->addrs[i].addr,
r->addresses->addrs[i].len, false /* is_balancer */,
NULL /* balancer_name */, NULL /* user_data */);
}
} else {
grpc_closure_init(&r->on_retry, dns_on_retry_timer_locked, r,
grpc_combiner_scheduler(r->base.combiner, false));
grpc_timer_init(exec_ctx, &r->retry_timer, next_try, &r->on_retry, now);
}
}
如果r->addresses == NULL,则解析失败,插入dns_on_retry_timer_locked函数在下个时间后调用。
static void dns_on_retry_timer_locked(grpc_exec_ctx *exec_ctx, void *arg,
grpc_error *error) {
dns_resolver *r = arg; r->have_retry_timer = false;
if (error == GRPC_ERROR_NONE) {
if (!r->resolving) {
dns_start_resolving_locked(exec_ctx, r);
}
} GRPC_RESOLVER_UNREF(exec_ctx, &r->base, "retry-timer");
}
dns_on_retry_timer_locked调用dns_start_resolving_locked函数重新开始解析请求2.1,解析失败的请求并不会被直接删除,而是进行多次尝试。
分析连接超时的过程:
1. 设置非法(不存在)的名称
2. 调用getaddrinfo函数进行解析
因为名称不存在,但是域名解析服务器可能认为名称是"存在"的,只是没有找到而已,所以需要更多的时间来进行查找。
3. getaddrinfo函数是阻塞的,在函数返回之前其他的名称只是简单添加到了队列之中,并没有立即进行解析。
4. 多次输入非法的名称之后,非法的解析请求被没有被直接删除,而是多长尝试重新请求,最后导致队列过长,即使设置了合法的名称,但是还没有对它进行解析,结果就是3秒超时。
解决方案:
1. 只允许输入合法IP地址,这样不会进行名称解析或解析立即返回,可以利用正则表达式对输入IP进行合法性校验。
2. 域名解析交给其他的第三方库完成,将获取的合法IP设置给GRPC使用
grpc源码分析之域名解析的更多相关文章
- gRPC源码分析0-导读
gRPC是Google开源的新一代RPC框架,官网是http://www.grpc.io.正式发布于2016年8月,技术栈非常的新,基于HTTP/2,netty4.1,proto3.虽然目前在工程化方 ...
- Go合集,gRPC源码分析,算法合集
年初时,朋友圈见到的最多的就是新的一年新的FlAG,年末时朋友圈最多的也是xxxx就要过去了,你的FLAG实现了吗? 这个公众号2016就已经创建了,但截至今年之前从来没发表过文章,现在想想以前很忙, ...
- gRPC源码分析(c++)
首先需要按照grpc官网上说的办法从github上下载源码,编译,然后跑一跑对应的测试代码.我分析的代码版本为v1.20.0. 在cpp的helloworld例子中,client端,第一个函数是创建c ...
- gRPC源码分析2-Server的建立
gRPC中,Server.Client共享的Class不是很多,所以我们可以单独的分别讲解Server和Client的源码. 通过第一篇,我们知道对于gRPC来说,建立Server是非常简单的,还记得 ...
- gRPC源码分析1-SSL/TLS
引子 前几天看到微信后台团队分享了TLS相关文章,正好gRPC里TLS数据加密是很重要的一块,于是整理出了这篇文章. 在gRPC里,如果仅仅是用来做后端微服务,可以考虑不加密.本文太长,先给个大纲. ...
- frakti && RunPodSandbox 源码分析
listen = flag.String("listen", "/var/run/frakti.sock", "...") hyperEnd ...
- [Abp 源码分析]十二、多租户体系与权限验证
0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限校验的. 1.多租户的 ...
- Envoy 源码分析--程序启动过程
目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...
- Envoy 源码分析--LDS
Envoy 源码分析--LDS LDS 是 Envoy 用来自动获取 listener 的 API. Envoy 通过 API 可以增加.修改或删除 listener. 先来总结下 listener ...
随机推荐
- C# 计算位置居中
(item.Height / 2 - lab.Height / 2)
- U3D外包团队—技术分享 U3d中获得物体的size
以size的x方向为例 1:gameObject.renderer.bounds.size.x;//这个值的结果真实反应出有MeshRenderer这个组件的模型的尺寸.不需要再乘以localScal ...
- 绑定方法与非绑定方法 classmethod和staticmethod
一:绑定方法:特点:绑定给谁就应该是由谁来调用,谁来调用就会将谁当做第一个参数传入 1:绑定给对象的方法:类中定义的函数默认就是绑定给对象的 例: 2:绑定给类的方法:为类中定义的函数加 ...
- WordPress上传到互联网搭建
1.建站流程: 1.购买域名 2.购买空间 3.解析绑定 4.上传安装 2.购买域名 域名的购买可以到相关域名出售的服务商那儿买,如阿里云,主机庙.这里就不做详细解释了 3.购买空间 域名的购买可以到 ...
- h5设计图尺寸
640 标准的话 设计图: 640*1136 body,html 背景图的话需要640*1008的 这样才能整屏刚刚好 750 标准的话 设计图: 750*1334 body,html背景图的话,75 ...
- [译] 关于 Angular 依赖注入你需要知道的
如果你之前没有深入了解 Angular 依赖注入系统,那你现在可能认为 Angular 程序内的根注入器包含所有合并的服务提供商,每一个组件都有它自己的注入器,延迟加载模块有它自己的注入器. 但是,仅 ...
- http content-type 与 java后端处理
http中的常用的content-type有: application/x-www-form-urlencoded;charset=UTF-8 传递表单类数据 application/json;ch ...
- 机器学习 之梯度提升树GBDT
目录 1.基本知识点简介 2.梯度提升树GBDT算法 2.1 思路和原理 2.2 梯度代替残差建立CART回归树 1.基本知识点简介 在集成学习的Boosting提升算法中,有两大家族:第一是AdaB ...
- Luffy之购物车页面搭建
前面已经将一些课程加入购物车中,并保存到了后端的redis数据库中,此时做购物车页面时,我们需要将在前端向后端发送请求,用来获取数据数据 购物车页面 1.首先后端要将数据构建好,后端视图函数如下代码: ...
- 查看局域网指定IP的电脑名
nbtstat -a 192.168.0.139 节点 IP 址址: [192.168.0.140] 范围 ID: [] NetBIOS 远程计算机名称表 名称 类型 状态 ------------- ...