# 子進程
~~~
穩定度: 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 對象。