nodejs的dns解析源码分析
发布于 4 年前 作者 theanarkh 4349 次浏览 来自 分享

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解析结束。

回到顶部