进程间通信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