Node.js child_process 核心 API 详解

一、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);
  }
});

六、性能优化技巧

  1. 共享内存转移 (V8.5+)
ini 复制代码
const largeBuffer = Buffer.alloc(1024 * 1024); // 1MB

// 传统方式:复制数据
child.send(largeBuffer);

// 高性能方式:共享内存
child.send(largeBuffer, [largeBuffer.buffer]);
  1. 连接复用
javascript 复制代码
// 建立长期存活的进程连接
const persistentChild = fork('daemon.js');

// 使用对象池复用连接
function request(data) {
  return new Promise(resolve => {
    persistentChild.send(data, resolve);
  });
}
  1. 流式数据处理
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'));

七、安全加固指南

  1. 沙盒隔离
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) }
});
  1. 权限限制
php 复制代码
const { exec } = require('child_process');

exec('sensitive-command', {
  // Linux 用户隔离
  uid: 1001,
  gid: 1001,
  
  // Windows 权限
  windowsVerbatimArguments: true
});

总结

方法 最佳适用场景 关键注意事项
fork Node.js 微服务、CPU 密集型任务、进程间通信 注意内存消耗,使用连接池管理
exec Shell 命令执行、外部工具调用、简单操作 严格限制 maxBuffer,防范命令注入攻击

实际选择原则

  1. 在 Node.js 进程间需要复杂通信时使用 fork

  2. 在调用系统命令或外部工具时使用 exec

  3. 在需要流式处理大量数据时考虑 spawn

  4. 在需要安全执行命令和解析参数时使用 execFile

通过合理使用这些 API,可以构建高性能、可扩展的 Node.js 应用,同时确保系统稳定和安全。

相关推荐
willow11 小时前
Generator与Iterator
javascript
兔子零102411 小时前
Star-Office-UI-Node 实战:从 0 到 1 接入 OpenClaw 的多 Agent 看板
前端·ai编程
helloweilei11 小时前
一文搞懂Nextjs中的Proxy
前端·next.js
wuhen_n12 小时前
Pinia状态管理原理:从响应式核心到源码实现
前端·javascript·vue.js
陆枫Larry12 小时前
小程序 scroll-view 设置 padding 右侧不生效?用一层包裹解决
前端
晴殇i12 小时前
CommonJS 与 ES6 模块引入的区别详解
前端·javascript·面试
Selicens12 小时前
git批量删除本地多余分支
前端·git·后端
wuhen_n12 小时前
KeepAlive:组件缓存实现深度解析
前端·javascript·vue.js
前端付豪12 小时前
Nest 项目小实践之图书展示和搜索
前端·node.js·nestjs
wuhen_n12 小时前
Vue Router与响应式系统的集成
前端·javascript·vue.js