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函数也是类似的原理