在桌面应用开发中,获取设备的网络信息是一个常见的需求。无论是网络诊断工具、系统监控应用,还是需要显示设备连接状态的应用程序,都需要能够获取和显示设备的 IP 地址信息。

方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
Node.js os.networkInterfaces() | 跨平台,无需额外依赖,简单可靠 | 无法直接获取公网 IP | 推荐使用,最可靠 |
系统命令(ifconfig/ipconfig) | 可获取详细信息 | 需要解析输出,平台差异大 | 复杂场景 |
第三方网络库 | 功能完整 | 增加依赖,可能过度设计 | 特殊需求 |
采用Node.js 内置模块 + 网络请求的组合方案:
os.networkInterfaces()**:获取所有网络接口的本地 IP 地址 - Node.js 内置 API,跨平台兼容os.hostname()**:获取设备主机名在主进程中实现 IP 地址获取功能:
const { app, BrowserWindow, ipcMain } = require('electron');
const os = require('os');
const https = require('https');
const http = require('http');
// 获取IP地址
asyncfunction getIPAddresses() {
const platform = process.platform;
console.log('获取IP地址,平台:', platform);
try {
// 使用Node.js内置模块获取网络接口信息
const networkInterfaces = os.networkInterfaces();
const ipAddresses = [];
// 遍历所有网络接口
for (const [interfaceName, addresses] ofObject.entries(networkInterfaces)) {
if (!addresses) continue;
for (const address of addresses) {
// 跳过内部(loopback)和非IPv4地址
if (address.internal || address.family !== 'IPv4') {
continue;
}
ipAddresses.push({
interface: interfaceName,
address: address.address,
netmask: address.netmask,
mac: address.mac || null
});
}
}
// 按接口名称排序(优先显示以太网和WiFi接口)
ipAddresses.sort((a, b) => {
const priority = (name) => {
const lower = name.toLowerCase();
if (lower.includes('ethernet') || lower.includes('eth')) return1;
if (lower.includes('wifi') || lower.includes('wlan') || lower.includes('airport')) return2;
if (lower.includes('en0') || lower.includes('en1')) return3;
return4;
};
return priority(a.interface) - priority(b.interface);
});
// 获取主机名
const hostname = os.hostname();
// 尝试获取公网IP(可选,需要网络请求)
let publicIP = null;
try {
// 使用多个服务尝试获取公网IP
const publicIPServices = [
'https://api.ipify.org?format=json',
'https://api.ip.sb/ip',
'https://ifconfig.me/ip'
];
for (const service of publicIPServices) {
try {
const parsedUrl = new URL(service);
const client = parsedUrl.protocol === 'https:' ? https : http;
const result = awaitnewPromise((resolve, reject) => {
const req = client.get(service, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
try {
const json = JSON.parse(data);
resolve(json.ip || data.trim());
} catch (e) {
resolve(data.trim());
}
});
});
req.on('error', reject);
req.setTimeout(3000, () => {
req.destroy();
reject(newError('Timeout'));
});
});
// 验证IP地址格式
if (result && /^\d+\.\d+\.\d+\.\d+$/.test(result)) {
publicIP = result;
console.log('获取到公网IP:', publicIP);
break;
}
} catch (error) {
console.warn(`从 ${service}获取公网IP失败:`, error.message);
continue;
}
}
} catch (error) {
console.warn('获取公网IP失败:', error.message);
}
return {
hostname: hostname,
interfaces: ipAddresses,
publicIP: publicIP,
platform: platform
};
} catch (error) {
console.error('获取IP地址失败:', error);
return {
hostname: os.hostname(),
interfaces: [],
publicIP: null,
platform: platform,
error: error.message
};
}
}
// IPC处理器:获取IP地址
ipcMain.handle('get-ip-addresses', async (event) => {
console.log('收到获取IP地址请求');
try {
const ipInfo = await getIPAddresses();
console.log('IP地址信息:', ipInfo);
return ipInfo;
} catch (error) {
console.error('获取IP地址错误:', error);
return {
hostname: os.hostname(),
interfaces: [],
publicIP: null,
platform: process.platform,
error: error.message
};
}
});
在预加载脚本中暴露安全的 API 给渲染进程:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// 获取IP地址
getIPAddresses: () => {
return ipcRenderer.invoke('get-ip-addresses');
}
});
在渲染进程中调用 API 并显示 IP 地址信息:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>IP地址获取示例</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
button {
padding: 15px30px;
font-size: 16px;
font-weight: 600;
color: white;
background: rgba(255, 255, 255, 0.2);
border: 2px solid white;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
backdrop-filter: blur(10px);
}
button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
#ip-info {
margin-top: 20px;
padding: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
backdrop-filter: blur(10px);
max-width: 800px;
}
</style>
</head>
<body>
<button onclick="getIPAddresses()">🌐 获取IP地址</button>
<div id="ip-info" style="display: none;">
<h2>IP地址信息</h2>
<div id="ip-details"></div>
</div>
<script>
// 获取IP地址
async function getIPAddresses() {
console.log('点击获取IP地址按钮');
const ipInfoDiv = document.getElementById('ip-info');
const ipDetailsDiv = document.getElementById('ip-details');
try {
if (window.electronAPI && window.electronAPI.getIPAddresses) {
console.log('使用 Electron IPC 获取IP地址');
const ipInfo = await window.electronAPI.getIPAddresses();
displayIPInfo(ipInfo);
} else {
alert('IP地址功能不可用');
}
} catch (error) {
console.error('获取IP地址失败:', error);
ipDetailsDiv.innerHTML = '<p style="color: #ff6b6b;">获取IP地址失败: ' + error.message + '</p>';
ipInfoDiv.style.display = 'block';
}
}
function displayIPInfo(ipInfo) {
const ipInfoDiv = document.getElementById('ip-info');
const ipDetailsDiv = document.getElementById('ip-details');
let html = '';
// 显示错误信息
if (ipInfo.error) {
html += `<div style="color: #ff6b6b; margin-bottom: 10px;">
<strong>错误:</strong> ${ipInfo.error}
</div>`;
}
// 显示平台信息
if (ipInfo.platform) {
html += `<div style="margin-bottom: 15px;">
<strong>平台:</strong><span style="color: #51cf66;">${ipInfo.platform}</span>
</div>`;
}
// 显示主机名
if (ipInfo.hostname) {
html += `<div style="margin-bottom: 15px;">
<strong>主机名:</strong>
<span style="color: #51cf66; font-size: 18px; font-weight: bold;">${ipInfo.hostname}</span>
</div>`;
}
// 显示公网IP
if (ipInfo.publicIP) {
html += `<div style="margin-bottom: 15px; padding: 10px; background: rgba(81, 207, 102, 0.2); border-radius: 5px; border-left: 3px solid #51cf66;">
<strong>公网IP:</strong>
<span style="color: #51cf66; font-size: 18px; font-weight: bold; font-family: monospace;">${ipInfo.publicIP}</span>
</div>`;
} else {
html += `<div style="margin-bottom: 15px; padding: 10px; background: rgba(255, 212, 59, 0.2); border-radius: 5px;">
<strong>公网IP:</strong>
<span style="color: #ffd43b;">无法获取(可能需要网络连接)</span>
</div>`;
}
// 显示网络接口
if (ipInfo.interfaces && ipInfo.interfaces.length > 0) {
html += `<div style="margin-top: 20px;">
<strong style="font-size: 16px;">网络接口 (${ipInfo.interfaces.length}个):</strong>
</div>`;
html += '<div style="margin-top: 10px;">';
ipInfo.interfaces.forEach((iface, index) => {
html += `<div style="margin-bottom: 15px; padding: 15px; background: rgba(255, 255, 255, 0.05); border-radius: 8px; border-left: 3px solid #51cf66;">
<div style="margin-bottom: 8px;">
<strong>接口 ${index + 1}:</strong>
<span style="color: #51cf66; font-weight: bold;">${iface.interface}</span>
</div>
<div style="margin-bottom: 5px;">
<strong>IP地址:</strong>
<span style="color: #51cf66; font-size: 16px; font-weight: bold; font-family: monospace;">${iface.address}</span>
</div>
<div style="margin-bottom: 5px;">
<strong>子网掩码:</strong>
<span style="color: #ffd43b; font-family: monospace;">${iface.netmask}</span>
</div>`;
if (iface.mac) {
html += `<div>
<strong>MAC地址:</strong>
<span style="color: #ffd43b; font-family: monospace;">${iface.mac}</span>
</div>`;
}
html += `</div>`;
});
html += '</div>';
} else {
html += `<div style="margin-top: 15px; padding: 10px; background: rgba(255, 107, 107, 0.2); border-radius: 5px;">
<strong>网络接口:</strong>
<span style="color: #ff6b6b;">未找到可用的网络接口</span>
</div>`;
}
ipDetailsDiv.innerHTML = html;
ipInfoDiv.style.display = 'block';
}
</script>
</body>
</html>
// 跳过内部(loopback)和非IPv4地址
if (address.internal || address.family !== 'IPv4') {
continue;
}
address.internal**:过滤掉回环接口(127.0.0.1)address.family !== 'IPv4'**:只保留 IPv4 地址,过滤 IPv6ipAddresses.sort((a, b) => {
const priority = (name) => {
const lower = name.toLowerCase();
if (lower.includes('ethernet') || lower.includes('eth')) return1;
if (lower.includes('wifi') || lower.includes('wlan') || lower.includes('airport')) return2;
if (lower.includes('en0') || lower.includes('en1')) return3;
return4;
};
return priority(a.interface) - priority(b.interface);
});
优先级排序规则:
ethernet、eth - 优先级最高wifi、wlan、airport - 优先级次之en0、en1 - 优先级第三const publicIPServices = [
'https://api.ipify.org?format=json',
'https://api.ip.sb/ip',
'https://ifconfig.me/ip'
];
使用多个公共服务,按顺序尝试:
try {
// 获取IP地址逻辑
} catch (error) {
console.error('获取IP地址失败:', error);
return {
hostname: os.hostname(),
interfaces: [],
publicIP: null,
platform: platform,
error: error.message
};
}
// 在渲染进程中调用
const ipInfo = awaitwindow.electronAPI.getIPAddresses();
console.log('主机名:', ipInfo.hostname);
console.log('平台:', ipInfo.platform);
console.log('公网IP:', ipInfo.publicIP);
console.log('网络接口数量:', ipInfo.interfaces.length);
// 遍历所有网络接口
ipInfo.interfaces.forEach((iface, index) => {
console.log(`接口 ${index + 1}:`, iface.interface);
console.log(' IP地址:', iface.address);
console.log(' 子网掩码:', iface.netmask);
console.log(' MAC地址:', iface.mac);
});
{
hostname: "MyComputer",
platform: "darwin",
publicIP: "123.45.67.89", // 可能为null
interfaces: [
{
interface: "en0",
address: "192.168.1.100",
netmask: "255.255.255.0",
mac: "aa:bb:cc:dd:ee:ff"
},
{
interface: "en1",
address: "10.0.0.5",
netmask: "255.255.0.0",
mac: "11:22:33:44:55:66"
}
],
error: null// 如果有错误,这里会有错误信息
}
以太网、WLAN)// 建议:添加缓存机制
let cachedIPInfo = null;
let cacheTime = null;
const CACHE_DURATION = 60000; // 缓存1分钟
asyncfunction getIPAddresses() {
// 如果缓存有效,直接返回
if (cachedIPInfo && cacheTime && Date.now() - cacheTime < CACHE_DURATION) {
return cachedIPInfo;
}
// 获取新的IP信息
const ipInfo = await fetchIPAddresses();
cachedIPInfo = ipInfo;
cacheTime = Date.now();
return ipInfo;
}
A: 可能的原因:
解决方案:
A: 可能的原因:
解决方案:
address.internal 和 address.family !== 'IPv4'address.internal 检查A: 是的,完全兼容。因为:
os 模块,跨平台兼容本文详细介绍了在鸿蒙 PC 平台上使用 Electron 实现 IP 地址获取功能的完整方案。通过使用 Node.js 内置的 os 模块,我们可以轻松获取设备的网络接口信息,包括本地 IP 地址、子网掩码、MAC 地址等。同时,通过 HTTP 请求可以获取设备的公网 IP 地址。
通过本文的实现方案,开发者可以轻松在 Electron 应用中集成 IP 地址获取功能,为用户提供清晰的网络信息展示。
参考资料
[1]
Node.js os 模块文档: https://nodejs.org/api/os.html
[2]
Electron IPC 通信文档: https://www.electronjs.org/docs/latest/api/ipc-main
[3]
Electron Context Bridge 文档: https://www.electronjs.org/docs/latest/api/context-bridge