本文深入解析Node.js中dns模块的两种核心解析机制:基于操作系统底层工具的dns.lookup与基于网络通信的dns.resolve系列函数。详细对比了它们在配置文件使用、线程池依赖及行为差异上的关键点,并逐一介绍了getServers、setServers、Resolver类及各类资源记录查询方法的用法与参数配置,帮助开发者根据实际需求选择正确的DNS查询方式。
本文详细介绍Node.js中dns模块的具体用法,旨在为开发者提供参考。
Node.js API详解之 dns
dns(域名服务器)模块主要包含两类不同的解析函数:
第一类函数依赖操作系统底层工具进行域名解析,不直接涉及网络通信。此类函数仅包含dns.lookup()。
例如,查询baidu.com的解析结果:
const dns = require('dns');
dns.lookup('www.baidu.com', (err, address, family) => {
console.log('IP 地址: %j 地址族: IPv%s', address, family);
});
// IP 地址: "180.149.131.98" 地址族: IPv4
第二类函数则直接连接真实的DNS服务器进行域名解析,始终通过网络进行DNS查询。 这类函数涵盖了dns模块中除dns.lookup()之外的所有其他函数。 它们使用与dns.lookup()不同的配置文件(例如/etc/hosts)。 这类函数适合那些不希望使用底层操作系统工具进行域名解析,而希望直接通过网络进行DNS查询的开发者。
例如,解析‘archive.org'并逆向解析返回的IP地址:
const dns = require('dns');
dns.resolve4('archive.org', (err, addresses) => {
if (err) throw err;
console.log(`IP 地址: ${JSON.stringify(addresses)}`);
addresses.forEach((a) => {
dns.reverse(a, (err, hostnames) => {
if (err) {
throw err;
}
console.log(`IP 地址 ${a} 逆向解析到域名: ${JSON.stringify(hostnames)}`);
});
});
});
// IP 地址: ["207.241.224.2"]
// IP 地址 207.241.224.2 逆向解析到域名: ["www.archive.org"]
dns.getServers()
说明:
返回当前用于DNS解析的IP地址字符串数组,格式遵循rfc5952标准。 若使用自定义端口,字符串中将包含端口部分。
示例:
const dns= require('dns');
console.log( dns.getServers() );
// [ '172.116.20.254', '127.0.0.1' ]
dns.setServers(servers)
说明:
设置DNS解析时使用的服务器IP地址,servers参数为rfc5952格式的地址数组。 若端口为IANA默认端口(53),则可忽略端口部分。
示例:
const dns = require('dns');
dns.setServers([ '172.116.20.254', '127.1.1.1' ])
console.log( dns.getServers() );
// [ '172.116.20.254', '127.1.1.1' ]
dns.lookup(hostname[, options], callback)
说明:
解析hostname(例如:'nodejs.org')找到的第一个A(IPv4)或AAAA(IPv6)记录。 options可以是对象或整数。若未提供options,则IPv4和IPv6均有效。若options为整数,只能是4或6。 此外,options可以是包含以下属性的对象: family:IP地址族。若提供,必须为整数4或6。若未提供,则接受IPv4和IPv6地址。 all:值为true时,回调函数返回包含所有解析后地址的数组,否则只返回一个地址。默认值为false hints:若提供,必须是一个或多个支持的getaddrinfo标识。若未提供,则不传递任何标识给getaddrinfo。 可通过逻辑OR操作传递多个标识。支持的getaddrinfo标识: dns.ADDRCONFIG: 返回当前系统支持的地址类型。例如,若当前系统至少配置了一个IPv4地址,则返回IPv4地址。不考虑回环地址。 dns.V4MAPPED: 如果指定了IPv6家族,但未找到IPv6地址,将返回IPv4映射的IPv6地址。在某些操作系统中不支持(如FreeBSD 10.1)。 回调函数包含(err, address, family)参数。 address是IPv4或IPv6地址字符串。 family是整数4或6,表示地址族(不一定是最初传递给查找的值)。 当all属性被设置为true时,回调函数参数变为(err, addresses),addresses则变成由address和family属性组成的对象数组。 dns.lookup()不需要与DNS协议有任何关系。它仅仅是一个连接名字和地址的操作系统功能。 在任何node.js程序中,它的实现对表现有一些微妙但是重要的影响。 尽管dns.lookup()和各种dns.resolve*()/ dns.reverse()函数有相同的目标将网络的名字与网络地址联系在一起(反之亦然), 他们的行为是完全不同的。这些差异可以有微妙但重大影响着Node.js程序行为。
示例:
const dns = require('dns');
const options = {
family: 6,
hints: dns.ADDRCONFIG | dns.V4MAPPED,
};
dns.lookup('nodejs.cn', options, (err, address, family) =>
console.log('address: %j family: IPv%s', address, family));
// address: "::ffff:112.124.39.54" family: IPv6
options.all = true;
dns.lookup('nodejs.cn', options, (err, addresses) =>
console.log('addresses: %j', addresses));
// addresses: [{"address":"::ffff:112.124.39.54","family":6}]
dns.lookupService(address, port, callback)
说明:
将参数address和port传入操作系统底层getnameinfo服务来解析处理并返回主机名。 如果address不是有效的IP地址,会抛出TypeError。port必须是一个整数。如果不是规定的端口号,会抛出TypeError。 出错情况下,err是一个Error对象,err.code代码错误码。
示例:
const dns = require('dns');
dns.lookupService('127.0.0.1', 22, (err, hostname, service) => {
console.log(hostname, service);
// localhost ssh
});
dns.reverse(ip, callback)
说明:
执行一个反向DNS查询返回IPv4或IPv6地址的主机名的数组。
示例:
const dns = require('dns');
dns.reverse('101.20.17.16', (err, hostname) => {
console.log(hostname);
});
dns.Resolver 类
说明:
DNS请求的独立解析程序。 使用默认的设置创建一个新的解析程序。 为一个解析程序设置servers使用resolver.setServers(),它不会影响其他的解析程序:
示例:
const { Resolver } = require('dns');
const resolver = new Resolver();
resolver.setServers(['4.4.4.4']);
resolver.resolve4('baidu.com', (err, addresses) => {
console.log(addresses);
});
resolver.cancel()
说明:
取消这个解析程序的未解决的DNS查询,相应的回调用一个ECANCELLED码调用。
示例:
const { Resolver } = require('dns');
const resolver = new Resolver();
resolver.setServers(['4.4.4.4']);
resolver.resolve4('baidu.com', (err, addresses) => {
console.log(addresses);
});
resolver.cancel();
dns.resolve(hostname[, rrtype], callback)
说明:
hostname:解析的主机名。 rrtype:资源记录类型。默认: ‘A'。 使用DNS协议来解析一个主机名(e.g. ‘nodejs.org')为一个资源记录的数组。 回调函数的参数为(err, records)。当成功时,records将是一个资源记录的数组。它的类型和结构取决于rrtype。 下面是不同rrtype对应resolve的快捷方法:

示例:
const dns = require('dns');
dns.resolve('archive.org', 'A', (err, addresses) => {
if (err) throw err;
console.log(`IP 地址: ${JSON.stringify(addresses)}`);
});
// IP 地址: ["207.241.224.2"]
dns.resolve4(hostname[, options], callback)
说明:
使用DNS协议解析IPv4地址主机名(A记录)。 adresses参数是传递给callback函数的IPv4地址数组。(例如:[‘74.125.79.104', ‘74.125.79.105', ‘74.125.79.106']) hostname:需要解析的主机名 options.ttl:记录每一条记录的存活次数 (TTL)。如果为 true, 返回的结果将会为 Object 的数组,就像 { address: ‘1.2.3.4', ttl: 60 } 带有 TTL 秒数的记录,而不是 string 的数组。
示例:
const dns = require('dns');
dns.resolve4('archive.org', {ttl: true}, (err, addresses) => {
if (err) throw err;
console.log(`IPv4地址数组: ${JSON.stringify(addresses)}`);
});
// IPv4地址数组: [{"address":"207.241.224.2","ttl":58}]
dns.resolve6(hostname[, options], callback)
说明:
使用DNS协议解析IPv6地址主机名(AAAA记录)。 adresses参数是传递给callback函数的IPv6地址数组。
示例:
const dns = require('dns');
dns.resolve6('www.taobao.com', {ttl: true}, (err, addresses) => {
if (err) throw err;
console.log(`IPv6地址主机名: ${JSON.stringify(addresses)}`);
});
// IPv6地址主机名: []
dns.resolveCname(hostname, callback)
说明:
使用DNS协议解析CNAME记录主机名。 adresses参数是传递给回调函数规范内有效的主机名数组(例如:[‘bar.example.com'])。
示例:
const dns = require('dns');
dns.resolveCname('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`CNAME记录主机名: ${JSON.stringify(addresses)}`);
});
// CNAME记录主机名: ["www.taobao.com.danuoyi.tbcache.com"]
dns.resolveMx(hostname, callback)
说明:
使用DNS协议处理邮件交换记录主机名(MX记录)。 adresses参数是传递给回调函数的主机名对象数组,对象包含priority和exchange属性(例如: [{priority: 10, exchange: ‘mx.example.com'}, …])。
示例:
const dns = require('dns');
dns.resolveMx('mail.qq.com', (err, addresses) => {
if (err) throw err;
console.log(`MX记录: ${JSON.stringify(addresses)}`);
});
dns.resolveNaptr(hostname, callback)
说明:
使用DNS协议来处理基于正则表达式匹配的记录(NAPTR记录)的主机名。 adresses参数是传递给回调函数的主机名对象数组,对象包含属性: flags service regexp replacement order preference
示例:
const dns = require('dns');
dns.resolveNaptr('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`NAPTR记录: ${JSON.stringify(addresses)}`);
});
dns.resolveNs(hostname, callback)
说明:
使用DNS协议处理名称服务器主机名记录(NS记录)。 adresses为有效的名称服务器记录主机名数组(eg:[‘ns1.example.com', ‘ns2.example.com'])。
示例:
const dns = require('dns');
dns.resolveNs('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`NS记录: ${JSON.stringify(addresses)}`);
});
dns.resolvePtr(hostname, callback)
说明:
使用DNS协议处理主机名引用记录(PTR记录)。 addresses参数将一个字符串数组传递给回调函数callback,其中包含回复记录。
示例:
const dns = require('dns');
dns.resolvePtr('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`PTR记录: ${JSON.stringify(addresses)}`);
});
dns.resolveSoa(hostname, callback)
说明:
使用DNS协议处理主机名子域名记录(SOA记录)。addresses参数为一个对象包含以下属性: nsname hostmaster serial refresh retry expire minttl
示例:
const dns = require('dns');
dns.resolveSoa('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`SOA记录: ${JSON.stringify(addresses)}`);
});
dns.resolveSrv(hostname, callback)
说明:
使用DNS协议来处理主机名服务记录(SRV记录)。 callback函数返回的addresses参数为对象数组,每个对象包含以下属性: priority weight port name
示例:
const dns = require('dns');
dns.resolveSrv('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`SRV记录: ${JSON.stringify(addresses)}`);
});
dns.resolveTxt(hostname, callback)
说明:
使用DNS协议处理文本查询主机名(TXT记录)。回调函数callback会返回records参数, 它是一个文本记录与主机名一一对应的二维数组(例如:[ [‘v=spf1 ip4:0.0.0.0 ‘, ‘~all' ]]). 每个数组文本块包含一条记录。根据用例,这些可以是连接在一起或单独对待。
示例:
const dns = require('dns');
dns.resolveTxt('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`TXT记录: ${JSON.stringify(addresses)}`);
});
dns.resolveAny(hostname, callback)
说明:
使用DNS协议解析所有记录。 callback函数的参数将是一个包含各种类型记录的数组。每个对象都有一个属性type,指示当前记录的类型。 “A”、”AAAA”、”CNAME”、”MX”、”NAPTR”、”NS”、”PTR”、”SOA”、”SRV”、”TXT”等
示例:
const dns = require('dns');
dns.resolveAny('www.taobao.com', (err, addresses) => {
if (err) throw err;
console.log(`${JSON.stringify(addresses)}`);
});
// [ { type: 'A', address: '127.0.0.1', ttl: 299 },
// { type: 'CNAME', value: 'example.com' },
// { type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 },
// { type: 'NS', value: 'ns1.example.com' },
// { type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] },
// { type: 'SOA',
// nsname: 'ns1.example.com',
// hostmaster: 'admin.example.com',
// serial: 156696742,
// refresh: 900,
// retry: 900,
// expire: 1800,
// minttl: 60 } ]
错误码
说明:
每个DNS查询可以返回一个错误代码如下: dns.NODATA:DNS服务返回没有数据。 dns.FORMERR:DNS服务器查询没有格式化。 dns.SERVFAIL:DNS服务器返回失败。 dns.NOTFOUND:域名未找到。 dns.NOIMP:DNS服务器不执行请求的操作。 dns.REFUSED:查询DNS服务器拒绝。 dns.BADQUERY:未格式化DNS查询。 dns.BADNAME:未格式化主机名 dns.BADFAMILY:没有提供地址族 dns.BADRESP:未格式化DNS回复 dns.CONNREFUSED:无法连接DNS服务器 dns.TIMEOUT:连接DNS服务器超时 dns.EOF:文件末尾 dns.FILE:读取文件错误 dns.NOMEM:内存溢出 dns.DESTRUCTION:通道以及销毁 dns.BADSTR:未格式化字符串 dns.BADFLAGS:指定非法标记 dns.NONAME:给定的主机名不是数字。 dns.BADHINTS:指定非法的提示标志。 dns.NOTINITIALIZED:c-ares异步DNS请求库初始化未完成。 dns.LOADIPHLPAPI:加载iphlpapi.dll(Windows IP辅助API应用程序接口模块)错误 dns.ADDRGETNETWORKPARAMS:找不到GetNetworkParams(读取本机DNS信息)函数 dns.CANCELLED:DNS查询取消
dns.lookup() 与 dns.resolve(), dns.resolve*() and dns.reverse()
说明:
在底层,dns.lookup()使用操作系统设施与大多数其他程序相同。 例如,dns.lookup()几乎总是解析给定的主机名与ping命令一样。 在许多类POSIX操作系统中, dns.lookup()函数的行为可以通过改变nsswitch.conf(5)并且/或resolv.conf(5)设置进行改变, 但是需要注意改变这些文件就意味着改变所有正在这个操作系统中运行 的所有进程的行为。 尽管以异步JavaScript的角度来调用dns.lookup(),但在内部libuv底层线程池中却是同步的调用getaddrinfo(3) dns.resolve(), dns.resolve*() and dns.reverse()这些功能实现与dns.lookup()截然不同。 它们不仅没有使用getaddrinfo(3)并且通过网络执行DNS查询。使用异步网络通信,并且没有使用libuv线程池。 因此,这些函数不会像使用libuv线程池的dns.lookup()函数一样会对其它进程有负面影响。 它们不像dns.lookup()一样使用相同的配置文件。例如,它们不会使用来自/etc/hosts配置。
希望本文所述对大家node.js程序设计有所帮助。