Node.js 中如何获取本地当前活动的 IPv4 地址
结论
Node.js 没有直接返回“唯一活动 IPv4 地址”的 API。设备可能同时连接 WiFi、Ethernet、VPN 或虚拟机网络,程序访问的目标不同,使用的本地地址也可能不同。
可以让操作系统根据路由表选择地址:
- 创建一个 UDP socket。
- 将它连接到目标网络中的地址,例如路由器、已知设备或公网地址。
- 通过
socket.address().address读取操作系统选出的本地 IPv4 地址。 - 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通常比绑定单个网卡地址更合适。
备注:内容仅供参考。
