您现在的位置是:网站首页 > child_process模块文章详情

child_process模块

child_process模块简介

Node.js的child_process模块允许创建子进程来执行系统命令或其他Node.js脚本。这个模块提供了多种方法来创建和管理子进程,包括exec、execFile、spawn和fork等。通过子进程可以充分利用多核CPU资源,执行耗时操作而不会阻塞主事件循环。

创建子进程的方法

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}`);
  }
});

exec适合执行快速完成的命令,因为它会将所有输出缓存在内存中。对于大量输出的命令,可能会消耗大量内存。

execFile方法

execFile与exec类似,但不通过shell执行命令,直接运行可执行文件:

const { execFile } = require('child_process');

execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});

这种方法更安全,因为它不调用shell,减少了shell注入攻击的风险。

spawn方法

spawn是更底层的API,适合处理大量数据或需要流式处理的场景:

const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`标准输出: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`标准错误输出: ${data}`);
});

ls.on('close', (code) => {
  console.log(`子进程退出码: ${code}`);
});

spawn返回一个ChildProcess对象,可以通过事件监听器处理输出。这种方法不会缓冲所有输出,适合处理大量数据。

fork方法

fork是spawn的特殊形式,专门用于创建新的Node.js进程:

const { fork } = require('child_process');
const child = fork('./child.js');

child.on('message', (msg) => {
  console.log('来自子进程的消息:', msg);
});

child.send({ hello: 'world' });

fork创建的进程会自动建立IPC通道,父子进程可以通过send()和message事件通信。子进程文件child.js示例:

process.on('message', (msg) => {
  console.log('来自父进程的消息:', msg);
  process.send({ foo: 'bar' });
});

子进程选项配置

创建子进程时可以传递选项对象进行配置:

const { spawn } = require('child_process');

const child = spawn('prg', [], {
  stdio: 'pipe',  // 标准输入输出配置
  cwd: '/usr',    // 工作目录
  env: {          // 环境变量
    NODE_ENV: 'development'
  },
  detached: true, // 使子进程独立于父进程
  shell: true     // 使用shell执行命令
});

常用选项包括:

  • cwd:设置子进程的工作目录
  • env:环境变量键值对
  • stdio:配置子进程的标准输入输出
  • detached:是否让子进程独立运行
  • uid/gid:设置进程的用户/组标识

进程间通信

使用标准输入输出

通过stdio选项可以配置进程间的通信管道:

const { spawn } = require('child_process');

const grep = spawn('grep', ['ssh'], {
  stdio: ['pipe', 'pipe', process.stderr]
});

grep.stdin.write('hello\n');
grep.stdin.write('ssh connection\n');
grep.stdin.end();

grep.stdout.on('data', (data) => {
  console.log(`匹配的行: ${data}`);
});

使用IPC通道

fork创建的进程可以通过IPC通道通信:

// parent.js
const { fork } = require('child_process');
const child = fork('./child.js');

child.on('message', (msg) => {
  console.log('父进程收到:', msg);
});

setInterval(() => {
  child.send({ counter: Math.random() });
}, 1000);

// child.js
process.on('message', (msg) => {
  console.log('子进程收到:', msg);
  process.send({ response: 'pong' });
});

错误处理和进程管理

错误处理

子进程可能因多种原因失败,需要妥善处理:

const { spawn } = require('child_process');
const child = spawn('cmd_that_might_fail');

child.on('error', (err) => {
  console.error('启动子进程失败:', err);
});

child.stderr.on('data', (data) => {
  console.error(`子进程错误: ${data}`);
});

child.on('exit', (code, signal) => {
  if (code) console.log(`子进程退出,代码 ${code}`);
  if (signal) console.log(`子进程被信号终止: ${signal}`);
});

进程管理

可以控制子进程的生命周期:

const { spawn } = require('child_process');
const child = spawn('long_running_process');

// 5秒后终止子进程
setTimeout(() => {
  child.kill('SIGTERM');
}, 5000);

// 强制终止
setTimeout(() => {
  child.kill('SIGKILL');
}, 7000);

实际应用场景

并行处理任务

利用多核CPU并行处理任务:

const { fork } = require('child_process');
const os = require('os');
const cpuCount = os.cpus().length;

const workers = [];
for (let i = 0; i < cpuCount; i++) {
  workers.push(fork('./worker.js'));
}

// 分发任务
workers.forEach((worker, index) => {
  worker.send({ task: `task_${index}` });
});

执行系统命令

安全地执行系统命令并处理结果:

const { exec } = require('child_process');

function safeExec(cmd, callback) {
  exec(cmd, { timeout: 5000 }, (error, stdout, stderr) => {
    if (error) return callback(error);
    if (stderr) return callback(new Error(stderr));
    callback(null, stdout);
  });
}

safeExec('ls -l', (err, result) => {
  if (err) return console.error(err);
  console.log(result);
});

长时间运行的服务

监控并重启崩溃的子进程:

const { spawn } = require('child_process');

function startService() {
  const service = spawn('node', ['service.js']);
  
  service.on('exit', (code) => {
    console.log(`服务退出,代码 ${code}`);
    if (code !== 0) {
      console.log('5秒后重启服务...');
      setTimeout(startService, 5000);
    }
  });
  
  return service;
}

startService();

性能和安全考虑

性能优化

处理大量数据时使用流:

const { spawn } = require('child_process');
const fs = require('fs');

const input = fs.createReadStream('large_input.txt');
const transform = spawn('tr', ['a-z', 'A-Z']);
const output = fs.createWriteStream('large_output.txt');

input.pipe(transform.stdin);
transform.stdout.pipe(output);

安全最佳实践

避免shell注入攻击:

// 不安全的做法
const userInput = '; rm -rf /';
exec(`ls ${userInput}`);

// 安全的做法
const { execFile } = require('child_process');
execFile('ls', [userInput], (err, stdout) => {
  // 处理结果
});

限制资源使用:

const { spawn } = require('child_process');
const child = spawn('compute_intensive_task', [], {
  stdio: 'ignore',
  detached: true,
  // 限制资源
  resourceLimits: {
    maxOldGenerationSizeMb: 512,
    maxYoungGenerationSizeMb: 256,
    cpuTime: 1000
  }
});

高级用法

重定向输入输出

将子进程输出重定向到文件:

const { spawn } = require('child_process');
const fs = require('fs');

const out = fs.openSync('./out.log', 'a');
const err = fs.openSync('./err.log', 'a');

const child = spawn('prg', [], {
  stdio: ['ignore', out, err],
  detached: true
});

child.unref();

集群管理

结合cluster模块创建进程集群:

const cluster = require('cluster');
const { fork } = require('child_process');

if (cluster.isMaster) {
  // 主进程
  const cpuCount = require('os').cpus().length;
  
  for (let i = 0; i < cpuCount; i++) {
    cluster.fork();
  }
  
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.id} died`);
    cluster.fork();
  });
} else {
  // 工作进程
  require('./worker.js');
}

跨平台兼容性

处理不同平台的命令差异:

const { exec } = require('child_process');
const os = require('os');

const command = os.platform() === 'win32' 
  ? 'dir' 
  : 'ls';

exec(command, (err, stdout) => {
  if (err) throw err;
  console.log(stdout);
});

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步