进程间通信IPC #
一、IPC 概述 #
1.1 什么是 IPC #
IPC(Inter-Process Communication,进程间通信)是 Electron 中主进程与渲染进程之间交换数据的机制。
text
┌─────────────────┐ ┌─────────────────┐
│ 渲染进程 │ │ 主进程 │
│ │ │ │
│ ipcRenderer │ ◄── IPC 通道 ──► │ ipcMain │
│ │ │ │
└─────────────────┘ └─────────────────┘
1.2 为什么需要 IPC #
| 原因 | 说明 |
|---|---|
| 安全隔离 | 渲染进程不能直接访问 Node.js API |
| 职责分离 | 主进程处理原生功能,渲染进程处理 UI |
| 进程独立 | 主进程和渲染进程运行在独立的环境中 |
1.3 IPC 模块 #
| 模块 | 使用位置 | 说明 |
|---|---|---|
| ipcMain | 主进程 | 接收/发送消息 |
| ipcRenderer | 渲染进程 | 发送/接收消息 |
| contextBridge | 预加载脚本 | 安全暴露 API |
二、基本通信方式 #
2.1 send/on 模式 #
渲染进程发送,主进程接收
javascript
// 渲染进程
const { ipcRenderer } = require('electron');
// 发送消息
ipcRenderer.send('message-channel', {
type: 'greeting',
content: 'Hello from renderer'
});
javascript
// 主进程
const { ipcMain } = require('electron');
// 接收消息
ipcMain.on('message-channel', (event, data) => {
console.log('收到消息:', data);
// 回复消息
event.reply('message-reply', {
status: 'success',
message: 'Message received'
});
});
javascript
// 渲染进程接收回复
ipcRenderer.on('message-reply', (event, data) => {
console.log('收到回复:', data);
});
2.2 invoke/handle 模式(推荐) #
异步请求-响应模式
javascript
// 主进程:注册处理器
const { ipcMain } = require('electron');
const fs = require('fs').promises;
ipcMain.handle('read-file', async (event, filePath) => {
try {
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
javascript
// 渲染进程:调用处理器
const { ipcRenderer } = require('electron');
async function readFile(filePath) {
const result = await ipcRenderer.invoke('read-file', filePath);
if (result.success) {
console.log('文件内容:', result.content);
} else {
console.error('读取失败:', result.error);
}
}
2.3 两种模式对比 #
| 特性 | send/on | invoke/handle |
|---|---|---|
| 返回值 | 需要手动回复 | 自动返回 Promise |
| 错误处理 | 需要手动处理 | 支持 try/catch |
| 代码简洁 | 较复杂 | 简洁 |
| 推荐程度 | 一般 | 推荐 |
三、预加载脚本桥接 #
3.1 为什么需要预加载脚本 #
text
┌─────────────────────────────────────────────────────┐
│ 安全架构 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 渲染进程 │ │ 主进程 │ │
│ │ │ │ │ │
│ │ 不能直接 │ │ Node.js │ │
│ │ 访问 Node │ │ API │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ └───┤ 预加载脚本 ├─────┘ │
│ │ (安全桥梁) │ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
3.2 使用 contextBridge #
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// 定义允许的频道
const validChannels = {
invoke: ['read-file', 'write-file', 'get-app-info'],
send: ['log-message', 'notify'],
receive: ['file-changed', 'update-available']
};
// 安全地暴露 API
contextBridge.exposeInMainWorld('electronAPI', {
// invoke 方法
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
getAppInfo: () => ipcRenderer.invoke('get-app-info'),
// send 方法
logMessage: (message) => ipcRenderer.send('log-message', message),
notify: (title, body) => ipcRenderer.send('notify', { title, body }),
// receive 方法
onFileChanged: (callback) => {
ipcRenderer.on('file-changed', (event, data) => callback(data));
},
onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (event, info) => callback(info));
},
// 移除监听器
removeFileChangedListener: (callback) => {
ipcRenderer.removeListener('file-changed', callback);
}
});
3.3 渲染进程使用 #
javascript
// renderer.js
// 使用暴露的 API
// 读取文件
async function loadFile() {
const result = await window.electronAPI.readFile('/path/to/file.txt');
console.log(result);
}
// 写入文件
async function saveFile(content) {
const result = await window.electronAPI.writeFile('/path/to/file.txt', content);
console.log(result);
}
// 监听文件变化
window.electronAPI.onFileChanged((data) => {
console.log('文件已变化:', data);
});
// 发送通知
window.electronAPI.notify('提示', '操作已完成');
四、高级通信模式 #
4.1 请求-响应封装 #
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// 通用请求方法
request: (channel, data) => {
return ipcRenderer.invoke(channel, data);
}
});
javascript
// renderer.js
class APIClient {
async get(path) {
return window.electronAPI.request('api:get', { path });
}
async post(path, data) {
return window.electronAPI.request('api:post', { path, data });
}
async put(path, data) {
return window.electronAPI.request('api:put', { path, data });
}
async delete(path) {
return window.electronAPI.request('api:delete', { path });
}
}
const api = new APIClient();
const users = await api.get('/users');
4.2 事件订阅模式 #
javascript
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
// 订阅事件
subscribe: (channel, callback) => {
const subscription = (event, data) => callback(data);
ipcRenderer.on(channel, subscription);
// 返回取消订阅函数
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
});
javascript
// renderer.js
// 订阅事件
const unsubscribe = window.electronAPI.subscribe('data-updated', (data) => {
console.log('数据更新:', data);
updateUI(data);
});
// 取消订阅
unsubscribe();
4.3 流式数据传输 #
javascript
// 主进程:流式发送数据
ipcMain.on('start-stream', (event, streamId) => {
const stream = getLargeDataStream();
stream.on('data', (chunk) => {
event.reply('stream-data', { streamId, chunk });
});
stream.on('end', () => {
event.reply('stream-end', { streamId });
});
});
javascript
// 渲染进程:接收流式数据
function receiveStream(streamId, onData, onEnd) {
ipcRenderer.on('stream-data', (event, data) => {
if (data.streamId === streamId) {
onData(data.chunk);
}
});
ipcRenderer.on('stream-end', (event, data) => {
if (data.streamId === streamId) {
onEnd();
}
});
ipcRenderer.send('start-stream', streamId);
}
// 使用
receiveStream(
'stream-1',
(chunk) => console.log('收到数据块:', chunk),
() => console.log('流结束')
);
五、IPC 频道管理 #
5.1 频道常量定义 #
javascript
// electron/ipc/channels.js
module.exports = {
FILE: {
READ: 'file:read',
WRITE: 'file:write',
DELETE: 'file:delete',
EXISTS: 'file:exists'
},
APP: {
VERSION: 'app:version',
PATH: 'app:path',
QUIT: 'app:quit',
RELOAD: 'app:reload'
},
SYSTEM: {
INFO: 'system:info',
NOTIFICATION: 'system:notification',
CLIPBOARD: 'system:clipboard'
},
WINDOW: {
MINIMIZE: 'window:minimize',
MAXIMIZE: 'window:maximize',
CLOSE: 'window:close'
}
};
5.2 主进程处理器注册 #
javascript
// electron/ipc/handlers/file.js
const { ipcMain } = require('electron');
const fs = require('fs').promises;
const CHANNELS = require('../channels');
function registerFileHandlers() {
ipcMain.handle(CHANNELS.FILE.READ, async (event, filePath) => {
const content = await fs.readFile(filePath, 'utf-8');
return content;
});
ipcMain.handle(CHANNELS.FILE.WRITE, async (event, filePath, content) => {
await fs.writeFile(filePath, content);
return true;
});
ipcMain.handle(CHANNELS.FILE.DELETE, async (event, filePath) => {
await fs.unlink(filePath);
return true;
});
ipcMain.handle(CHANNELS.FILE.EXISTS, async (event, filePath) => {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
});
}
module.exports = { registerFileHandlers };
javascript
// electron/ipc/index.js
const { registerFileHandlers } = require('./handlers/file');
const { registerAppHandlers } = require('./handlers/app');
const { registerSystemHandlers } = require('./handlers/system');
function setupIpc() {
registerFileHandlers();
registerAppHandlers();
registerSystemHandlers();
}
module.exports = { setupIpc };
5.3 预加载脚本使用频道常量 #
javascript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const CHANNELS = require('./ipc/channels');
contextBridge.exposeInMainWorld('electronAPI', {
file: {
read: (path) => ipcRenderer.invoke(CHANNELS.FILE.READ, path),
write: (path, content) => ipcRenderer.invoke(CHANNELS.FILE.WRITE, path, content),
delete: (path) => ipcRenderer.invoke(CHANNELS.FILE.DELETE, path),
exists: (path) => ipcRenderer.invoke(CHANNELS.FILE.EXISTS, path)
},
app: {
getVersion: () => ipcRenderer.invoke(CHANNELS.APP.VERSION),
getPath: () => ipcRenderer.invoke(CHANNELS.APP.PATH),
quit: () => ipcRenderer.send(CHANNELS.APP.QUIT)
}
});
六、错误处理 #
6.1 主进程错误处理 #
javascript
// 主进程处理器中的错误处理
ipcMain.handle('dangerous-operation', async (event, data) => {
try {
const result = await performOperation(data);
return { success: true, data: result };
} catch (error) {
console.error('操作失败:', error);
return {
success: false,
error: {
message: error.message,
code: error.code
}
};
}
});
6.2 渲染进程错误处理 #
javascript
// 渲染进程调用时的错误处理
async function performOperation(data) {
try {
const result = await window.electronAPI.dangerousOperation(data);
if (!result.success) {
throw new Error(result.error.message);
}
return result.data;
} catch (error) {
console.error('操作失败:', error);
showErrorMessage(error.message);
throw error;
}
}
6.3 超时处理 #
javascript
// 带超时的 IPC 调用
function invokeWithTimeout(channel, data, timeout = 5000) {
return Promise.race([
ipcRenderer.invoke(channel, data),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('IPC 调用超时'));
}, timeout);
})
]);
}
// 使用
try {
const result = await invokeWithTimeout('slow-operation', data, 10000);
console.log(result);
} catch (error) {
console.error('操作超时或失败:', error);
}
七、安全最佳实践 #
7.1 频道白名单 #
javascript
// preload.js
const validChannels = {
invoke: ['file:read', 'file:write', 'app:version'],
send: ['log:message'],
receive: ['file:changed', 'update:available']
};
contextBridge.exposeInMainWorld('electronAPI', {
invoke: (channel, data) => {
if (validChannels.invoke.includes(channel)) {
return ipcRenderer.invoke(channel, data);
}
throw new Error(`无效的频道: ${channel}`);
},
send: (channel, data) => {
if (validChannels.send.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
on: (channel, callback) => {
if (validChannels.receive.includes(channel)) {
ipcRenderer.on(channel, (event, data) => callback(data));
}
}
});
7.2 数据验证 #
javascript
// 主进程:验证传入数据
const Joi = require('joi');
const fileWriteSchema = Joi.object({
path: Joi.string().required(),
content: Joi.string().required()
});
ipcMain.handle('file:write', async (event, data) => {
const { error, value } = fileWriteSchema.validate(data);
if (error) {
return { success: false, error: error.message };
}
await fs.writeFile(value.path, value.content);
return { success: true };
});
7.3 权限控制 #
javascript
// 主进程:基于权限的 IPC 处理
const permissions = new Map();
function checkPermission(event, action) {
const webContentsId = event.sender.id;
const allowedActions = permissions.get(webContentsId) || [];
return allowedActions.includes(action);
}
ipcMain.handle('file:read', async (event, filePath) => {
if (!checkPermission(event, 'file:read')) {
throw new Error('没有权限执行此操作');
}
return await fs.readFile(filePath, 'utf-8');
});
八、调试技巧 #
8.1 IPC 日志 #
javascript
// 主进程:记录所有 IPC 通信
ipcMain.on('*', (event, ...args) => {
console.log(`[IPC] 收到消息: ${event.channel}`, args);
});
// 或使用装饰器模式
function logIpcHandler(channel, handler) {
ipcMain.handle(channel, async (event, ...args) => {
console.log(`[IPC] ${channel} 被调用`, args);
const start = Date.now();
try {
const result = await handler(event, ...args);
console.log(`[IPC] ${channel} 完成 (${Date.now() - start}ms)`);
return result;
} catch (error) {
console.error(`[IPC] ${channel} 失败:`, error);
throw error;
}
});
}
8.2 DevTools 查看 IPC #
javascript
// 在渲染进程中监听所有 IPC 消息
const originalSend = ipcRenderer.send;
ipcRenderer.send = (channel, ...args) => {
console.log(`[IPC Send] ${channel}`, args);
return originalSend.call(ipcRenderer, channel, ...args);
};
const originalInvoke = ipcRenderer.invoke;
ipcRenderer.invoke = async (channel, ...args) => {
console.log(`[IPC Invoke] ${channel}`, args);
const result = await originalInvoke.call(ipcRenderer, channel, ...args);
console.log(`[IPC Result] ${channel}`, result);
return result;
};
九、总结 #
9.1 核心要点 #
| 要点 | 说明 |
|---|---|
| invoke/handle | 推荐的异步通信方式 |
| contextBridge | 安全暴露 API 的桥梁 |
| 频道管理 | 使用常量管理频道名称 |
| 错误处理 | 完善的错误处理机制 |
| 安全验证 | 频道白名单和数据验证 |
9.2 下一步 #
现在你已经掌握了 IPC 通信,接下来让我们学习 BrowserWindow窗口,深入了解窗口的创建和管理!
最后更新:2026-03-28