一、fork:专用 Node.js 进程创建
核心功能
创建一个新的 Node.js 子进程,自带进程间通信(IPC)通道
ini
const { fork } = require('child_process');
const child = fork(path, [args], [options]);
参数解析
参数 | 类型 | 说明 |
---|---|---|
path | string | 要运行的 JavaScript 文件路径 |
args | Array | 字符串参数列表 |
options | Object | 配置选项 |
关键配置项
arduino
{
execPath: '/usr/bin/node', // 指定 Node.js 解释器
execArgv: ['--inspect'], // 传递给 Node.js 的选项
silent: true, // 分离 stdio 不与父进程共享
stdio: ['ipc', 'pipe', 'pipe'] // IPC 通道配置
}
通信机制
父进程 -> 子进程:
go
child.send({ type: 'COMMAND', data: payload });
// 子进程接收:
process.on('message', (msg) => {
if (msg.type === 'COMMAND') {
// 处理命令
}
});
子进程 -> 父进程:
javascript
process.send({ status: 'READY', pid: process.pid });
// 父进程接收:
child.on('message', (msg) => {
console.log(`Received from child: ${msg.status}`);
});
经典用例
javascript
// 父进程
const apiWorker = fork('./api-service.js', ['production'], {
execPath: '/usr/local/bin/node',
silent: true
});
apiWorker.on('message', (msg) => {
if (msg === 'SERVER_READY') {
console.log('API service started');
startWebServer();
}
});
apiWorker.stdout.on('data', (data) => {
console.log(`[API] ${data}`);
});
arduino
// api-service.js(子进程)
const express = require('express');
const app = express();
// ...配置路由...
const server = app.listen(3000, () => {
console.log('API service running on port 3000');
// 通知父进程
if (process.send) {
process.send('SERVER_READY');
}
});
二、exec:Shell 命令执行专家
核心功能
执行 shell 命令 并捕获输出结果
bash
const { exec } = require('child_process');
exec(command, [options], [callback]);
参数解析
参数 | 类型 | 说明 |
---|---|---|
command | string | 要执行的 shell 命令 |
options | Object | 进程执行选项 |
callback | Function | 带(error, stdout, stderr) |
安全实践
javascript
// 危险!存在命令注入风险
exec(`ls ${userInput}`);
// 安全方案(使用 execFile)
execFile('ls', [sanitize(userInput)], (err, stdout) => {
// 处理结果
});
配置选项详解
arduino
{
cwd: '/project', // 工作目录
env: { NODE_ENV: 'prod' }, // 环境变量
timeout: 5000, // 超时时间(ms)
maxBuffer: 1024 * 1024, // 输出缓冲区大小(1MB)
encoding: 'utf8', // 输出编码
shell: '/bin/bash' // 指定 shell
}
完整示例
lua
const { exec } = require('child_process');
exec('git pull origin master', {
cwd: '/var/www/project',
timeout: 10000
}, (error, stdout, stderr) => {
if (error) {
console.error(`部署失败: ${error.message}`);
return;
}
console.log('Git 更新:');
console.log(stdout);
if (stderr) {
console.warn('警告信息:');
console.warn(stderr);
}
restartServices();
});
三、fork vs exec 比较
特性 | fork | exec |
---|---|---|
进程类型 | 新的 Node.js 实例 | 任意系统进程 |
通信机制 | 支持 IPC 通信 | 无内置通信机制 |
性能开销 | 较高 (需加载新 Node 实例) | 较低 |
输出处理 | 通过 stdio 流处理 | 缓冲区返回 |
适用场景 | Node 服务集群 | 执行系统命令/工具 |
执行控制 | 细粒度进程控制 | 命令级别控制 |
资源回收 | 需手动管理 | 自动回收 |
四、高级应用模式
1. 进程池管理
ini
const { fork } = require('child_process');
const os = require('os');
class ProcessPool {
constructor(scriptPath, size = os.cpus().length) {
this.pool = [];
this.taskQueue = [];
// 初始化工作进程
for (let i = 0; i < size; i++) {
const worker = fork(scriptPath);
worker.on('message', (result) => {
this._returnWorker(worker, result);
});
this.pool.push(worker);
}
}
execute(taskData) {
return new Promise((resolve) => {
if (this.pool.length > 0) {
const worker = this.pool.pop();
worker.send(taskData);
worker.resolve = resolve;
} else {
this.taskQueue.push({ taskData, resolve });
}
});
}
_returnWorker(worker, result) {
worker.resolve(result);
if (this.taskQueue.length > 0) {
const { taskData, resolve } = this.taskQueue.shift();
worker.resolve = resolve;
worker.send(taskData);
} else {
this.pool.push(worker);
}
}
}
// 使用示例
const pool = new ProcessPool('./task-processor.js', 4);
pool.execute({ data: 'process-this' })
.then(result => console.log(result));
2. 超时控制增强
javascript
function executeWithTimeout(command, timeout = 3000) {
return new Promise((resolve, reject) => {
const child = exec(command);
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
child.kill('SIGTERM');
reject(new Error(`Command timeout after ${timeout}ms`));
}, timeout);
child.on('exit', (code) => {
clearTimeout(timer);
if (!timedOut) {
code === 0
? resolve()
: reject(new Error(`Exited with code ${code}`));
}
});
});
}
// 使用示例
executeWithTimeout('npm run build', 10000)
.catch(err => console.error('构建失败:', err));
五、错误处理最佳实践
1. 错误分级处理
javascript
child.on('error', (err) => {
// 进程创建失败(如权限问题)
console.error('FATAL:', err);
});
child.on('exit', (code, signal) => {
if (code !== 0) {
// 异常退出处理
console.error(`Process crashed with code ${code}`);
}
});
// exec 特例错误代码
exec('invalid-command', (err, stdout, stderr) => {
if (err) {
console.error('执行错误:');
console.error(`- 代码: ${err.code}`);
console.error(`- 信号: ${err.signal}`);
console.error(`- 堆栈: ${err.stack}`);
}
});
2. 跨进程错误传递
javascript
// 子进程 (child.js)
try {
// ...危险操作...
} catch (err) {
process.send({
type: 'ERROR',
message: err.message,
stack: err.stack
});
process.exit(1);
}
// 父进程
child.on('message', (msg) => {
if (msg.type === 'ERROR') {
console.error(`子进程错误: ${msg.message}`);
console.error(msg.stack);
}
});
六、性能优化技巧
- 共享内存转移 (V8.5+)
ini
const largeBuffer = Buffer.alloc(1024 * 1024); // 1MB
// 传统方式:复制数据
child.send(largeBuffer);
// 高性能方式:共享内存
child.send(largeBuffer, [largeBuffer.buffer]);
- 连接复用
javascript
// 建立长期存活的进程连接
const persistentChild = fork('daemon.js');
// 使用对象池复用连接
function request(data) {
return new Promise(resolve => {
persistentChild.send(data, resolve);
});
}
- 流式数据处理
c
// 子进程
process.stdin.pipe(transformStream).pipe(process.stdout);
// 父进程
const { spawn } = require('child_process');
const child = spawn('processor');
fs.createReadStream('input.txt')
.pipe(child.stdin);
child.stdout
.pipe(fs.createWriteStream('output.txt'));
七、安全加固指南
- 沙盒隔离
javascript
const { fork } = require('child_process');
const vm = require('vm');
const sandbox = {
console: console,
require: name => {
if (name !== 'safe-module') {
throw new Error('Module not allowed');
}
return require(name);
}
};
const script = new vm.Script('require("safe-module").process()');
const context = vm.createContext(sandbox);
fork('handler.js', {
execArgv: ['-r', 'sandbox-module'],
env: { ...process.env, SANDBOX_CONTEXT: JSON.stringify(context) }
});
- 权限限制
php
const { exec } = require('child_process');
exec('sensitive-command', {
// Linux 用户隔离
uid: 1001,
gid: 1001,
// Windows 权限
windowsVerbatimArguments: true
});
总结
方法 | 最佳适用场景 | 关键注意事项 |
---|---|---|
fork | Node.js 微服务、CPU 密集型任务、进程间通信 | 注意内存消耗,使用连接池管理 |
exec | Shell 命令执行、外部工具调用、简单操作 | 严格限制 maxBuffer,防范命令注入攻击 |
实际选择原则:
-
在 Node.js 进程间需要复杂通信时使用 fork
-
在调用系统命令或外部工具时使用 exec
-
在需要流式处理大量数据时考虑 spawn
-
在需要安全执行命令和解析参数时使用 execFile
通过合理使用这些 API,可以构建高性能、可扩展的 Node.js 应用,同时确保系统稳定和安全。