ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 子进程 ~~~ 稳定度: 3 - 稳定 ~~~ Node 通过 `child_process` 模块提供了类似 `popen(3)` 的处理三向数据流(stdin/stdout/stderr)的功能。 它能够以完全非阻塞的方式与子进程的 `stdin`、`stdout` 和 `stderr` 以流式传递数据。(请注意,某些程序在内部使用行缓冲 I/O。这不会影响到 node.js,但您发送到子进程的数据不会被立即消费。) 使用 `require('child_process').spawn()`或者 `require('child_process').fork()` 创建子进程,这两种方法的语义有些区别,下文将会解释。 ### 类: ChildProcess `ChildProcess` 是一个 [EventEmitter](#)。 子进程有三个与之关联的流:`child.stdin`、`child.stdout` 和 `child.stderr`。它们可以共享父进程的 stdio 流,也可以作为独立的被导流的流对象。 ChildProcess 类不能直接被使用, 使用 `spawn()` 或者 `fork()` 方法创建一个 Child Process 实例。 ### 事件: 'error' - `err` {Error Object} 错误。 发生于: 1. 进程不能被创建, 或者 1. 进程不能被终止掉, 或者 1. 由任何原因引起的数据发送到子进程失败. 参阅 [`ChildProcess#kill()`](#) 和 [`ChildProcess#send()`](#)。 ### 事件: 'exit' - `code` {Number} 假如进程正常退出,则为它的退出代码。 - `signal` {String} 假如是被父进程终止,则为所传入的终止子进程的信号。 这个事件是在子进程被结束的时候触发的. 假如进程被正常结束,‘code’就是退出进程的指令代码, 否则为'null'. 假如进程是由于接受到signal结束的, `signal` 就代表着信号的名称, 否则为`null`. 注意子进程的 stdio 流可能仍为开启状态。 参阅`waitpid(2)`. ### 事件: 'close' - `code` {Number} 假如进程正常退出,则为它的退出代码。 - `signal` {String} 假如是被父进程终止,则为所传入的终止子进程的信号。 这个事件会在一个子进程的所有stdio流被终止时触发, 这和'exit'事件有明显的不同,因为多进程有时候会共享同一个stdio流 ### 事件: 'disconnect' 在子进程或父进程中使用使用.disconnect()方法后,这个事件会被触发,在断开之后,就不可能再相互发送信息了。可以通过检查子进程的child.connected属性是否为true去检查是否可以发送信息 ### 事件: 'message' - `message` {Object} 一个已解析的JSON对象或者原始类型值 - `sendHandle` {Handle object} 一个socket 或者 server对象 通过.send()发送的信息可以通过监听'message'事件获取到 ### child.stdin - {Stream object} 子进程的'stdin'是一个‘可写流’,通过end()方法关闭该可写流可以终止子进程, 假如子进程的stdio流与父线程共享,这个child.stdin不会被设置 ### child.stdout - {Stream object} 子进程的`stdout`是个可读流. 假如子进程的stdio流与父线程共享,这个child.stdin不会被设置 ### child.stderr - {Stream object} 子进程的stderr是一个可读流 假如子进程的stdio流与父线程共享,这个child.stdin不会被设置 ### child.pid - {Integer} 子进程的PID 实例: ~~~ console.log('Spawned child pid: ' + grep.pid); grep.stdin.end(); ~~~ ### child.kill([signal]) - `signal` {String} 发送一个信号给子线程. 假如没有给参数, 将会发送 `'SIGTERM'`. 参阅 `signal(7)` 查看所有可用的signals列表 ~~~ // send SIGHUP to process grep.kill('SIGHUP'); ~~~ 当一个signal不能被传递的时候,会触发一个'error'事件, 发送一个信号到已终止的子线程不会发生错误,但是可能引起不可预见的后果, 假如该子进程的ID已经重新分配给了其他进程,signal将会被发送到其他进程上面,大家可以猜想到这发生什么后果。 注意,当函数调用‘kill’, 传递给子进程的信号不会去终结子进程, ‘kill’实际上只是发送一个信号到进程而已。 See `kill(2)` ### child.send(message, [sendHandle]) - `message` {Object} - `sendHandle` {Handle object} 当使用 `child_process.fork()` 你可以使用 `child.send(message, [sendHandle])`向子进程写数据 and 数据将通过子进程上的‘message’事件接受. 例如: ~~~ n.send({ hello: 'world' }); ~~~ 然后是子进程脚本的代码, `'sub.js'` 代码如下: ~~~ process.send({ foo: 'bar' }); ~~~ 在子进程脚本中'process'对象有‘send()’方法, ‘process’每次通过它的信道接收到信息都会触发事件,信息以对象形式返回。 不过发送`{cmd: 'NODE_foo'}` 信息是个比较特殊的情况. 所有在‘cmd’属性中包含 a `NODE_`前缀的信息将不会触发‘message’事件, 因为他们是由node 核心使用的内部信息. 相反这种信息会触发 `internalMessage` 事件, 你应该通过各种方法避免使用这种特性, 他改变的时候不会接收到通知. `child.send()`的`sendHandle` 选项是用来发送一个TCP服务或者socket对象到另一个线程的,子进程将会接收这个参数作为‘message’事件的第二个参数。 假如信息不能被发送,将会触发一个‘error’事件, 比如说因为子线程已经退出了。 #### 例子: 发送一个server对象 这里是一个发送一个server对象的例子: ~~~ // 创建一个handle对象,发送一个句柄. var server = require('net').createServer(); server.on('connection', function (socket) { socket.end('handled by parent'); }); server.listen(1337, function() { child.send('server', server); }); ~~~ 同时子进程将会以如下方式接收到这个server对象: ~~~ process.on('message', function(m, server) { if (m === 'server') { server.on('connection', function (socket) { socket.end('handled by child'); }); } }); ~~~ 注意,server对象现在有父进程和子进程共享,这意味着某些连接将会被父进程和子进程处理。 对‘dgram’服务器,工作流程是一样的, 你监听的是‘message’事件,而不是 ‘connection’事件, 使用‘server.bind’ ,而不是‘server.listen’.(当前仅在UNIX平台支持) #### 示例: 发送socket对象 这是个发送socket的例子. 他将创建两个子线程 ,同时处理连接,这是通过使用远程地址 `74.125.127.100` 作为 VIP 发送socket到一个‘特殊’的子线程. 其他的socket将会发送到‘正常’的线程里. ~~~ // if this is a VIP if (socket.remoteAddress === '74.125.127.100') { special.send('socket', socket); return; } // just the usual dudes normal.send('socket', socket); }); server.listen(1337); ~~~ `child.js` 文件代码如下: ~~~ process.on('message', function(m, socket) { if (m === 'socket') { socket.end('You were handled as a ' + process.argv[2] + ' person'); } }); ~~~ 注意,一旦单个的socket被发送到子进程,当这个socket被删除之后,父进程将不再对它保存跟踪,这表明了这个条件下‘.connetions’属性将变成'null', 在这个条件下同时也不推荐使用‘.maxConnections’属性. ### child.disconnect() 使用`child.disconnect()` 方法关闭父进程与子进程的IPC连接. 他让子进程非常优雅的退出,因为已经没有活跃的IPC信道. 当调用这个方法,‘disconnect’事件将会同时在父进程和子进程内被触发,‘connected’的标签将会被设置成‘flase’, 请注意,你也可以在子进程中调用‘process.disconnect()’ ### child_process.spawn(command, [args], [options]) - `command` {String}要运行的命令 - `args` {Array} 字符串参数列表 - `options` {Object} - `cwd` {String} 子进程的当前的工作目录 - `stdio` {Array|String} 子进程 stdio 配置. (参阅下文) - `customFds` {Array} **Deprecated** 作为子进程 stdio 使用的 文件标示符. (参阅下文) - `env` {Object} 环境变量的键值对 - `detached` {Boolean} 子进程将会变成一个进程组的领导者. (参阅下文) - `uid` {Number} 设置用户进程的ID. (See setuid(2).) - `gid` {Number} 设置进程组的ID. (See setgid(2).) - 返回: {ChildProcess object} 用给定的命令发布一个子进程,带有‘args’命令行参数,如果省略的话,‘args’默认为一个空数组 第三个参数被用来指定额外的设置,默认是: ~~~ { cwd: undefined, env: process.env } ~~~ `cwd`允许你从被创建的子进程中指定一个工作目录. 使用 `env` 去指定在新进程中可用的环境变量. 一个运行 `ls -lh /usr`的例子, 获取`stdout`, `stderr`, 和退出代码: ~~~ ls.on('close', function (code) { console.log('child process exited with code ' + code); }); ~~~ 例子: 一个非常精巧的方法执行 'ps ax | grep ssh' ~~~ grep.on('close', function (code) { if (code !== 0) { console.log('grep process exited with code ' + code); } }); ~~~ 检查执行错误的例子: ~~~ child.stderr.setEncoding('utf8'); child.stderr.on('data', function (data) { if (/^execvp\(\)/.test(data)) { console.log('Failed to start child process.'); } }); ~~~ 注意,当在spawn过程中接收一个空对象,这会导致创建的进程使用空的环境变量而不是使用‘process.env’.这是由于与一个废弃API向后兼容的问题. `child_process.spawn()` 中的 `stdio` 选项是一个数组,每个索引对应子进程中的一个文件标识符。可以是下列值之一: 1. `'pipe'` -在子进程与父进程之间创建一个管道,管道的父进程端以 `child_process` 的属性的形式暴露给父进程,如 `ChildProcess.stdio[fd]`。 为 文件标识(fds) 0 - 2 建立的管道也可以通过 ChildProcess.stdin,ChildProcess.stdout 及 ChildProcess.stderr 分别访问。 1. `'ipc'` - 创建一个IPC通道以在父进程与子进程之间传递 消息/文件标识符。一个子进程只能有最多*一个* IPC stdio 文件标识。 设置该选项激活 ChildProcess.send() 方法。如果子进程向此文件标识符写JSON消息,则会触发 ChildProcess.on("message")。 如果子进程是一个nodejs程序,那么IPC通道的存在会激活process.send()和process.on('message') 1. `'ignore'` - 不在子进程中设置该文件标识。注意,Node 总是会为其spawn的进程打开 文件标识(fd) 0 - 2。 当其中任意一项被 ignored,node 会打开 `/dev/null` 并将其附给子进程的文件标识(fd)。 1. `Stream` 对象 - 与子进程共享一个与tty,文件,socket,或者管道(pipe)相关的可读或可写流。 该流底层(underlying)的文件标识在子进程中被复制给stdio数组索引对应的文件标识(fd) 1. 正数 - 该整形值被解释为父进程中打开的文件标识符。他与子进程共享,和`Stream`被共享的方式相似。 1. `null`, `undefined` - 使用默认值。 对于stdio fds 0,1,2(或者说stdin,stdout和stderr),pipe管道被建立。对于fd 3及往后,默认为`ignore` 作为快捷方式,`stdio` 参数除了数组也可以是下列字符串之一: - `ignore` - `['ignore', 'ignore', 'ignore']` - `pipe` - `['pipe', 'pipe', 'pipe']` - `inherit` - `[process.stdin, process.stdout, process.stderr]` 或 `[0,1,2]` 实例: ~~~ // 开启一个额外的 fd=4 来与提供 startd 风格接口的程序进行交互。 spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] }); ~~~ 如果 `detached` 选项被设置,则子进程会被作为新进程组的 leader。这使得子进程可以在父进程退出后继续运行。 缺省情况下,父进程会等待脱离了的子进程退出。要阻止父进程等待一个给出的子进程 `child`,使用 `child.unref()` 方法,则父进程的事件循环引用计数中将不会包含这个子进程。 脱离一个长时间运行的进程并将它的输出重定向到一个文件的例子: ~~~ child.unref(); ~~~ 当使用 `detached` 选项来启动一个长时间运行的进程,该进程不会在后台保持运行,除非向它提供了一个不连接到父进程的 `stdio` 配置。如果继承了父进程的 `stdio`,则子进程会继续附着在控制终端。 有一个已废弃的选项 `customFds` 允许指定特定文件描述符作为子进程的 stdio。该 API 无法移植到所有平台,因此被移除。使用 `customFds` 可以将新进程的 `[stdin, stdout, stderr]` 钩到已有流上;`-1` 表示创建新流。自己承担使用风险。 参阅:`child_process.exec()` 和 `child_process.fork()` ### child_process.exec(command, [options], callback) - `command` {String} 将要执行的命令,用空格分隔参数 - `options` {Object} - `cwd` {String} 子进程的当前工作目录 - `env` {Object} 环境变量键值对 - `encoding` {String} 编码(缺省为 'utf8') - `shell` {String} 运行命令的 shell(UNIX 上缺省为 '/bin/sh',Windows 上缺省为 'cmd.exe'。该 shell 在 UNIX 上应当接受 `-c` 开关,在 Windows 上应当接受 `/s /c` 开关。在 Windows 中,命令行解析应当兼容 `cmd.exe`。) - `timeout` {Number} 超时(缺省为 0) - `maxBuffer` {Number} 最大缓冲(缺省为 200*1024) - `killSignal` {String} 结束信号(缺省为 'SIGTERM') - `callback` {Function} 进程结束时回调并带上输出 - `error` {Error} - `stdout` {Buffer} - `stderr` {Buffer} - 返回:ChildProcess 对象 在 shell 中执行一个命令并缓冲输出。 ~~~ child = exec('cat *.js bad_file | wc -l', function (error, stdout, stderr) { console.log('stdout: ' + stdout); console.log('stderr: ' + stderr); if (error !== null) { console.log('exec error: ' + error); } }); ~~~ 回调参数为 `(error, stdout, stderr)`。当成功时,`error` 会是 `null`。当遇到错误时,`error` 会是一个 `Error` 实例,并且 `err.code` 会是子进程的退出代码,同时 `err.signal` 会被设置为结束进程的信号名。 第二个可选的参数用于指定一些选项,缺省选项为: ~~~ { encoding: 'utf8', timeout: 0, maxBuffer: 200*1024, killSignal: 'SIGTERM', cwd: null, env: null } ~~~ 如果 `timeout` 大于 0,则当进程运行超过 `timeout` 毫秒后会被终止。子进程使用 `killSignal` 信号结束(缺省为 `'SIGTERM'`)。`maxBuffer` 指定了 stdout 或 stderr 所允许的最大数据量,如果超出这个值则子进程会被终止。 ### child_process.execFile(file, args, options, callback) - `file` {String} 要运行的程序的文件名 - `args` {Array} 字符串参数列表 - `options` {Object} - `cwd` {String} 子进程的当前工作目录 - `env` {Object} 环境变量键值对 - `encoding` {String} 编码(缺省为 'utf8') - `timeout` {Number} 超时(缺省为 0) - `maxBuffer` {Number} 最大缓冲(缺省为 200*1024) - `killSignal` {String} 结束信号(缺省为 'SIGTERM') - `callback` {Function} 进程结束时回调并带上输出 - `error` {Error} - `stdout` {Buffer} - `stderr` {Buffer} - 返回:ChildProcess 对象 该方法类似于 `child_process.exec()`,但是它不会执行一个子 shell,而是直接执行所指定的文件。因此它稍微比 `child_process.exec` 精简,参数与之一致。 ### child_process.fork(modulePath, [args], [options]) - `modulePath` {String} 子进程中运行的模块 - `args` {Array} 字符串参数列表 - `options` {Object} - `cwd` {String} 子进程的当前工作目录 - `env` {Object} 环境变量键值对 - `encoding` {String} 编码(缺省为 'utf8') - `execPath` {String} 创建子进程的可执行文件 - 返回:ChildProcess 对象 该方法是 `spawn()` 的特殊情景,用于派生 Node 进程。除了普通 ChildProcess 实例所具有的所有方法,所返回的对象还具有内建的通讯通道。详见 `child.send(message, [sendHandle])`。 缺省情况下所派生的 Node 进程的 stdout、stderr 会关联到父进程。要更改该行为,可将 `options` 对象中的 `silent` 属性设置为 `true`。 子进程运行完成时并不会自动退出,您需要明确地调用 `process.exit()`。该限制可能会在未来版本里接触。 这些子 Node 是全新的 V8 实例,假设每个新的 Node 需要至少 30 毫秒的启动时间和 10MB 内存,就是说您不能创建成百上千个这样的实例。 `options` 对象中的 `execPath` 属性可以用非当前 `node` 可执行文件来创建子进程。这需要小心使用,并且缺省情况下会使用子进程上的 `NODE_CHANNEL_FD` 环境变量所指定的文件描述符来通讯。该文件描述符的输入和输出假定为以行分割的 JSON 对象。