dns
模块是 Node.js 的核心模块之一,提供了域名系统(DNS)查询功能,允许开发者将域名解析为 IP 地址、反向解析 IP 地址为域名,以及查询各种 DNS 记录(如 MX、TXT、SRV 等)。
一、模块引入与基本概念
const dns = require('dns');
DNS 的核心功能是将人类可读的域名(如 www.example.com
)转换为机器可读的 IP 地址(如 192.0.2.1
)。Node.js 的 dns
模块封装了这一过程,提供了编程接口。
1. 解析方式
Node.js 的 dns
模块提供两种解析方式:
- 底层操作系统解析:使用
dns.lookup()
,依赖操作系统的getaddrinfo
功能,不经过网络通信。 - 网络 DNS 查询:使用
dns.resolve()
等方法,直接连接 DNS 服务器进行查询。
二、核心方法详解
1. dns.lookup(hostname[, options], callback)
功能:将域名解析为第一个找到的 IPv4 或 IPv6 地址(类似 ping
命令的行为)。
参数:
hostname
:要解析的域名。options
(可选):family
:指定 IP 版本(4
或6
)。hints
:设置查询类型(如dns.ADDRCONFIG
)。all
:设为true
时返回所有地址。
callback
:回调函数,参数为(err, address, family)
。
示例:
dns.lookup('www.example.com', (err, address, family) => {
if (err) throw err;
console.log(`IP: ${address}, 版本: IPv${family}`);
});
2. dns.resolve(hostname[, rrtype], callback)
功能:查询指定类型的 DNS 记录,返回数组。
参数:
rrtype
:记录类型,默认为'A'
(IPv4)。'A'
:IPv4 地址。'AAAA'
:IPv6 地址。'MX'
:邮件交换记录。'TXT'
:文本记录。'SRV'
:服务记录。'PTR'
:反向解析(用于dns.reverse
)。'NS'
:域名服务器记录。'CNAME'
:别名记录。'SOA'
:授权记录。
示例:
dns.resolve('example.com', 'MX', (err, records) => {
if (err) throw err;
console.log('MX 记录:', records);
});
3. dns.reverse(ip, callback)
功能:将 IP 地址反向解析为域名(PTR 记录)。
示例:
dns.reverse('8.8.8.8', (err, hostnames) => {
if (err) throw err;
console.log('反向解析结果:', hostnames);
});
4. dns.setServers(servers)
功能:设置自定义 DNS 服务器列表。
示例:
dns.setServers(['8.8.8.8', '8.8.4.4']); // 使用 Google DNS
5. dns.getServers()
功能:获取当前配置的 DNS 服务器列表。
示例:
console.log(dns.getServers()); // 输出当前 DNS 服务器
三、Promise 版本(Node.js v10.6.0+)
dns
模块提供了基于 Promise 的 API,通过 dns.promises
访问:
const { promises: dnsPromises } = require('dns');
async function resolveExample() {
try {
const result = await dnsPromises.lookup('example.com');
console.log('IP:', result.address);
} catch (err) {
console.error('解析失败:', err);
}
}
resolveExample();
四、高级用法与技巧
1. 批量解析域名
const domains = ['google.com', 'github.com', 'example.com'];
Promise.all(domains.map(domain => dnsPromises.lookup(domain)))
.then(results => {
results.forEach((result, index) => {
console.log(`${domains[index]} => ${result.address}`);
});
})
.catch(err => console.error('批量解析失败:', err));
2. 缓存 DNS 查询结果
为避免重复查询,可以手动实现缓存:
const cache = new Map();
async function cachedLookup(domain) {
if (cache.has(domain)) {
return cache.get(domain);
}
const result = await dnsPromises.lookup(domain);
cache.set(domain, result);
return result;
}
3. 自定义超时控制
function lookupWithTimeout(domain, timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`DNS 查询超时 (${timeout}ms)`));
}, timeout);
dnsPromises.lookup(domain)
.then(result => {
clearTimeout(timer);
resolve(result);
})
.catch(err => {
clearTimeout(timer);
reject(err);
});
});
}
五、错误处理
DNS 查询可能因多种原因失败,需捕获并处理错误:
dns.lookup('nonexistent.example.com', (err, address) => {
if (err) {
if (err.code === 'ENOTFOUND') {
console.log('域名不存在');
} else {
console.error('未知错误:', err);
}
return;
}
console.log('IP:', address);
});
常见错误代码:
ENOTFOUND
:域名不存在。ESERVFAIL
:DNS 服务器返回失败。ETIMEOUT
:查询超时。ECONNREFUSED
:无法连接到 DNS 服务器。
六、性能优化建议
- 避免频繁查询:对频繁访问的域名进行缓存。
- 限制并发查询:使用队列或限流机制防止过多并发查询。
- 合理设置超时:根据网络环境调整查询超时时间。
- 使用本地缓存工具:如
NodeLocal DNSCache
减少远程查询。
七、与 net.Socket
的协同使用
在建立 TCP 连接前,通常需要先解析域名:
const net = require('net');
dns.lookup('example.com', (err, address) => {
if (err) throw err;
const socket = net.createConnection({ port: 80, host: address }, () => {
console.log('已连接到服务器');
});
});
八、底层实现原理
dns.lookup()
使用操作系统的getaddrinfo
,通过线程池执行,可能阻塞(但 Node.js 内部优化了线程池管理)。dns.resolve()
等方法基于c-ares
库,完全异步,不依赖操作系统设施。
九、实际应用场景
- 邮件服务器配置:查询 MX 记录以确定邮件路由。
- 负载均衡:根据 SRV 记录发现服务实例。
- 安全验证:检查域名的 TXT 记录(如 SPF、DKIM)。
- 服务发现:在微服务架构中解析服务地址。