Electron 基于 Chromium 和 Node.js,既能利用前端技术栈实现美观的 UI,又能通过 Node.js 调用系统底层网络 API。本文以网络性能监控工具为场景,开发一款支持 TCP 端口检测、UDP 连通性测试、网络延迟实时监控的桌面应用,覆盖 Electron 核心能力:主进程异步任务处理、渲染进程数据可视化、跨平台网络 API 调用、系统通知集成。

一、环境准备

1. 初始化项目

mkdir electron-network-monitor
cd electron-network-monitor
npm init -y

# 安装核心依赖
npm install electron@28.0.0 --save-dev
npm install ping.js net socket.io-client chart.js --save

2. 配置 package.json

{
  "name": "network-monitor",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "package": "electron-packager . NetworkMonitor --platform=win32,darwin,linux --arch=x64 --out=dist"
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-packager": "^17.1.2"
  },
  "dependencies": {
    "chart.js": "^4.4.8",
    "net": "^1.0.2",
    "ping.js": "^0.3.0",
    "socket.io-client": "^4.7.5"
  }
}

二、核心代码实现

1. 主进程(main.js)

负责网络检测、系统通知、窗口管理,通过 IPC 与渲染进程通信:

const { app, BrowserWindow, ipcMain, Notification, dialog } = require('electron');
const path = require('path');
const net = require('net');
const Ping = require('ping.js');
const dgram = require('dgram');

let mainWindow;
const ping = new Ping();

// 创建主窗口
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1000,
    height: 700,
    minWidth: 800,
    minHeight: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      enableRemoteModule: true
    },
    title: 'Electron 网络性能监控工具'
  });

  mainWindow.loadFile('index.html');
  // 开发阶段打开开发者工具
  // mainWindow.webContents.openDevTools();

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

// TCP 端口检测
function checkTcpPort(host, port, timeout = 3000) {
  return new Promise((resolve) => {
    const socket = new net.Socket();
    const timer = setTimeout(() => {
      socket.destroy();
      resolve({ status: 'closed', host, port, time: timeout });
    }, timeout);

    socket.connect(port, host, () => {
      clearTimeout(timer);
      socket.destroy();
      resolve({ status: 'open', host, port, time: Date.now() - timer._idleStart });
    });

    socket.on('error', () => {
      clearTimeout(timer);
      resolve({ status: 'error', host, port, time: timeout });
    });
  });
}

// UDP 连通性测试
function checkUdpPort(host, port, timeout = 3000) {
  return new Promise((resolve) => {
    const client = dgram.createSocket('udp4');
    const message = Buffer.from('network-monitor-test');
    const timer = setTimeout(() => {
      client.close();
      resolve({ status: 'timeout', host, port, time: timeout });
    }, timeout);

    client.send(message, 0, message.length, port, host, (err) => {
      if (err) {
        clearTimeout(timer);
        client.close();
        resolve({ status: 'error', host, port, time: timeout });
      }
    });

    client.on('message', () => {
      clearTimeout(timer);
      client.close();
      resolve({ status: 'open', host, port, time: Date.now() - timer._idleStart });
    });
  });
}

// 网络延迟测试(Ping)
function testPing(host, count = 5) {
  return new Promise((resolve) => {
    const results = [];
    let completed = 0;

    for (let i = 0; i < count; i++) {
      ping.ping(host, (err, data) => {
        completed++;
        if (err) {
          results.push({ status: 'fail', time: null });
        } else {
          results.push({ status: 'success', time: data.time });
        }

        if (completed === count) {
          const avgTime = results
            .filter(r => r.status === 'success')
            .reduce((sum, r) => sum + r.time, 0) / Math.max(1, results.filter(r => r.status === 'success').length);
          resolve({
            host,
            count,
            results,
            avgTime: isNaN(avgTime) ? 0 : avgTime.toFixed(2)
          });
        }
      });
    }
  });
}

// 显示系统通知
function showNotification(title, body) {
  if (Notification.isSupported()) {
    new Notification({ title, body, silent: false }).show();
  } else {
    dialog.showMessageBox(mainWindow, { type: 'info', title, message: body });
  }
}

// 注册 IPC 事件
function registerIpcHandlers() {
  // TCP 端口检测请求
  ipcMain.on('network:check-tcp', async (event, { host, port, timeout }) => {
    const result = await checkTcpPort(host, port, timeout);
    event.reply('network:tcp-result', result);
    if (result.status === 'closed') {
      showNotification('TCP 端口检测', `${host}:${port} 端口关闭`);
    }
  });

  // UDP 端口检测请求
  ipcMain.on('network:check-udp', async (event, { host, port, timeout }) => {
    const result = await checkUdpPort(host, port, timeout);
    event.reply('network:udp-result', result);
  });

  // Ping 延迟测试请求
  ipcMain.on('network:test-ping', async (event, { host, count }) => {
    const result = await testPing(host, count);
    event.reply('network:ping-result', result);
    if (result.avgTime > 100) {
      showNotification('网络延迟警告', `${host} 平均延迟 ${result.avgTime}ms,网络状况不佳`);
    }
  });
}

// 应用生命周期管理
app.whenReady().then(() => {
  createWindow();
  registerIpcHandlers();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

2. 渲染进程(index.html)

负责 UI 交互、数据可视化、用户输入处理:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>网络性能监控工具</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: Arial, sans-serif; padding: 20px; background-color: #f5f5f5; }
    .container { max-width: 1000px; margin: 0 auto; }
    .tab-container { display: flex; margin-bottom: 20px; border-bottom: 1px solid #ddd; }
    .tab-btn { padding: 10px 20px; border: none; background: transparent; cursor: pointer; font-size: 16px; }
    .tab-btn.active { border-bottom: 2px solid #2196F3; color: #2196F3; }
    .tab-content { display: none; padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
    .tab-content.active { display: block; }
    .form-group { margin-bottom: 15px; }
    label { display: block; margin-bottom: 5px; font-weight: bold; }
    input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
    .btn { padding: 10px 20px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; }
    .btn:hover { background: #1976D2; }
    .result { margin-top: 20px; padding: 15px; background: #f8f8f8; border-radius: 4px; white-space: pre-wrap; }
    canvas { width: 100%; height: 300px; margin-top: 20px; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Electron 网络性能监控工具</h1>
    <div class="tab-container">
      <button class="tab-btn active" onclick="switchTab('tcp')">TCP 端口检测</button>
      <button class="tab-btn" onclick="switchTab('udp')">UDP 端口检测</button>
      <button class="tab-btn" onclick="switchTab('ping')">网络延迟测试</button>
    </div>

    <!-- TCP 检测标签页 -->
    <div id="tcp" class="tab-content active">
      <div class="form-group">
        <label>目标主机</label>
        <input type="text" id="tcpHost" placeholder="例如:127.0.0.1 或 www.baidu.com" value="www.baidu.com">
      </div>
      <div class="form-group">
        <label>目标端口</label>
        <input type="number" id="tcpPort" placeholder="例如:80、443" value="80">
      </div>
      <div class="form-group">
        <label>超时时间(ms)</label>
        <input type="number" id="tcpTimeout" value="3000">
      </div>
      <button class="btn" onclick="checkTcp()">开始检测</button>
      <div id="tcpResult" class="result"></div>
    </div>

    <!-- UDP 检测标签页 -->
    <div id="udp" class="tab-content">
      <div class="form-group">
        <label>目标主机</label>
        <input type="text" id="udpHost" placeholder="例如:127.0.0.1" value="127.0.0.1">
      </div>
      <div class="form-group">
        <label>目标端口</label>
        <input type="number" id="udpPort" placeholder="例如:8080" value="8080">
      </div>
      <div class="form-group">
        <label>超时时间(ms)</label>
        <input type="number" id="udpTimeout" value="3000">
      </div>
      <button class="btn" onclick="checkUdp()">开始检测</button>
      <div id="udpResult" class="result"></div>
    </div>

    <!-- Ping 延迟测试标签页 -->
    <div id="ping" class="tab-content">
      <div class="form-group">
        <label>目标主机</label>
        <input type="text" id="pingHost" placeholder="例如:www.baidu.com" value="www.baidu.com">
      </div>
      <div class="form-group">
        <label>测试次数</label>
        <input type="number" id="pingCount" value="5" min="1" max="10">
      </div>
      <button class="btn" onclick="testPing()">开始测试</button>
      <div id="pingResult" class="result"></div>
      <canvas id="pingChart"></canvas>
    </div>
  </div>

  <script>
    const { ipcRenderer } = require('electron');
    const Chart = require('chart.js');

    let pingChart = null;

    // 切换标签页
    function switchTab(tabId) {
      document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
      document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
      document.querySelector(`.tab-btn[onclick="switchTab('${tabId}')"]`).classList.add('active');
      document.getElementById(tabId).classList.add('active');
    }

    // TCP 端口检测
    function checkTcp() {
      const host = document.getElementById('tcpHost').value;
      const port = parseInt(document.getElementById('tcpPort').value);
      const timeout = parseInt(document.getElementById('tcpTimeout').value);
      const resultEl = document.getElementById('tcpResult');

      resultEl.textContent = '检测中...';
      ipcRenderer.send('network:check-tcp', { host, port, timeout });
      
      ipcRenderer.once('network:tcp-result', (event, result) => {
        resultEl.textContent = JSON.stringify(result, null, 2);
      });
    }

    // UDP 端口检测
    function checkUdp() {
      const host = document.getElementById('udpHost').value;
      const port = parseInt(document.getElementById('udpPort').value);
      const timeout = parseInt(document.getElementById('udpTimeout').value);
      const resultEl = document.getElementById('udpResult');

      resultEl.textContent = '检测中...';
      ipcRenderer.send('network:check-udp', { host, port, timeout });
      
      ipcRenderer.once('network:udp-result', (event, result) => {
        resultEl.textContent = JSON.stringify(result, null, 2);
      });
    }

    // Ping 延迟测试
    function testPing() {
      const host = document.getElementById('pingHost').value;
      const count = parseInt(document.getElementById('pingCount').value);
      const resultEl = document.getElementById('pingResult');

      resultEl.textContent = '测试中...';
      ipcRenderer.send('network:test-ping', { host, count });
      
      ipcRenderer.once('network:ping-result', (event, result) => {
        resultEl.textContent = JSON.stringify(result, null, 2);
        renderPingChart(result.results);
      });
    }

    // 渲染 Ping 延迟图表
    function renderPingChart(results) {
      const ctx = document.getElementById('pingChart').getContext('2d');
      const labels = results.map((_, index) => `第 ${index+1} 次`);
      const data = results.map(r => r.time || 0);

      if (pingChart) pingChart.destroy();
      
      pingChart = new Chart(ctx, {
        type: 'line',
        data: {
          labels: labels,
          datasets: [{
            label: '延迟 (ms)',
            data: data,
            borderColor: '#2196F3',
            backgroundColor: 'rgba(33, 150, 243, 0.1)',
            borderWidth: 2,
            tension: 0.3,
            fill: true
          }]
        },
        options: {
          responsive: true,
          scales: {
            y: {
              beginAtZero: true,
              title: { display: true, text: '延迟 (ms)' }
            }
          }
        }
      });
    }
  </script>
</body>
</html>

三、核心功能解析

1. 网络检测核心实现

  • TCP 端口检测:利用 Node.js net 模块创建 Socket 连接,通过超时机制判断端口状态(开启 / 关闭 / 异常);
  • UDP 连通性测试:通过 dgram 模块发送测试数据包,监听是否收到响应来判断端口可用性;
  • Ping 延迟测试:基于 ping.js 库实现多轮 ICMP 测试,计算平均延迟,支持自定义测试次数。

2. 主进程与渲染进程通信

  • 异步任务处理:网络检测属于耗时操作,全部放在主进程执行,避免阻塞渲染进程的 UI 渲染;
  • IPC 双向通信
    • 渲染进程通过 ipcRenderer.send 发送检测请求;
    • 主进程通过 event.reply 返回检测结果;
    • 使用 once 监听结果,防止重复绑定事件。

3. 数据可视化与用户交互

  • Chart.js 图表渲染:将 Ping 测试的每轮延迟数据绘制成折线图,直观展示网络波动情况;
  • 标签页切换:通过原生 JS 实现标签页功能,分类展示不同检测模块;
  • 实时状态反馈:检测过程中显示「检测中...」,完成后展示格式化的 JSON 结果。

4. 系统通知集成

  • 跨平台通知:利用 Electron Notification 模块实现系统级通知,TCP 端口关闭或网络延迟过高时自动推送警告;
  • 降级处理:若系统不支持通知,则通过 dialog.showMessageBox 弹出提示框。

四、运行与打包

1. 启动应用

npm start

启动后可进行三项操作:

  1. TCP 端口检测:输入主机和端口,检测目标端口是否开放(默认检测百度 80 端口);
  2. UDP 端口检测:测试 UDP 端口连通性(默认检测本地 8080 端口);
  3. 网络延迟测试:对目标主机执行多轮 Ping 测试,生成延迟报告和折线图。

2. 打包应用

# 安装打包工具
npm install electron-packager --save-dev

# 打包全平台
npm run package

# 打包指定平台
electron-packager . NetworkMonitor --platform=win32 --arch=x64 --out=dist

五、进阶优化方向

1. 功能扩展

  • 批量检测:支持导入 IP 端口列表,批量执行检测并生成报告;
  • 持续监控:添加定时任务,周期性检测网络状态,记录历史数据;
  • 路由追踪:集成 traceroute 功能,显示数据包传输路径;
  • 带宽测试:通过上传下载测试文件,计算网络带宽。

2. 性能优化

  • 连接池复用:TCP 检测时复用 Socket 连接,减少重复创建连接的开销;
  • 结果缓存:缓存近期检测结果,避免重复检测相同主机;
  • 进度条展示:为批量检测添加进度条,提升用户体验。

3. 安全与兼容性

  • 输入验证:严格校验用户输入的主机和端口,防止恶意输入;
  • 权限适配:Linux 平台下 Ping 可能需要 root 权限,添加权限检测和提示;
  • 跨平台兼容:处理不同系统的网络 API 差异,例如 Windows 的 ICMP 限制。

六、总结

本文以网络性能监控为场景,展示了 Electron 开发跨平台桌面应用的完整流程,核心亮点包括:

  1. Node.js 网络 API 调用:直接使用 net dgram 模块实现底层网络操作;
  2. 主渲染进程分工:耗时任务主进程处理,UI 渲染渲染进程负责,保证应用流畅性;
  3. 数据可视化:结合 Chart.js 实现检测结果的图表化展示;
  4. 系统级能力集成:利用 Electron 原生 API 实现通知推送、对话框等功能。

该工具可作为网络运维人员的辅助工具,也可作为 Electron 网络开发的入门案例,扩展后可应用于企业内网监控、服务器状态巡检等场景。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐