Skip to content

第6章:Electron 进程间通信(IPC)深入

6.1 IPC 核心模块

6.1.1 ipcMain

ipcMain 是 Electron 主进程中用于处理来自渲染进程消息的模块。它提供了多种方法来监听和处理渲染进程发送的消息。

6.1.1.1 主要方法

  • ipcMain.on(channel, listener):监听渲染进程发送的消息
  • ipcMain.handle(channel, listener):处理渲染进程的请求并返回结果
  • ipcMain.once(channel, listener):只监听一次消息
  • ipcMain.removeListener(channel, listener):移除指定的监听器
  • ipcMain.removeAllListeners(channel):移除指定通道的所有监听器

6.1.2 ipcRenderer

ipcRenderer 是 Electron 渲染进程中用于与主进程通信的模块。它提供了多种方法来发送消息和接收主进程的回复。

6.1.2.1 主要方法

  • ipcRenderer.send(channel, ...args):向主进程发送消息
  • ipcRenderer.invoke(channel, ...args):向主进程发送请求并等待响应
  • ipcRenderer.on(channel, listener):监听主进程发送的消息
  • ipcRenderer.once(channel, listener):只监听一次消息
  • ipcRenderer.removeListener(channel, listener):移除指定的监听器
  • ipcRenderer.removeAllListeners(channel):移除指定通道的所有监听器

6.2 单向通信

6.2.1 渲染进程 → 主进程

场景:渲染进程向主进程发送消息,主进程处理但不需要返回结果。

6.2.1.1 实现方式

渲染进程

javascript
const { ipcRenderer } = require('electron')

// 向主进程发送消息
ipcRenderer.send('message-from-renderer', 'Hello from renderer')

主进程

javascript
const { ipcMain } = require('electron')

// 监听渲染进程发送的消息
ipcMain.on('message-from-renderer', (event, data) => {
  console.log('主进程收到消息:', data)
  // 处理消息
})

6.2.2 主进程 → 渲染进程

场景:主进程向渲染进程发送消息,渲染进程接收并处理。

6.2.2.1 实现方式

主进程

javascript
// 向渲染进程发送消息
win.webContents.send('message-from-main', 'Hello from main')

渲染进程

javascript
const { ipcRenderer } = require('electron')

// 监听主进程发送的消息
ipcRenderer.on('message-from-main', (event, data) => {
  console.log('渲染进程收到消息:', data)
  // 处理消息
})

6.3 双向通信

6.3.1 请求-响应模式

场景:渲染进程向主进程发送请求,主进程处理并返回结果。

6.3.1.1 实现方式

渲染进程

javascript
const { ipcRenderer } = require('electron')

// 向主进程发送请求并等待响应
async function getProcessInfo() {
  try {
    const result = await ipcRenderer.invoke('get-process-info')
    console.log('获取到进程信息:', result)
    return result
  } catch (error) {
    console.error('请求失败:', error)
  }
}

// 调用函数
getProcessInfo()

主进程

javascript
const { ipcMain } = require('electron')

// 处理渲染进程的请求
ipcMain.handle('get-process-info', async (event) => {
  // 执行异步操作
  await new Promise(resolve => setTimeout(resolve, 1000))
  
  // 返回结果
  return {
    pid: process.pid,
    platform: process.platform,
    version: process.version
  }
})

6.4 消息发送与接收的常用方法

6.4.1 send + on 组合

特点:单向通信,适合不需要返回结果的场景。

使用场景

  • 渲染进程通知主进程执行某个操作
  • 主进程向渲染进程发送状态更新

示例

javascript
// 渲染进程
ipcRenderer.send('update-settings', { theme: 'dark' })

// 主进程
ipcMain.on('update-settings', (event, settings) => {
  console.log('更新设置:', settings)
  // 执行更新操作
})

6.4.2 invoke + handle 组合

特点:双向通信,适合需要返回结果的场景。

使用场景

  • 渲染进程请求主进程获取数据
  • 渲染进程请求主进程执行操作并返回结果

示例

javascript
// 渲染进程
async function getFileContent(path) {
  const content = await ipcRenderer.invoke('read-file', path)
  return content
}

// 主进程
const fs = require('fs')

ipcMain.handle('read-file', async (event, path) => {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf8', (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
})

6.4.3 event.sender.send

特点:主进程向发送消息的渲染进程回复消息。

使用场景

  • 主进程处理完渲染进程的消息后,向该渲染进程发送回复

示例

javascript
// 主进程
ipcMain.on('get-data', (event, query) => {
  // 处理查询
  const result = processQuery(query)
  // 向发送消息的渲染进程回复
  event.sender.send('data-response', result)
})

// 渲染进程
ipcRenderer.send('get-data', 'latest-news')
ipcRenderer.on('data-response', (event, data) => {
  console.log('收到数据:', data)
})

6.5 实操案例:IPC 通信实战

6.5.1 场景描述

创建一个具有以下功能的 Electron 应用:

  • 渲染进程向主进程发送文件读取请求
  • 主进程读取文件并返回内容
  • 渲染进程显示文件内容
  • 主进程向渲染进程发送实时状态更新

6.5.2 实现步骤

  1. 修改 main.js
javascript
const { app, BrowserWindow, ipcMain } = require('electron')
const fs = require('fs')
const path = require('path')

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  })

  mainWindow.loadFile('index.html')
  mainWindow.webContents.openDevTools()

  // 处理文件读取请求
  ipcMain.handle('read-file', async (event, filePath) => {
    return new Promise((resolve, reject) => {
      fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      })
    })
  })

  // 处理文件写入请求
  ipcMain.handle('write-file', async (event, { filePath, content }) => {
    return new Promise((resolve, reject) => {
      fs.writeFile(filePath, content, 'utf8', (err) => {
        if (err) {
          reject(err)
        } else {
          resolve('文件写入成功')
        }
      })
    })
  })

  // 定期向渲染进程发送状态更新
  setInterval(() => {
    if (mainWindow) {
      mainWindow.webContents.send('status-update', {
        memory: process.memoryUsage(),
        uptime: process.uptime(),
        timestamp: new Date().toLocaleString()
      })
    }
  }, 2000)
}

app.whenReady().then(() => {
  createWindow()

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

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})
  1. 修改 index.html
html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>IPC 通信示例</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
      padding: 0;
      background-color: #f0f0f0;
    }
    .container {
      max-width: 700px;
      margin: 0 auto;
      background-color: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    h1 {
      color: #333;
      text-align: center;
    }
    .section {
      margin: 20px 0;
      padding: 15px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    h2 {
      color: #555;
      margin-top: 0;
    }
    button {
      padding: 10px 15px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      margin: 5px;
    }
    button:hover {
      background-color: #45a049;
    }
    textarea {
      width: 100%;
      height: 200px;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      resize: vertical;
    }
    #status {
      background-color: #f9f9f9;
      padding: 10px;
      border-radius: 4px;
      font-family: monospace;
      font-size: 12px;
      white-space: pre-wrap;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>IPC 通信示例</h1>
    
    <div class="section">
      <h2>文件操作</h2>
      <button id="read-btn">读取文件</button>
      <button id="write-btn">写入文件</button>
      <textarea id="file-content" placeholder="文件内容将显示在这里"></textarea>
    </div>
    
    <div class="section">
      <h2>系统状态</h2>
      <div id="status">状态更新将显示在这里</div>
    </div>
  </div>
  
  <script>
    const { ipcRenderer } = require('electron')
    const path = require('path')
    
    // 读取文件按钮点击事件
    document.getElementById('read-btn').addEventListener('click', async () => {
      try {
        const testFile = path.join(__dirname, 'test.txt')
        const content = await ipcRenderer.invoke('read-file', testFile)
        document.getElementById('file-content').value = content
      } catch (error) {
        alert('读取文件失败: ' + error.message)
      }
    })
    
    // 写入文件按钮点击事件
    document.getElementById('write-btn').addEventListener('click', async () => {
      try {
        const testFile = path.join(__dirname, 'test.txt')
        const content = document.getElementById('file-content').value
        const result = await ipcRenderer.invoke('write-file', { 
          filePath: testFile, 
          content: content 
        })
        alert(result)
      } catch (error) {
        alert('写入文件失败: ' + error.message)
      }
    })
    
    // 监听主进程发送的状态更新
    ipcRenderer.on('status-update', (event, status) => {
      const statusDiv = document.getElementById('status')
      statusDiv.textContent = `
系统状态更新: ${status.timestamp}
内存使用: ${Math.round(status.memory.heapUsed / 1024 / 1024)} MB
运行时间: ${Math.round(status.uptime)} 秒
`
    })
  </script>
</body>
</html>
  1. 创建测试文件

创建一个 test.txt 文件,内容如下:

Hello Electron!

这是一个测试文件,用于演示 IPC 通信中的文件操作。

你可以修改这个文件的内容,然后点击 "写入文件" 按钮保存更改。

6.5.3 运行效果

  1. 启动应用

    bash
    npm start
  2. 测试功能

    • 点击 "读取文件" 按钮,读取并显示 test.txt 文件的内容
    • 修改文本框中的内容,点击 "写入文件" 按钮保存更改
    • 观察系统状态区域,查看主进程发送的实时状态更新

6.6 新手易错点

6.6.1 IPC 通信权限问题

错误表现

  • 渲染进程无法发送消息到主进程
  • 主进程无法向渲染进程发送消息

解决方案

  • 确保在 webPreferences 中正确配置 nodeIntegrationcontextIsolation
  • 使用预加载脚本(preload.js)进行安全的 IPC 通信
  • 检查事件名称是否一致

6.6.2 消息监听遗漏

错误表现

  • 消息发送但未被处理
  • 响应未被接收

解决方案

  • 确保在正确的进程中注册了监听器
  • 检查监听器是否在消息发送之前注册
  • 使用 console.log 调试消息传递过程
  • 确保使用了正确的通信方法(send/on 或 invoke/handle)

6.6.3 异步操作处理不当

错误表现

  • 异步操作未正确处理
  • 回调地狱或 Promise 链错误

解决方案

  • 使用 async/await 处理异步操作
  • 正确使用 Promise 处理异步逻辑
  • ipcMain.handle 中返回 Promise
  • 处理错误情况,避免未捕获的异常

6.6.4 性能问题

错误表现

  • 频繁的 IPC 通信导致性能下降
  • 消息队列堆积

解决方案

  • 批量处理消息,减少通信次数
  • 使用事件节流(throttle)或防抖(debounce)
  • 避免在渲染进程和主进程之间传递大量数据
  • 考虑使用共享内存或其他方式优化通信

6.7 小结

通过本章的学习,你已经掌握了 Electron 进程间通信的高级用法:

  • IPC 核心模块:ipcMain 和 ipcRenderer 的使用
  • 单向通信:渲染进程和主进程之间的消息传递
  • 双向通信:请求-响应模式的实现
  • 常用通信方法:send/on 和 invoke/handle 的使用场景
  • 实战案例:文件操作和实时状态更新
  • 常见问题排查:权限、监听、异步操作和性能问题

这些知识是 Electron 应用开发的核心,掌握它们可以帮助你创建更加复杂和功能丰富的桌面应用。在接下来的章节中,我们将学习 Electron 的原生功能访问和前端集成。

© 2026 编程马·菜鸟教程 版权所有