nodejs的dns解析源码分析

nodejs的dns解析模块是dns.js,下面是一个使用的例子。

dns.lookup('www.a.com', function(err, address, family) {
        console.log(address);
});

我们根据沿着这个例子的代码看一下nodejs的dns过程。我们先看一下dns.js里的lookup函数,下面是核心代码。

  var req = new GetAddrInfoReqWrap();
  req.callback = callback;
  req.family = family;
  req.hostname = hostname;
  req.oncomplete = all ? onlookupall : onlookup;

  var err = cares.getaddrinfo(req, hostname, family, hints, verbatim);

nodejs设置了一些参数后,调用cares模块(cares_wrap.cc)的getaddrinfo方法,在care_wrap.cc的初始化函数中我们看到, getaddrinfo函数对应的函数是GetAddrInfo。

void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context) {
  Environment* env = Environment::GetCurrent(context);

  env->SetMethod(target, "getaddrinfo", GetAddrInfo);
  ...
}

GetAaddrInfo函数的核心代码如下。

  auto req_wrap = new GetAddrInfoReqWrap(env, req_wrap_obj, args[4]->IsTrue());

  struct addrinfo hints;
  memset(&hints, 0, sizeof(struct addrinfo));
  hints.ai_family = family;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = flags;

  int err = uv_getaddrinfo(env->event_loop(),req_wrap->req(), AfterGetAddrInfo,*hostname,nullptr,&hints);

到这里我们可以看到nodejs是调用了libuv的uv_getaddrinfo进行dns解析的。继续往下看libuv的代码。

// dns解析的入口函数
int uv_getaddrinfo(uv_loop_t* loop,
                  // 上层传进来的req
                   uv_getaddrinfo_t* req,
                   // 解析完后的上层回调
                   uv_getaddrinfo_cb cb,
                   const char* hostname,
                   const char* service,
                   const struct addrinfo* hints) {
  size_t hostname_len;
  size_t service_len;
  size_t hints_len;
  size_t len;
  char* buf;

  if (req == NULL || (hostname == NULL && service == NULL))
    return UV_EINVAL;

  hostname_len = hostname ? strlen(hostname) + 1 : 0;
  service_len = service ? strlen(service) + 1 : 0;
  hints_len = hints ? sizeof(*hints) : 0;
  buf = uv__malloc(hostname_len + service_len + hints_len);

  if (buf == NULL)
    return UV_ENOMEM;

  uv__req_init(loop, req, UV_GETADDRINFO);
  req->loop = loop;
  // 设置请求的回调
  req->cb = cb;
  req->addrinfo = NULL;
  req->hints = NULL;
  req->service = NULL;
  req->hostname = NULL;
  req->retcode = 0;

  /* order matters, see uv_getaddrinfo_done() */
  len = 0;

  if (hints) {
    req->hints = memcpy(buf + len, hints, sizeof(*hints));
    len += sizeof(*hints);
  }

  if (service) {
    req->service = memcpy(buf + len, service, service_len);
    len += service_len;
  }

  if (hostname)
    req->hostname = memcpy(buf + len, hostname, hostname_len);
  // 传了cb是异步
  if (cb) {
    uv__work_submit(loop,
                    &req->wor k_req,
                    UV__WORK_SLOW_IO,
                    uv__getaddrinfo_work,
                    uv__getaddrinfo_done);
    return 0;
  } else {
    // 阻塞式查询,然后执行回调
    uv__getaddrinfo_work(&req->work_req);
    uv__getaddrinfo_done(&req->work_req, 0);
    return req->retcode;
  }
}

从上面代码中可以看到,libuv设置了一些参数,根据是否有cb,判断是阻塞还是非阻塞调用。然后接着往下传。这里以非阻塞的方式为例子进行分析,uv__work_submit函数是给线程池对应的任务队列新增一个节点,然后线程执行的时候,会取下某个节点,执行设置的函数,这里被执行的函数是uv__getaddrinfo_work。

// dns解析的工作函数
static void uv__getaddrinfo_work(struct uv__work* w) {
  uv_getaddrinfo_t* req;
  int err;
  // 根据结构体的字段获取结构体首地址
  req = container_of(w, uv_getaddrinfo_t, work_req);
  // 阻塞在这
  err = getaddrinfo(req->hostname, req->service, req->hints, &req->addrinfo);
  req->retcode = uv__getaddrinfo_translate_error(err);
}

从上面代码我们可以知道,libuv是调用了操作系统的getaddrinfo函数,然后会阻塞在这,所以线程会被挂起,等待查询返回时,libuv会执行uv__getaddrinfo_done函数。

// dns解析完执行的函数
static void uv__getaddrinfo_done(struct uv__work* w, int status) {
  uv_getaddrinfo_t* req;

  req = container_of(w, uv_getaddrinfo_t, work_req);
  uv__req_unregister(req->loop, req);

  /* See initialization in uv_getaddrinfo(). */
  // 释放初始化时申请的内存
  if (req->hints)
    uv__free(req->hints);
  else if (req->service)
    uv__free(req->service);
  else if (req->hostname)
    uv__free(req->hostname);
  else
    assert(0);

  req->hints = NULL;
  req->service = NULL;
  req->hostname = NULL;

  if (status == UV_ECANCELED) {
    assert(req->retcode == 0);
    req->retcode = UV_EAI_CANCELED;
  }
  // 执行上层回调
  if (req->cb)
    req->cb(req, req->retcode, req->addrinfo);
}

代码里的req->cb是在上层的cares_wrap.cc里设置的,即AfterGetAddrInfo,该函数主要是对返回结果做一些处理,然后继续调用上层的js回调函数,在dns.js里我们可以看到,设置的回调是onlookup或者onlookupall,在该函数里会执行用户层的回调函数。即我们传进去的函数。至此,dns解析结束。nodejs的通过ip和端口查找host的lookupserverce函数也是类似的原理

本文分享自微信公众号 - 编程杂技(theanarkh)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏移动开发面面观

Android JNI 开发

几乎稍有经验的Android开发,都会在工作中用到JNI的开发。即使工作中没有涉及到JNI的开发,在我们使用第三方的库时,也经常需要引入.so文件。

36910
来自专栏C/C++基础

C++11 Lambda表达式

C++11新增了很多特性,Lambda表达式(Lambda expression)就是其中之一,很多语言都提供了 Lambda 表达式,如 Python,Jav...

9430
来自专栏小L的魔法馆

C++求值顺序

结果可能是0 1或者是1 1. 因为虽然<<是左结合,但是对于那些没有明确规定运算对象的求值顺序的运算符而言,求值顺序就和优先级,以及结合律无关。 所以...

11520
来自专栏C/C++基础

C++11新关键字

auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用。在C++11中被赋予了新的含义和作用,用于类型推断。

58110
来自专栏C/C++基础

CC++的全缓冲、行缓冲和无缓冲

C/C++中,基于I/O流的操作最终会调用系统接口read()和write()完成I/O操作。为了使程序的运行效率最高,流对象通常会提供缓冲区,以减少调用系统I...

24830
来自专栏小L的魔法馆

范围for语句的整理

由于编译器初始化时会将这些数组形式的元素转换成指向该数组内首元素的指针,这样得到的row类型就是int*,这样原来的for语句就是在一个int*中遍历,这是不合...

10020
来自专栏程序手艺人

团队效率工具: 代码格式化之Clang-format

平时团队进行合作的时候需要注意代码的格式,虽然很难统一每个人的编码风格,但是通过工具能够很好的管理代码格式。这里介绍下clang-format,它是基于clan...

90920
来自专栏容器服务测试账号

从公有云上导镜像导私有云环境

要求环境上的机器有访问公网的能力,同时安装的docker版本最低为1.12.6,推荐使用的版本为17.12.1(或者以上)

16850
来自专栏小L的魔法馆

string类中字符的大小写转换

今天做一道题,要用string类,涉及大小写转换,查看了C++文档,string类没有提供这样的方法,只好自己写。 之后是想到一个比较笨的方法,我把...

24010
来自专栏C/C++基础

C++11就地初始化与列表初始化

在C++11中,结构体或类的数据成员在申明时可以直接赋予一个默认值,初始化的方式有两种,一是使用等号“=”,二是使用大括号列表初始化的方式。注意,使用参考如下代...

52010

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励