← crypto.setFips() 方法 dns.lookup() 方法 →

Node.js dns.getServers()方法教程:系统DNS配置读取、跨平台差异与解析策略设计

著
原创 2026-10-16 Node.js 已有人查阅

一、dns.getServers()的方法定位

dns.getServers()是Node.jsdns模块中一个同步方法,用于读取当前系统配置的DNS服务器地址列表。与dns.lookup()和dns.resolve()这类执行实际域名解析操作的方法不同,getServers()的职责是信息查询——它不发起任何网络请求,也不触发DNS查询,只是从操作系统的网络配置中提取已配置的DNS服务器IP地址。

这个方法的调用开销较低,因为它读取的是本地系统状态而非网络数据。在需要了解当前运行环境的DNS配置时,getServers()提供了一个直接的入口。

二、返回值格式与平台差异

返回值为字符串数组,每个元素是一个IP地址。在Linux和macOS上,这些地址通常来自/etc/resolv.conf中的nameserver指令。在Windows上,来源是网络适配器的DNS设置,可能通过系统注册表或网络配置API读取。

一个值得注意的细节是IPv6地址的格式处理。根据Node.js官方issue#13795的讨论,getServers()在读取IPv6地址时不会在地址两侧添加方括号,即使该地址在setServers()调用时是以[addr]:port的格式传入的。例如,设置'[2001:4860:4860::8888]'后,getServers()返回的是裸地址'2001:4860:4860::8888',不含方括号。这一行为已被标记为“knownlimitation”。

三、基本用法

const dns = require('dns');

const servers = dns.getServers();
console.log('代码号学习编程 - 系统 DNS 服务器配置:');
servers.forEach((server, index) => {
  console.log(`  ${index + 1}. ${server}`);
});

在没有配置DNS服务器或读取失败的情况下,返回值为空数组。

四、权威来源引用

Node.js官方文档将dns.getServers()定义为同步方法,返回系统配置的DNS服务器地址数组。dns.setServers()的文档进一步明确了二者的职责边界:setServers()影响的是dns.resolve()系列函数,不影响dns.lookup()。这一区分对于理解getServers()的适用范围非常重要——读取到的服务器列表反映的是系统配置,但实际使用哪个服务器取决于调用方选择的解析路径。

五、PHP环境下的对照差异

PHP没有与dns.getServers()直接对等的方法来读取系统配置的DNS服务器。PHP的DNS函数(如dns_get_record())执行的是实际的DNS查询,而非读取系统配置。

dns_get_record()返回的是DNS资源记录数组,每条记录包含host、class、type、ttl以及类型特定的附加字段(如A记录的ip,MX记录的target和pri)。这与getServers()返回的“系统DNS服务器地址列表”在语义上不同。

在PHP中获取系统DNS服务器配置,通常需要直接读取/etc/resolv.conf(Linux)或调用系统命令。PHP5.3起dns_get_record()在Windows平台也可用,但其参数行为在早期版本中存在文档不准确的问题:PHP5.2中第四个参数并非如文档所述为可选,省略时会触发警告,该问题在5.3.1中修复。

六、单元测试与边界测试

const assert = require('assert');
const dns = require('dns');

// 返回值类型验证
const servers = dns.getServers();
assert.ok(Array.isArray(servers));
assert.ok(servers.every(s => typeof s === 'string'));

// 在正常配置的系统上应返回非空数组
if (servers.length === 0) {
  console.warn('当前系统未配置 DNS 服务器,或读取失败');
}

// 验证返回值可用于 setServers(往返测试)
if (servers.length > 0) {
  const original = dns.getServers();
  try {
    dns.setServers(servers);
    const after = dns.getServers();
    // 注意:setServers 可能移除端口号,IPv6 可能去除方括号
    assert.ok(after.length === servers.length);
  } finally {
    // 恢复原始配置
    dns.setServers(original);
  }
}

// 边界:setServers 与 getServers 的 IPv6 格式差异
const ipv6Test = '[2001:4860:4860::8888]';
try {
  dns.setServers([ipv6Test]);
  const result = dns.getServers();
  // getServers 返回的 IPv6 不带方括号
  assert.ok(result[0].indexOf('[') === -1, 
    'getServers 返回的 IPv6 地址不含方括号');
} catch (e) {
  // 某些环境可能不支持 IPv6 设置
}

七、常见错误与异常处理

dns.getServers()本身很少抛出异常,因为它不执行网络操作。但在这些场景中可能遇到问题:

系统配置缺失:返回空数组。调用方应当处理空数组的情况,而非假设至少有一个DNS服务器可用。

权限限制:在受限容器或沙箱环境中,读取系统网络配置可能受限,返回空数组或旧缓存值。

与setServers的交互异常:如果在DNS查询进行中调用setServers(),Node.js会抛出错误。getServers()作为纯读取操作,不受此限制。

八、与相邻函数的对比

函数 操作类型 是否触发网络 返回值
dns.getServers() 同步读取 否 系统DNS服务器地址数组
dns.setServers() 同步设置 否 无返回值,修改resolve系列的行为
dns.lookup() 异步解析 是(使用系统设施) 单个IP地址
dns.resolve() 异步解析 是(使用DNS协议) 记录数组

getServers()与lookup()的关键区别在于:前者读取配置,后者执行解析。系统配置了8.8.8.8并不意味着lookup()一定会向8.8.8.8发起查询——lookup()使用操作系统的getaddrinfo(3),其行为受nsswitch.conf和/etc/hosts影响。

九、结构化数据的应用

getServers()返回的扁平数组可以按地址族分类,便于诊断和展示:

function categorizeDnsServers(servers) {
  const result = { ipv4: [], ipv6: [], withPort: [] };
  for (const s of servers) {
    if (s.includes(':')) {
      if (s.includes(']')) {
        // 形如 [addr]:port 或 addr:port
        result.withPort.push(s);
      } else {
        result.ipv6.push(s);
      }
    } else {
      result.ipv4.push(s);
    }
  }
  return result;
}

const servers = dns.getServers();
const categorized = categorizeDnsServers(servers);
console.log('IPv4 DNS:', categorized.ipv4);
console.log('IPv6 DNS:', categorized.ipv6);

十、性能考量

getServers()的性能开销较低。它不涉及网络I/O,读取的是操作系统已缓存或可从配置文件中快速提取的数据。在Linux上,读取/etc/resolv.conf是一次文件系统操作;在Windows上,通过系统API获取网络配置的开销同样在微秒到毫秒量级。

如果需要在应用启动时多次调用getServers(),可以在初始化阶段读取一次并缓存结果。但需要注意,系统DNS配置可能因网络切换(如从Wi-Fi切换到有线)而动态变化,长时间缓存可能返回过时信息。

十一、与Resolver的职责对比

dns.getServers()返回的是系统级的DNS服务器配置。dns.Resolver实例则提供了独立于系统配置的解析能力。每个Resolver实例有自己的服务器列表,通过resolver.setServers()设置后,该实例的解析操作使用自己的服务器,不受全局dns.setServers()影响。

一个常见的模式是:用getServers()获取系统默认配置作为Resolver的初始服务器,然后根据业务需求覆盖或追加特定服务器。但需要注意,getServers()读取的是系统配置,而系统配置的来源可能因平台而异,在容器化环境中可能与宿主机配置不同。

十二、常见误区速查

误区一:认为getServers()返回的服务器会被lookup()使用。lookup()使用操作系统的getaddrinfo(3),其行为由nsswitch.conf和/etc/hosts决定,getServers()的返回值不直接控制lookup()的行为。

误区二:在DNS查询进行中调用setServers()。这会导致错误。虽然getServers()不受此限制,但在修改配置前应确保没有正在进行的解析操作。

误区三:假设getServers()返回的IPv6地址带方括号。如issue#13795所述,返回值不含方括号,即使设置时使用了方括号格式。

误区四:在跨平台应用中假设返回值的来源一致。Linux读取resolv.conf,Windows读取网络适配器配置,macOS可能使用scutil。容器的DNS配置可能被Docker或Kubernetes覆盖。

十三、专业级教程补充内容

练习思考题

  1. 在Docker容器中运行一个Node.js应用,调用dns.getServers()。返回的地址与宿主机是否一致?容器的/etc/resolv.conf通常由Docker守护进程管理,这对getServers()的结果有何影响?

  2. 设计一个DNS诊断工具函数,接收一个域名,依次使用系统配置的每个DNS服务器调用resolver.resolve4(),并报告每个服务器的响应时间。需要注意哪些边界情况?

  3. 如果getServers()返回空数组,而dns.lookup('localhost')仍然能正常解析,解释这一现象的原因。

现在框架中的惯用写法

在需要自定义DNS解析的HTTP客户端中,通常结合getServers()和Resolver:

const dns = require('dns');
const http = require('http');

function createDnsAwareAgent() {
  const resolver = new dns.Resolver();
  const systemServers = dns.getServers();
  
  if (systemServers.length > 0) {
    resolver.setServers(systemServers);
  } else {
    resolver.setServers(['8.8.8.8', '1.1.1.1']);
  }
  
  return {
    resolver,
    lookup(hostname, options, callback) {
      resolver.resolve4(hostname, (err, addresses) => {
        if (err) return callback(err);
        callback(null, addresses[0], 4);
      });
    }
  };
}

这种模式将DNS解析从系统默认行为切换为可控的Resolver,适用于需要绕过系统配置(如/etc/hosts覆盖)的场景。

项目反思

在一个微服务项目中,团队发现某个服务的健康检查偶发性失败。日志显示DNS解析超时。排查时使用dns.getServers()发现容器内的DNS配置指向了Kubernetes的kube-dns服务,但该服务的负载在某些时段较高。

解决方案不是修改getServers()的返回值,而是在的HTTPAgent中配置了独立的Resolver,将解析请求指向一个专用的DNS缓存服务。getServers()在这个排查过程中的价值在于:它快速揭示了容器内的DNS配置状态,帮助定位了问题的源头。

错误处理与类型安全

TypeScript项目中,getServers()返回string[]。对于需要区分地址族的场景,可以添加运行时校验:

function isIPv6Address(addr: string): boolean {
  return addr.includes(':');
}

function getDnsServerReport(): {
  servers: string[];
  hasIPv4: boolean;
  hasIPv6: boolean;
  isEmpty: boolean;
} {
  const servers = dns.getServers();
  return {
    servers,
    hasIPv4: servers.some(s => !isIPv6Address(s)),
    hasIPv6: servers.some(isIPv6Address),
    isEmpty: servers.length === 0
  };
}

十四、延伸阅读与参考文献

Node.js官方文档—dns.getServers()
https://nodejs.org/api/dns.html#dnsgetservers
权威定义,包含返回值格式和同步方法说明。

Node.js官方文档—dns.setServers()
https://nodejs.org/api/dns.html#dnssetserversservers
明确了setServers仅影响resolve系列函数,不影响lookup。

Node.js官方文档—Implementationconsiderations
https://nodejs.org/api/dns.html#implementation-considerations
详述lookup与resolve的实现差异,解释为何系统DNS配置对lookup的影响间接。

GitHubIssue#13795—getServersreturnsIPv6addresseswithoutbrackets
https://github.com/nodejs/node/issues/13795
IPv6格式问题的官方讨论,确认了getServers返回裸地址的行为。

DenoPR#32536—improvenode:dnscompat
https://app.semanticdiff.com/gh/denoland/deno/pull/32536
Deno运行时对getServers跨平台实现的改进,可作为替代实现的参考。

PHPdns_get_record()手册
https://www.php.net/manual/en/function.dns-get-record.php
PHP对等DNS查询函数的权威文档,说明了记录类型和返回结构。

PHPBug#52456—dns_get_recordfourthparameterin5.2
https://bugs.php.net/bug.php?id=52456
PHP5.2中参数可选性问题的修复记录,反映了跨版本行为差异。

StackOverflow—ReadandsetDNSserveronWindowsandLinux
https://stackoverflow.com/questions/79236513
实际项目中读取和设置系统DNS配置的讨论,涉及跨平台差异。

← crypto.setFips() 方法 dns.lookup() 方法 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号