和進(jìn)程管理相關(guān)的 API 單獨(dú)介紹起來比較枯燥,因此這里從一些典型的應(yīng)用場景出發(fā),分別介紹一些重要 API 的使用方法。
在 NodeJS 中可以通過 process.argv 獲取命令行參數(shù)。但是比較意外的是,node 執(zhí)行程序路徑和主模塊文件路徑固定占據(jù)了 argv[0]和 argv[1]兩個(gè)位置,而第一個(gè)命令行參數(shù)從 argv[2]開始。為了讓 argv 使用起來更加自然,可以按照以下方式處理。
function main(argv) {
// ...
}
main(process.argv.slice(2));
通常一個(gè)程序做完所有事情后就正常退出了,這時(shí)程序的退出狀態(tài)碼為 0?;蛘咭粋€(gè)程序運(yùn)行時(shí)發(fā)生了異常后就掛了,這時(shí)程序的退出狀態(tài)碼不等于 0。如果我們?cè)诖a中捕獲了某個(gè)異常,但是覺得程序不應(yīng)該繼續(xù)運(yùn)行下去,需要立即退出,并且需要把退出狀態(tài)碼設(shè)置為指定數(shù)字,比如1,就可以按照以下方式:
try {
// ...
} catch (err) {
// ...
process.exit(1);
}
NodeJS 程序的標(biāo)準(zhǔn)輸入流(stdin)、一個(gè)標(biāo)準(zhǔn)輸出流(stdout)、一個(gè)標(biāo)準(zhǔn)錯(cuò)誤流(stderr)分別對(duì)應(yīng) process.stdin、process.stdout 和 process.stderr,第一個(gè)是只讀數(shù)據(jù)流,后邊兩個(gè)是只寫數(shù)據(jù)流,對(duì)它們的操作按照對(duì)數(shù)據(jù)流的操作方式即可。例如,console.log 可以按照以下方式實(shí)現(xiàn)。
function log() {
process.stdout.write(
util.format.apply(util, arguments) + '\n');
}
在 Linux 系統(tǒng)下,我們知道需要使用 root 權(quán)限才能監(jiān)聽 1024 以下端口。但是一旦完成端口監(jiān)聽后,繼續(xù)讓程序運(yùn)行在 root 權(quán)限下存在安全隱患,因此最好能把權(quán)限降下來。以下是這樣一個(gè)例子。
http.createServer(callback).listen(80, function () {
var env = process.env,
uid = parseInt(env['SUDO_UID'] || process.getuid(), 10),
gid = parseInt(env['SUDO_GID'] || process.getgid(), 10);
process.setgid(gid);
process.setuid(uid);
});
上例中有幾點(diǎn)需要注意:
如果是通過 sudo 獲取 root 權(quán)限的,運(yùn)行程序的用戶的 UID 和 GID 保存在環(huán)境變量 SUDO_UID 和 SUDO_GID 里邊。如果是通過 chmod +s 方式獲取 root 權(quán)限的,運(yùn)行程序的用戶的 UID 和 GID 可直接通過 process.getuid 和 process.getgid 方法獲取。
process.setuid 和 process.setgid 方法只接受 number 類型的參數(shù)。
降權(quán)時(shí)必須先降 GID 再降 UID,否則順序反過來的話就沒權(quán)限更改程序的 GID了。
以下是一個(gè)創(chuàng)建 NodeJS 子進(jìn)程的例子。
var child = child_process.spawn('node', [ 'xxx.js' ]);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('child process exited with code ' + code);
});
上例中使用了.spawn(exec, args, options)
方法,該方法支持三個(gè)參數(shù)。第一個(gè)參數(shù)是執(zhí)行文件路徑,可以是執(zhí)行文件的相對(duì)或絕對(duì)路徑,也可以是根據(jù) PATH 環(huán)境變量能找到的執(zhí)行文件名。第二個(gè)參數(shù)中,數(shù)組中的每個(gè)成員都按順序?qū)?yīng)一個(gè)命令行參數(shù)。第三個(gè)參數(shù)可選,用于配置子進(jìn)程的執(zhí)行環(huán)境與行為。
另外,上例中雖然通過子進(jìn)程對(duì)象的.stdout
和.stderr
訪問子進(jìn)程的輸出,但通過 options.stdio 字段的不同配置,可以將子進(jìn)程的輸入輸出重定向到任何數(shù)據(jù)流上,或者讓子進(jìn)程共享父進(jìn)程的標(biāo)準(zhǔn)輸入輸出流,或者直接忽略子進(jìn)程的輸入輸出。
在 Linux 系統(tǒng)下,進(jìn)程之間可以通過信號(hào)互相通信。以下是一個(gè)例子。
/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ]);
child.kill('SIGTERM');
/* child.js */
process.on('SIGTERM', function () {
cleanUp();
process.exit(0);
});
在上例中,父進(jìn)程通過.kill
方法向子進(jìn)程發(fā)送 SIGTERM 信號(hào),子進(jìn)程監(jiān)聽 process 對(duì)象的 SIGTERM 事件響應(yīng)信號(hào)。不要被.kill
方法的名稱迷惑了,該方法本質(zhì)上是用來給進(jìn)程發(fā)送信號(hào)的,進(jìn)程收到信號(hào)后具體要做啥,完全取決于信號(hào)的種類和進(jìn)程自身的代碼。
另外,如果父子進(jìn)程都是 NodeJS 進(jìn)程,就可以通過 IPC(進(jìn)程間通訊)雙向傳遞數(shù)據(jù)。以下是一個(gè)例子。
/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ], {
stdio: [ 0, 1, 2, 'ipc' ]
});
child.on('message', function (msg) {
console.log(msg);
});
child.send({ hello: 'hello' });
/* child.js */
process.on('message', function (msg) {
msg.hello = msg.hello.toUpperCase();
process.send(msg);
});
可以看到,父進(jìn)程在創(chuàng)建子進(jìn)程時(shí),在 options.stdio 字段中通過 ipc 開啟了一條 IPC 通道,之后就可以監(jiān)聽子進(jìn)程對(duì)象的 message 事件接收來自子進(jìn)程的消息,并通過.send
方法給子進(jìn)程發(fā)送消息。在子進(jìn)程這邊,可以在 process 對(duì)象上監(jiān)聽 message 事件接收來自父進(jìn)程的消息,并通過.send
方法向父進(jìn)程發(fā)送消息。數(shù)據(jù)在傳遞過程中,會(huì)先在發(fā)送端使用 JSON.stringify 方法序列化,再在接收端使用 JSON.parse 方法反序列化。
守護(hù)進(jìn)程一般用于監(jiān)控工作進(jìn)程的運(yùn)行狀態(tài),在工作進(jìn)程不正常退出時(shí)重啟工作進(jìn)程,保障工作進(jìn)程不間斷運(yùn)行。以下是一種實(shí)現(xiàn)方式。
/* daemon.js */
function spawn(mainModule) {
var worker = child_process.spawn('node', [ mainModule ]);
worker.on('exit', function (code) {
if (code !== 0) {
spawn(mainModule);
}
});
}
spawn('worker.js');
可以看到,工作進(jìn)程非正常退出時(shí),守護(hù)進(jìn)程立即重啟工作進(jìn)程。
更多建議: