PAYATHON 2026

Node.js 中如何获取本地当前活动的 IPv4 地址

技术老齐

结论

Node.js 没有直接返回“唯一活动 IPv4 地址”的 API。设备可能同时连接 WiFi、Ethernet、VPN 或虚拟机网络,程序访问的目标不同,使用的本地地址也可能不同。

可以让操作系统根据路由表选择地址:

  1. 创建一个 UDP socket。
  2. 将它连接到目标网络中的地址,例如路由器、已知设备或公网地址。
  3. 通过 socket.address().address 读取操作系统选出的本地 IPv4 地址。
  4. HTTP 服务监听 0.0.0.0,设备发现或对外公布服务时再使用选出的地址。

这种方法不依赖网卡名称,也无须根据 os.networkInterfaces() 的返回顺序猜测应该使用哪个接口。

根据路由获取本地 IPv4 地址

const dgram = require('node:dgram');

function getLocalIPv4(remoteAddress = '1.1.1.1', remotePort = 53) {
  return new Promise((resolve, reject) => {
    const socket = dgram.createSocket('udp4');

    socket.once('error', (error) => {
      socket.close();
      reject(error);
    });

    socket.connect(remotePort, remoteAddress, () => {
      const localAddress = socket.address();
      socket.close();

      resolve(localAddress.address);
    });
  });
}

getLocalIPv4()
  .then((address) => {
    console.log('当前路由使用的本地 IPv4 地址:', address);
  })
  .catch((error) => {
    console.error('无法确定本地 IPv4 地址:', error);
  });

这里的 socket.connect() 不会建立 TCP 连接,也不需要远端返回数据。操作系统会查询路由表,确定访问 remoteAddress 应使用哪个网络接口,并为 socket 选择对应的本地地址。

例如,笔记本电脑当前通过 WiFi 使用 10.x.x.x 网络访问外部地址时,通常会得到 WiFi 接口上的 10.x.x.x 地址。即使某个 Ethernet 接口仍保留 192.168.x.x 地址,也不会因此选中该接口。

根据通信目标选择探测地址

示例中的 1.1.1.1 可用于判断默认路由所使用的网络接口。如果程序只在局域网内通信,可以传入该局域网中的已知地址,例如路由器地址:

const address = await getLocalIPv4('10.0.0.1', 53);
console.log(address);

也可以使用已知设备的地址:

const address = await getLocalIPv4('10.0.0.25', 3000);
console.log(address);

探测地址不同,结果的含义也不同:

  • 公网地址对应访问公网时使用的本地地址。
  • 局域网设备地址对应访问该设备时使用的本地地址。
  • VPN 网段地址可能对应 VPN 接口的地址。

多网卡设备没有脱离通信目标的“唯一活动 IP”。

HTTP 服务监听所有接口

如果 HTTP 服务需要供局域网中的其他设备访问,通常无须绑定自动检测出的具体 IP,可以监听 0.0.0.0:

const http = require('node:http');

const server = http.createServer((request, response) => {
  response.writeHead(200, {
    'Content-Type': 'text/plain; charset=utf-8',
  });
  response.end('OK');
});

server.listen(3000, '0.0.0.0', () => {
  console.log('服务器正在监听所有 IPv4 网络接口的 3000 端口');
});

0.0.0.0 表示接受所有本地 IPv4 接口上的连接。它只能用于监听,其他设备不能用它访问服务器。进行设备发现或服务广播时,仍需公布实际的 10.x.x.x 地址。

监听服务和获取地址可以组合使用:

const http = require('node:http');
const dgram = require('node:dgram');

function getLocalIPv4(remoteAddress, remotePort = 53) {
  return new Promise((resolve, reject) => {
    const socket = dgram.createSocket('udp4');

    socket.once('error', (error) => {
      socket.close();
      reject(error);
    });

    socket.connect(remotePort, remoteAddress, () => {
      const { address } = socket.address();
      socket.close();
      resolve(address);
    });
  });
}

async function startServer() {
  const localIPv4 = await getLocalIPv4('10.0.0.1');

  const server = http.createServer((request, response) => {
    response.end('OK');
  });

  server.listen(3000, '0.0.0.0', () => {
    console.log(`局域网访问地址: http://${localIPv4}:3000`);
  });
}

startServer().catch(console.error);

需要将 10.0.0.1 替换为目标局域网内稳定且可路由的地址。

用 os.networkInterfaces() 列出候选地址

程序需要显示所有非回环 IPv4 地址时,可以使用 os.networkInterfaces()。判断实际使用的地址时,不要只看接口名称或排列顺序:

const os = require('node:os');
const net = require('node:net');

function getLocalIPv4Candidates() {
  return Object.entries(os.networkInterfaces()).flatMap(
    ([interfaceName, addresses]) =>
      (addresses ?? [])
        .filter((item) => {
          const isIPv4 = item.family === 'IPv4' || item.family === 4;

          return (
            isIPv4 &&
            !item.internal &&
            net.isIPv4(item.address)
          );
        })
        .map((item) => ({
          interface: interfaceName,
          address: item.address,
          netmask: item.netmask,
          cidr: item.cidr,
        }))
  );
}

console.log(getLocalIPv4Candidates());

这些地址都只是候选项。接口名称可能是 en0、eth0、wlan0,也可能采用其他系统相关名称。非回环接口同样不一定承担当前目标的路由。

注意事项

  • Ethernet 接口即使没有连接当前路由器,也可能保留静态地址或之前分配的地址。仅检查地址是否存在,无法判断它是否负责当前通信。
  • 安装 VPN、Docker 或虚拟机软件后,系统通常会增加虚拟网络接口。
  • 设备同时连接多个网络时,不同目标可能对应不同的本地 IPv4 地址。
  • 没有可用路由时,UDP 探测可能失败,调用方需要处理异常。
  • 使用公网地址探测默认路由不能验证互联网是否可用,因为示例没有发送应用数据,也没有等待远端响应。
  • 服务监听成功后,如果其他设备仍无法连接,还需检查系统防火墙、路由器的客户端隔离设置和设备发现机制。
  • 程序需要同时服务多个网络时,监听 0.0.0.0 通常比绑定单个网卡地址更合适。

备注:内容仅供参考。