前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nodejs的dns解析源码分析

nodejs的dns解析源码分析

作者头像
theanarkh
发布2019-03-15 16:29:18
4.6K0
发布2019-03-15 16:29:18
举报
文章被收录于专栏:原创分享

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

代码语言:javascript
复制
dns.lookup('www.a.com', function(err, address, family) {
        console.log(address);
});

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

代码语言:javascript
复制
  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。

代码语言:javascript
复制
void Initialize(Local<Object> target,
                Local<Value> unused,
                Local<Context> context) {
  Environment* env = Environment::GetCurrent(context);

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

GetAaddrInfo函数的核心代码如下。

代码语言:javascript
复制
  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的代码。

代码语言:javascript
复制
// 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。

代码语言:javascript
复制
// 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函数。

代码语言:javascript
复制
// 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函数也是类似的原理

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档