Node.js 中 child_process 模块教程
简介
Node.js 的 child_process
模块提供了在 Node.js 应用程序中创建和管理子进程的能力。这个功能对于执行系统命令、运行外部程序、以及充分利用多核 CPU 资源都非常有用。
主要方法
1. spawn()
spawn()
方法用于启动一个新进程来执行命令。它是异步的,适合处理大量数据的场景。
javascript
const { spawn } = require('child_process');
// 执行 ls 命令
const ls = spawn('ls', ['-l']);
// 处理输出
ls.stdout.on('data', (data) => {
console.log(`输出:${data}`);
});
// 处理错误
ls.stderr.on('data', (data) => {
console.error(`错误:${data}`);
});
// 进程结束
ls.on('close', (code) => {
console.log(`子进程退出码:${code}`);
});
2. exec()
exec()
方法会创建一个 shell 来执行命令,并缓冲产生的输出。适合执行简单的命令。
javascript
const { exec } = require('child_process');
exec('ls -l', (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
3. execFile()
execFile()
直接执行一个可执行文件,不创建 shell。这使得它比 exec()
更高效。
javascript
const { execFile } = require('child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`Node 版本: ${stdout}`);
});
4. fork()
fork()
是 spawn()
的特殊形式,专门用于创建 Node.js 进程。它会自动建立与子进程的 IPC 通道。
javascript
const { fork } = require('child_process');
// 启动子进程
const child = fork('child.js');
// 向子进程发送消息
child.send({ hello: 'world' });
// 接收子进程消息
child.on('message', (message) => {
console.log('收到子进程消息:', message);
});
进程间通信(IPC)
父进程和子进程之间可以通过 IPC 通道进行通信:
javascript
// 父进程 (parent.js)
const { fork } = require('child_process');
const child = fork('child.js');
child.send({ type: 'greeting', data: 'Hello from parent' });
child.on('message', (message) => {
console.log('Parent received:', message);
});
// 子进程 (child.js)
process.on('message', (message) => {
console.log('Child received:', message);
process.send({ type: 'response', data: 'Hello from child' });
});
错误处理和进程管理
错误处理
javascript
const child = spawn('nonexistent-command');
child.on('error', (error) => {
console.error('Failed to start child process:', error);
});
child.stderr.on('data', (data) => {
console.error(`子进程错误输出:${data}`);
});
进程终止
javascript
const child = spawn('long-running-process');
// 优雅地终止进程
child.kill('SIGTERM');
// 确保进程被终止
child.on('exit', (code, signal) => {
console.log(`进程退出,退出码:${code},信号:${signal}`);
});
最佳实践
-
选择合适的方法:
- 使用
spawn()
处理大量数据 - 使用
exec()
执行简单命令 - 使用
execFile()
执行文件 - 使用
fork()
运行 Node.js 模块
- 使用
-
资源管理:
javascriptconst child = spawn('some-process'); // 设置超时 const timeout = setTimeout(() => { child.kill(); }, 5000); child.on('exit', () => { clearTimeout(timeout); });
-
错误处理:
javascriptconst child = spawn('command'); child.on('error', handleError); child.stdout.on('error', handleError); child.stderr.on('error', handleError); function handleError(err) { console.error('Error:', err); child.kill(); }
进阶特性
1. 环境变量设置
javascript
const child = spawn('node', ['script.js'], {
env: { ...process.env, NODE_ENV: 'production' }
});
2. 工作目录设置
javascript
const child = spawn('command', [], {
cwd: '/custom/working/directory'
});
3. 管道和流
javascript
const { spawn } = require('child_process');
const fs = require('fs');
const child = spawn('grep', ['pattern']);
const inputFile = fs.createReadStream('input.txt');
const outputFile = fs.createWriteStream('output.txt');
inputFile.pipe(child.stdin);
child.stdout.pipe(outputFile);
调试和监控
1. 状态监控
javascript
const child = spawn('long-process');
child.on('spawn', () => {
console.log('进程已启动');
});
child.on('disconnect', () => {
console.log('IPC 通道已断开');
});
child.on('exit', (code, signal) => {
console.log(`进程已退出:code ${code}, signal ${signal}`);
});
2. 性能监控
javascript
const child = spawn('process');
const startTime = Date.now();
let bytesProcessed = 0;
child.stdout.on('data', (data) => {
bytesProcessed += data.length;
});
child.on('exit', () => {
const duration = Date.now() - startTime;
console.log(`处理了 ${bytesProcessed} 字节,用时 ${duration}ms`);
});
注意事项
- 始终处理错误事件
- 注意设置适当的超时机制
- 合理管理子进程资源
- 避免启动过多子进程
- 谨慎处理用户输入
- 注意跨平台兼容性
安全考虑
-
输入验证:
javascriptconst userInput = '...'; // 用户输入 if (!/^[a-zA-Z0-9_-]+$/.test(userInput)) { throw new Error('Invalid input'); }
-
权限控制:
javascriptconst child = spawn('command', [], { uid: nonRootUser, gid: nonRootGroup });