ChildProcess类


ChildProcess 类是代表衍生子进程的 EventEmitters 的实例。

不打算直接创建 ChildProcess 的实例。相反,要使用 child_process.spawn()child_process.exec()child_process.execFile()child_process.fork() 方法创建 ChildProcess 实例。

‘message’ 事件

  • message {Object} 一个已解析的 JSON 对象或原始值。

  • sendHandle {Handle} 一个 net.Socketnet.Server 对象,或是 undefined

当一个子进程使用 process.send() 发送信息时会触发 'message' 事件。

‘disconnect’ 事件

在父进程或子进程中调用 ChildProcess.disconnect() 后触发 'disconnect' 事件。在断开后它将不能再发送或接收信息,并且 ChildProcess.connected 属性会被设为 false

‘close’ 事件

  • code {Number} 如果子进程退出自身,该值会是退出码。

  • signal {String} 子进程被终止时的信号。

当子进程的 stdio 流被关闭时触发 'close' 事件。这与 'exit' 事件不同,因为多个进程可能共享相同的 stdio 流。

‘exit’ 事件

  • code {Number} 如果子进程退出自身,该值会是退出码。

  • signal {String} 子进程被终止时的信号。

当子进程结束时触发 'exit' 事件。如果进程退出,code 会是进程的最终退出码,否则会是 null。如果进程是由于收到的信号而终止的,signal 会是信号的字符串名称,否则会是 null。这两个将总有一个是非空的。

注意,当 'exit' 事件触发时,子进程的 stdio 流仍可能打开着。

另外,还要注意,Node.js 建立了 SIGINTSIGTERM 的信号处理程序,并且 Node.js 进程因为收到这些信号,所以不会立即终止。相反,Node.js 将执行一个清理序列的操作然后重新引发处理信号。

详见 waitpid(2)

‘error’ 事件

  • err {Error} 该错误对象。

每当出现以下情况时触发 'error' 事件:

  1. 该进程无法被衍生时;

  2. 该进程无法被杀死时;

  3. 向子进程发送信息失败时。

请注意,在一个错误发生后,'exit' 事件可能会也可能不会触发。如果你同时监听了 'exit''error' 事件,谨防处理函数被多次调用。

请同时参阅 ChildProcess#kill()ChildProcess#send()

child.pid

  • {Number} 整数

返回子进程的进程 id(PID)。

  1. const spawn = require('child_process').spawn;
  2. const grep = spawn('grep', ['ssh']);
  3. console.log(`Spawned child pid: ${grep.pid}`);
  4. grep.stdin.end();

child.connected

  • {Boolean} 在调用 .disconnect 后被设为 false

child.connected 属性指示是否仍可以从一个子进程发送和接收消息。当 child.connectedfalse 时,它将不再能够发送或接收的消息。

child.stdio

  • {Array}

一个到子进程的管道的稀疏数组,对应着传给 child_process.spawn() 的选项中值被设为 'pipe'stdio。请注意,与 child.stdinchild.stdoutchild.stderr 分别对应的 child.stdio[0]child.stdio[1]child.stdio[2] 同样有效。

在下面的例子中,只有子进程的 fd 1(stdout)被配置为 pipe,所以只有该父进程的 child.stdio[1] 是一个流,且在数组中的其他值都是 null

  1. const assert = require('assert');
  2. const fs = require('fs');
  3. const child_process = require('child_process');
  4. const child = child_process.spawn('ls', {
  5. stdio: [
  6. 0, // Use parents stdin for child
  7. 'pipe', // Pipe child's stdout to parent
  8. fs.openSync('err.out', 'w') // Direct child's stderr to a file
  9. ]
  10. });
  11. assert.equal(child.stdio[0], null);
  12. assert.equal(child.stdio[0], child.stdin);
  13. assert(child.stdout);
  14. assert.equal(child.stdio[1], child.stdout);
  15. assert.equal(child.stdio[2], null);
  16. assert.equal(child.stdio[2], child.stderr);

child.stdin

  • {Stream}

一个代表子进程的 stdinWritable Stream

注意,如果一个子进程等待读取所有的输入,子进程直到该流在通过 end() 关闭前不会继续。

如果衍生的子进程的 stdio[0] 设置任何不是 'pipe' 的值,那么这会是 undefined

child.stdinchild.stdio[0] 的一个别名。这两个属性都会指向相同的值。

child.stdout

  • {Stream}

一个代表子进程的 stdoutReadable Stream

如果衍生的子进程的 stdio[1] 设置任何不是 'pipe' 的值,那么这会是 undefined

child.stdoutchild.stdio[1] 的一个别名。这两个属性都会指向相同的值。

child.stderr

  • {Stream}

一个代表子进程的 stderrReadable Stream

如果衍生的子进程的 stdio[2] 设置任何不是 'pipe' 的值,那么这会是 undefined

child.stderrchild.stdio[2] 的一个别名。这两个属性都会指向相同的值。

child.send(message[, sendHandle[, options]][, callback])

  • message {Object}

  • sendHandle {Handle}

  • options {Object}

  • callback {Function}

  • 返回:{Boolean}

当在父进程和子进程之间建立了一个 IPC 通道时(例如,当使用 child_process.fork() 时),该 child.send() 方法可以用于将消息发送到子进程。当该进程是一个 Node.js 实例时,该信息可以通过 process.on('message') 事件接收到。

例如,在父脚本:

  1. const cp = require('child_process');
  2. const n = cp.fork(`${__dirname}/sub.js`);
  3. n.on('message', (m) => {
  4. console.log('PARENT got message:', m);
  5. });
  6. n.send({
  7. hello: 'world'
  8. });

然后是子进程脚本,'sub.js' 可能看上去像这样:

  1. process.on('message', (m) => {
  2. console.log('CHILD got message:', m);
  3. });
  4. process.send({
  5. foo: 'bar'
  6. });

Node.js 中的子进程有自己的一个 process.send() 方法,该方法允许子进程将信息发送回父进程。

当发送的是 {cmd: 'NODE_foo'} 消息时,则是一个特例。所有在 cmd 属性里包含前缀为 NODE_ 的属性被认为是预留给 Node.js 核心代码内部使用的,并且不会触发子进程的 process.on('message') 事件。相反,这种消息被用于触发 process.on('internalMessage') 事件,并且被 Node.js 内部消费。应用程序应避免使用这样的消息或监听 'internalMessage' 事件作为不通知该变化的依据。

可选的 sendHandle 参数可能被传给 child.send(),它是用于将一个 TCP 服务器或 socket 对象传给子进程。子进程会将收到的这个对象作为第二个参数传给注册在 process.on('message') 事件上的回调函数。

options 参数,如果存在的话,是用于发送某些类型的处理程序的参数对象。options 支持以下属性:

  • keepOpen - 当传 net.Socket 时的一个可使用的 Boolean 值。当它为 true 时,socket 在发送进程中保持开启状态。默认为 false

可选的 callback 是一个函数,它在信息发送后,但在子进程可能收到信息之前被调用。该函数被调用后只有一个参数:成功时是 null,或在失败时是一个 Error 对象。

如果没有提供 callback 函数,并且信息没被发送,一个 'error' 事件将被 ChildProcess 对象触发。这是有可能发生的,例如,当子进程已经退出时。

如果该通道已关闭或当未发送的信息积压超过阈值,使得它无法发送更多时,child.send() 将会返回 false。除此以外,该方法返回 true。该 callback 函数可用于实现流量控制。

例子:发送服务器对象

例如,sendHandle 参数可以用于将一个 TCP 服务器的处理程序传递给子进程,如下所示:

  1. const child = require('child_process').fork('child.js');
  2. // Open up the server object and send the handle.
  3. const server = require('net').createServer();
  4. server.on('connection', (socket) => {
  5. socket.end('handled by parent');
  6. });
  7. server.listen(1337, () => {
  8. child.send('server', server);
  9. });

那么子进程将会得到该服务器对象:

  1. process.on('message', (m, server) => {
  2. if (m === 'server') {
  3. server.on('connection', (socket) => {
  4. socket.end('handled by child');
  5. });
  6. }
  7. });

一旦服务器现在是在父进程和子进程之间共享,那么一些连接可以由父进程处理,一些可以由子进程来处理。

虽然上面的例子使用了 net 模块创建了一个服务器,使用 dgram 模块创建的服务器使用完全相同的工作流程,不同的是它监听一个 'message' 事件而不是 'connection' 事件并使用 server.bind 替代 server.listen。但是,目前仅 UNIX 平台支持这一点。

例子:发送 socket 对象

同样,sendHandle 参数可以用于将一个 socket 处理程序传递给子进程。以下的例子衍生了两个子进程分别用于处理 “normal” 连接或优先处理 “special” 连接:

  1. const normal = require('child_process').fork('child.js', ['normal']);
  2. const special = require('child_process').fork('child.js', ['special']);
  3. // Open up the server and send sockets to child
  4. const server = require('net').createServer();
  5. server.on('connection', (socket) => {
  6. // If this is special priority
  7. if (socket.remoteAddress === '74.125.127.100') {
  8. special.send('socket', socket);
  9. return;
  10. }
  11. // This is normal priority
  12. normal.send('socket', socket);
  13. });
  14. server.listen(1337);

child.js 会收到一个 socket 处理器作为第二个参数传递给事件回调函数:

  1. process.on('message', (m, socket) => {
  2. if (m === 'socket') {
  3. socket.end(`Request handled with ${process.argv[2]} priority`);
  4. }
  5. });

一旦一个 socket 被传递给了子进程,父进程不再能够跟踪套接字何时被销毁。为了说明这一点,.connections 属性会变成 null。当发生这种情况时,建议不要使用 .maxConnections

注意,该函数内部使用 JSON.stringify() 序列化 message

child.disconnect()

关闭父进程与子进程之间的 IPC 通道,一旦没有其他的连接使保持它活着,将允许子进程正常退出。在调用该方法后,分别在父进程和子进程上的 child.connectedprocess.connected 属性都会被设置为 false,并且它将不再能够在进程之间传递消息。

当正在接收的进程中没有消息时,将会触发 'disconnect' 事件。这经常在调用 child.disconnect() 后立即被触发。

请注意,当子进程是一个 Node.js 实例(例如,使用 child_process.fork() 衍生)时,最好在子进程内部调用 process.disconnect() 方法来关闭 IPC 通道。

child.kill([signal])

  • signal {String}

child.kill() 方法向子进程发送一个信号。如果没有给定参数,该进程将会发送 'SIGTERM' 信号。可以在 signal(7) 中查阅可用的信号列表。

  1. const spawn = require('child_process').spawn;
  2. const grep = spawn('grep', ['ssh']);
  3. grep.on('close', (code, signal) => {
  4. console.log(`child process terminated due to receipt of signal ${signal}`);
  5. });
  6. // Send SIGHUP to process
  7. grep.kill('SIGHUP');

如果信号没有被送达,ChildProcess 对象可能会触发一个 'error' 事件。向一个已经退出的进程发送一个信号不是一个错误,但可能有无法预知的后果。特别是如果该进程的 id 已经被其他程序注册时,信号会被发送到该进程,反而它可能会有意想不到的结果。

请注意,当函数被调用 kill 时,发送到子进程处理的信号实际上可能没有终止该进程。

kill(2),以供参考。

还要注意:当试图杀死他们的父进程时,子进程的子进程不会被终止。这可能发生在当在一个 shell 中运行一个新进程时或使用 ChildProcess 中的 shell 选项时,如本示例所示:

  1. 'use strict';
  2. const spawn = require('child_process').spawn;
  3. let child = spawn('sh', ['-c',
  4. `node -e "setInterval(() => {
  5. console.log(process.pid + 'is alive')
  6. }, 500);"`
  7. ], {
  8. stdio: ['inherit', 'inherit', 'inherit']
  9. });
  10. setTimeout(() => {
  11. child.kill(); // does not terminate the node process in the shell
  12. }, 2000);