child_process模块
child_process
是 Node.js 的核心模块之一,用于创建和管理子进程。它提供了多种方式执行外部命令或脚本,并支持进程间通信。通过该模块,Node.js 可以调用系统命令、运行其他语言编写的程序,甚至实现多进程并行处理任务。
子进程的创建方式
Node.js 提供了四种主要的子进程创建方法,每种方法适用于不同的场景:
- exec() - 执行 shell 命令,适合短时间运行的小命令
- execFile() - 直接执行可执行文件,不通过 shell 解析
- spawn() - 底层方法,适合长时间运行的进程,支持流式数据
- fork() - spawn 的特殊形式,专门用于创建新的 Node.js 进程
exec() 方法
exec()
是最简单的子进程创建方式,它会创建一个 shell 来执行命令,并将结果缓冲在内存中。当命令执行完成后,通过回调函数返回结果。
const { exec } = require('child_process');
exec('ls -lh', (error, stdout, stderr) => {
if (error) {
console.error(`执行错误: ${error}`);
return;
}
console.log(`标准输出:\n${stdout}`);
if (stderr) {
console.error(`标准错误输出:\n${stderr}`);
}
});
这种方法适合执行简单的 shell 命令,但不适合处理大量数据输出,因为所有输出都会缓存在内存中。
execFile() 方法
execFile()
与 exec()
类似,但不通过 shell 执行命令,而是直接运行可执行文件。这提高了安全性,避免了 shell 注入攻击。
const { execFile } = require('child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
throw error;
}
console.log(stdout);
});
spawn() 方法
spawn()
是更底层的 API,它返回一个带有 stdout 和 stderr 流的子进程对象。这种方法适合处理大量数据,因为数据是以流的形式处理的。
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`标准输出:\n${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`标准错误输出:\n${data}`);
});
ls.on('close', (code) => {
console.log(`子进程退出码: ${code}`);
});
fork() 方法
fork()
是 spawn()
的特殊形式,专门用于创建新的 Node.js 进程。它会自动建立一个 IPC 通道,允许父子进程之间通信。
// parent.js
const { fork } = require('child_process');
const child = fork('./child.js');
child.on('message', (msg) => {
console.log('来自子进程的消息:', msg);
});
child.send({ hello: 'world' });
// child.js
process.on('message', (msg) => {
console.log('来自父进程的消息:', msg);
process.send({ foo: 'bar' });
});
进程间通信
Node.js 子进程支持多种通信方式:
- 标准输入输出 - 通过 stdin、stdout 和 stderr 流通信
- IPC 通道 - fork() 创建的进程可以通过 send() 和 message 事件通信
- 网络 - 子进程可以创建自己的服务器或客户端
IPC 通信示例
// 父进程
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (m) => {
console.log('父进程收到:', m);
});
child.send({ parent: 'pid' });
// 子进程 (child.js)
process.on('message', (m) => {
console.log('子进程收到:', m);
process.send({ child: 'pid' });
});
高级用法
处理长时间运行的进程
对于长时间运行的进程,需要妥善处理错误和退出事件:
const { spawn } = require('child_process');
const server = spawn('node', ['server.js']);
server.stdout.on('data', (data) => {
console.log(`服务器输出: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`服务器错误: ${data}`);
});
server.on('close', (code) => {
console.log(`服务器进程退出,代码 ${code}`);
});
// 5秒后终止服务器
setTimeout(() => {
server.kill('SIGTERM');
}, 5000);
并行执行多个命令
可以利用 Promise 和 async/await 来并行执行多个命令:
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
async function runCommands() {
try {
const [lsResult, psResult] = await Promise.all([
execPromise('ls -lh'),
execPromise('ps aux')
]);
console.log('目录内容:', lsResult.stdout);
console.log('进程列表:', psResult.stdout);
} catch (error) {
console.error('执行出错:', error);
}
}
runCommands();
安全注意事项
使用 child_process 模块时需要注意以下安全问题:
- 命令注入 - 避免将用户输入直接拼接到命令中
- 资源耗尽 - 限制子进程数量和资源使用
- 敏感信息泄露 - 注意子进程可能访问父进程环境变量
安全示例
// 不安全的做法
const userInput = 'some; rm -rf /';
exec(`ls ${userInput}`); // 危险!
// 安全的做法
const { execFile } = require('child_process');
execFile('ls', [userInput], (err, stdout) => {
// 处理结果
});
性能优化
对于需要频繁创建子进程的场景,可以考虑以下优化:
- 复用子进程 - 对于长时间运行的任务,保持子进程存活
- 使用工作池 - 管理一组子进程,避免频繁创建销毁
- 负载均衡 - 将任务均匀分配给多个子进程
工作池示例
const { fork } = require('child_process');
const os = require('os');
const path = require('path');
class WorkerPool {
constructor(workerPath, size = os.cpus().length) {
this.workers = [];
this.queue = [];
for (let i = 0; i < size; i++) {
const worker = fork(workerPath);
worker.on('message', (result) => {
const { resolve } = this.queue.shift();
resolve(result);
this.workers.push(worker);
this.processQueue();
});
this.workers.push(worker);
}
}
processQueue() {
if (this.queue.length > 0 && this.workers.length > 0) {
const worker = this.workers.pop();
const { task, resolve } = this.queue[0];
worker.send(task);
}
}
runTask(task) {
return new Promise((resolve) => {
this.queue.push({ task, resolve });
this.processQueue();
});
}
}
// 使用示例
const pool = new WorkerPool(path.join(__dirname, 'worker.js'));
pool.runTask({ data: 'some data' }).then(console.log);
实际应用场景
构建工具集成
许多构建工具使用 child_process 来执行外部命令:
const { execSync } = require('child_process');
function buildProject() {
try {
console.log('安装依赖...');
execSync('npm install', { stdio: 'inherit' });
console.log('运行测试...');
execSync('npm test', { stdio: 'inherit' });
console.log('构建项目...');
execSync('npm run build', { stdio: 'inherit' });
console.log('构建成功!');
} catch (error) {
console.error('构建失败:', error);
process.exit(1);
}
}
buildProject();
服务监控
可以使用 child_process 来监控其他服务的状态:
const { spawn } = require('child_process');
const http = require('http');
const serverProcess = spawn('node', ['server.js']);
serverProcess.stdout.on('data', (data) => {
console.log(`服务器输出: ${data}`);
});
serverProcess.stderr.on('data', (data) => {
console.error(`服务器错误: ${data}`);
});
// 监控端点
http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200);
res.end('OK');
} else if (req.url === '/restart' && req.method === 'POST') {
serverProcess.kill();
serverProcess = spawn('node', ['server.js']);
res.writeHead(200);
res.end('服务器已重启');
}
}).listen(8080);
调试技巧
调试子进程可能会遇到一些挑战,以下是几个有用的技巧:
- 启用调试输出 - 设置
{ stdio: 'inherit' }
可以看到子进程的输出 - 捕获退出码 - 检查子进程的退出码可以帮助诊断问题
- 使用 IPC 调试 - 通过 IPC 通道发送调试信息
调试示例
const { spawn } = require('child_process');
const child = spawn('node', ['buggy.js'], {
stdio: ['pipe', 'pipe', 'pipe', 'ipc']
});
child.stdout.on('data', (data) => {
console.log(`输出: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`错误: ${data}`);
});
child.on('close', (code, signal) => {
console.log(`进程退出,代码: ${code}, 信号: ${signal}`);
});
child.on('message', (message) => {
console.log('调试信息:', message);
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn