Node被設計用來有效率的處理I/O操作,但你應該知道,有些類型的程式並不適合這種模式。例如,如果你打算用Node處理一個CPU密集的任務,你可能會堵塞事件循環,並因此降低了程式的回應。替代辦法是,把CPU密集的任務分配給一個單獨的程序來處理,從而釋放事件循環。 Node允許你產生進程,並把這個新進程做成它父進程的子進程。在Node裡,子進程可以和父進程進行雙向通信,而且在某種程度上,父進程還可以監控和管理子進程。
另一個需要使用子程序的情況是,當你想簡單地執行一個外部指令,並讓Node取得指令的回傳值時。例如,你可以執行一個UNIX指令、腳本或其他那些不能在Node裡直接執行的指令。
本章將向你展示如何執行外部命令,創建,並和子進程通信,以及終結子進程。重點是讓你了解如何在Node進程外完成一系列任務。
執行外部指令
當你需要執行一個外部shell指令或執行檔時,你可以使用child_process模組,像這樣導入它:
var child_process = require(‘child_process')
然後可以用模組內的exec函數來執行外部指令:
var exec = child_process.exec;
exec(command,callback);
exec的第一個參數是你準備要執行的shell指令字串,第二個參數是一個回呼函數。這個回呼函數將會在exec執行完外部指令或有錯誤發生時被呼叫。回呼函數有三個參數:error,stdout,stderr,看下面的範例:
exec(‘ls',function(err,stdout,stderr){
//譯者註:若使用windows,可改為windows指令,如dir,後面不再贅述
});
如果有錯誤發生,第一個參數將會是一個Error類別的實例,如果第一個參數不包含錯誤,那麼第二個參數stdout將會包含指令的標準輸出。最後一個參數包含命令相關的錯誤輸出。
清單8-1 展示了一個複雜些的執行外部指令的範例
LISTING 8-1:執行外部指令(原始碼:chapter8/01_external_command.js)
//導入child_process模組的exec函數
var exec = require(‘child_process').exec;
//呼叫“cat *.js | wc -l”指令
exec(‘cat *.js | wc –l ‘, function(err, stdout, stderr ){ //第四行
//指令退出或呼叫失敗
if( err ){
//啟動外部程序失敗
console.log(‘child_process 退出,錯誤碼是:',err.code);
return;
}
}
第四行,我們把「cat *.js | wc -l」當作第一個參數傳遞給exec,你也可以嘗試任何其它指令,只要你在shell裡使用過的指令都可以。
然後將一個回呼函數作為第二個參數,它將會在錯誤發生或子程序終結的時候被呼叫。
還可以在回呼函數之前傳遞第三個可選參數,它包含一些配置選項,例如:
var exec = require(‘child_process').exec;
var options ={
timeout: 1000,
killSignal: ‘SIGKILL'
};
exec(‘cat *.js | wc –l ‘, options, function(err,stdout,stderr){
//…
});
可以使用的參數有:
1.cwd —— 目前目錄,可以指定目前工作目錄。
2.encoding —— 子進程輸出內容的編碼格式,預設值是”utf8”,也就是UTF-8編碼。如果子進程的輸出不是utf8,你可以用這個參數來設置,支援的編碼格式有:
ascii
utf8
ucs2
base64
如果你想了解Node支援的這些編碼格式的更多信息,請參考第4章「使用Buffer處理,編碼,解碼二進位資料」。
1.timeout —— 以毫秒為單位的指令執行逾時時間,預設為0,即無限制,一直等到子程序結束。
2.maxBuffer —— 指定stdout流和stderr流允許輸出的最大位元組數,如果達到最大值,子程序會被殺死。預設值是200*1024。
3.killSignal —— 當逾時或輸出快取達到最大值時發送給子程序的終結訊號。預設值是“SIGTERM”,它將給子程序發送一個終結訊號。通常都會使用這種有序的方式來結束流程。當用SIGTERM訊號時,進程接收到以後還可以進行處理或重寫訊號處理器的預設行為。如果目標進程需要,你可以同時向他傳遞其它的訊號(例如SIGUSR1)。你也可以選擇發送SIGKILL訊號,它會被作業系統處理並強制立刻結束子進程,這樣的話,子進程的任何清理操作都不會被執行。
如果你想更進一步的控制進程的結束,可以使用child_process.spawn指令,後面會介紹。
1.evn —— 指定傳遞給子進程的環境變量,預設是null,也就是說子進程會繼承在它被建立之前的所有父進程的環境變數。
注意:使用killSignal選項,你可以以字串的形式向目標程序發送訊號。在Node裡訊號以字串的形式存在,以下是UNIX訊號和對應預設操作的列表:
你可能想要為子行程提供一組可擴充的父級環境變數。如果直接去修改process.env對象,你會改變Node進程內所有模組的環境變量,這樣會惹很多麻煩。替代方案是,建立一個新對象,複製process.env裡的所有參數,請參閱範例8-2:
LISTING 8-2:使用參數化的環境變數來執行指令(原始碼:chapter8/02_env_vars_augment.js)
var env = process.env,
varName,
envCopy = {},
exec = require(‘child_prcess').exec;
//將process.env複製到envCopy
for( vaName in ev){
envCopy[varName] = env[varName];
}
//設定一些自訂變數
envCopy[‘CUSTOM ENV VAR1'] = ‘some value';
envCopy[‘CUSTOM ENV VAR2'] = ‘some other value';
//使用process.env和自訂變數來執行指令
exec(‘ls –la',{env: envCopy}, function(err,stdout,stderr){
if(err){ throw err; }
console.log(‘stdout:', stdout);
console.log(‘stderr:',stderr);
}
上面例子,創建了一個用來保存環境變量的envCopy變量,它首先從process.env那裡複製Node進程的環境變量,然後又添加或替換了一些需要修改的環境變量,最後把envCopy作為環境變數參數傳遞給exec函數並執行外部命令。
記住,環境變數是透過作業系統在進程之間傳遞的,所有類型的環境變數值都是以字串的形式到達子進程的。例如,如果父進程將數字123作為一個環境變量,子進程將會以字串的形式接收「123」。
下面的例子,將在同一個目錄裡建立2個Node腳本:parent.js和child.js,第一個腳本將會呼叫第二個,下面我們來建立這兩個檔案:
LISTING 8-3: 父行程設定環境變數(chapter8/03_environment_number_parent.js)
var exec = require('child_process').exec;
exec('node child.js', {env: {number: 123}}, function(err, stdout, stderr) {
if (err) { throw err; }
console.log('stdout:n', stdout);
console.log('stderr:n', stderr);
});
把這段程式碼存到parent.js,下面是子進程的源碼,把它們存到child.js(見例8-4)
例 8-4: 子程序解析環境變數(chapter8/04_environment_number_child.js)
var number = process.env.number;
console.log(typeof(number)); // → "string"
number = parseInt(number, 10);
console.log(typeof(number)); // → "number"
當你把這個檔案儲存為child.js後,就可以在這個目錄下執行下面的指令:
$ node parent.js
將會看到以下的輸出:
sdtou:
string
number
stderr:
可以看到,儘管父進程傳遞了一個數字型的環境變量,但是子進程卻以字符串形式接收它(參見輸出的第二行),在第三行你把這個字符串解析成了一個數字。
生成子程序
如你所見,可以使用child_process.exec()函數來啟動外部進程,並在進程結束的時候呼叫你的回呼函數,這樣用起來很簡單,不過也有一些缺點:
1.除了使用命令列參數和環境變量,使用exec()無法和子進程通訊
2.子程序的輸出是被快取的,因此你無法流化它,它可能會耗盡記憶體
幸運的是,Node的child_process模組允許更細粒度的控制子程序的啟動,停止,及其它常規操作。你可以在應用程式裡啟動一個新的子進程,Node提供一個雙向的通訊通道,可以讓父進程和子進程相互收發字串資料。父進程還可以有些針對子進程的管理操作,給子進程發送訊號,以及強制關閉子進程。
建立子程序
你可以使用child_process.spawn函式來建立一個新的子進程,見例8-5:
例 8-5: 產生子程序。 (chapter8/05_spawning_child.js)
// 導入child_process模組的spawn函式
var spawn = require('child_process').spawn;
// 產生用來執行 "tail -f /var/log/system.log"指令的子程序
var child = spawn('tail', ['-f', '/var/log/system.log']);
上面程式碼產生了一個用來執行tail指令的子程序,並將「-f」和「/bar/log/system.log」當作參數。 tail指令將會監控/var/log/system.og檔案(如果有的話),然後將所有追加的新資料輸出到stdout標準輸出流。 spawn函數傳回一個ChildProcess對象,它是一個指針對象,封裝了真實進程的存取介面。這個例子裡我們把這個新的描述子賦值給一個叫做child的變數。
監聽來自子程序的資料
任何包含stdout屬性的子程序句柄,都會將子程序的標準輸出stdout作為一個流對象,你可以在這個流對像上綁定data事件,這樣每當有資料塊可用時,就會調用對應的回調函數,請看下面的例子:
//將子程序的輸出印到控制台
child.stdout.on(‘data',function(data){
console.log(‘tail output: ‘ data);
});
每當子程序將資料輸出到標準輸出stdout時,父行程就會被通知並把資料印到控制台。
除了標準輸出,進程還有另一個預設輸出流:標準錯誤流,通常用這個流來輸出錯誤訊息。
在這個例子裡,如果/var/log/system.log檔案不存在,tail進程將會輸出類似下面的訊息:“/var/log/system.log:No such file or directory”,透過監聽stderr流,父進程會在這種錯誤發生時被通知。
父行程可以這樣監聽標準錯誤流:
child.stderr.on('data', function(data) {
console.log('tail error output:', data);
});
stderr屬性和stdout一樣,也是唯讀流,每當子程序往標準錯誤流裡輸出資料時,父程序就會被通知,並輸出資料。
傳送資料到子程序
除了從子進程的輸出流裡接收數據,父進程還可以透過childPoces.stdin屬性往子進程的標準輸入裡寫入數據,以此來往子進程發送數據。
子程序可以透過process.stdin只讀流來監聽標準輸入的數據,但是注意你首先必須得恢復(resume)標準輸入流,因為它默認處於暫停(paused)狀態。
例8-6將會建立一個包含下列功能的程式:
1. 1 應用:一個簡單的應用程序,可以從標準輸入接收整型,然後相加,再把相加以後的結果輸出到標準輸出流。這個應用程式作為一個簡單的計算服務, 把Node進程模擬成一個可以執行特定工作的外部服務。
2.測試 1應用的客戶端,傳送隨機整型,然後輸出結果。用來示範Node進程如何產生一個子進程然後讓它執行特定的任務。
用下面例8-6的程式碼建立一個名為plus_one.js的檔案:
例 8-6: 1 應用程式(chapter8/06_plus_one.js)
// 恢復預設是暫停狀態的標準輸入流
process.stdin.resume();
process.stdin.on('data', function(data) {
var number;
try {
// 將輸入資料解析為整數
number = parseInt(data.toString(), 10);
// 1
number = 1;
// 輸出結果
process.stdout.write(number "n");
} catch(err) {
process.stderr.write(err.message "n");
}
});
上面程式碼裡,我們等待來自stdin標準輸入流的數據,每當有數據可用,就假設它是個整數並把它解析到一個整型變數裡,然後加1,並把結果輸出到標準輸出流。
可以透過下面指令來執行這個程式:
$ node plus_one.js
執行後程式就開始等待輸入,如果你輸入一個整數然後按回車,就會看到一個被加1以後的數字被顯示到螢幕上。
可以按Ctrl-C來退出程式。
一個測試客戶端
現在你要建立一個Node進程來使用前面的「 1應用程式」提供的計算服務。
先建立一個名為plus_one_test.js的文件,內容見例8-7:
例 8-7: 測試 1應用(chapter8/07_plus_one_test.js)
var spawn = require('child_process').spawn;
// 產生一個子程序來執行 1應用程式
var child = spawn('node', ['plus_one.js']);
// 每一秒呼叫一次函數
setInterval(function() {
// Create a random number smaller than 10.000
var number = Math.floor(Math.random() * 10000);
// Send that number to the child process:
child.stdin.write(number "n");
// Get the response from the child process and print it:
child.stdout.once('data', function(data) {
console.log('child replied to ' number ' with: ' data);
});
}, 1000);
child.stderr.on('data', function(data) {
process.stdout.write(data);
});
從第一行到第四行啟動了一個用來運行「 1應用」的子進程,然後使用setInterval函數每秒鐘執行一次下列操作:
1..新建一個小於10000的隨機數
2.將這個數字當作字串傳遞給子程序
3.等待子程序回復一個字串
4.因為你想每次只接收1個數字的計算結果,所以需要使用child.stdout.once而不是child.stdout.on。如果使用了後者,會每隔1秒註冊一個data事件的回調函數,每個被註冊的回呼函數都會在子進程的stdout接收到資料時被執行,這樣你會發現同一個計算結果會被輸出多次,這種行為顯然是錯的。
在子行程退出時接收通知
當子程序退出時,exit事件會被觸發。例8-8展示如何監聽它:
例 8-8: 監聽子程序的退出事件 (chapter8/09_listen_child_exit.js)
var spawn = require('child_process').spawn;
// 產生子程序來執行 "ls -la"指令
var child = spawn('ls', ['-la']);
child.stdout.on('data', function(data) {
console.log('data from child: ' data);
});
// 當子程序退出:
child.on('exit', function(code) {
console.log('child process terminated with code ' code);
});
最後幾行加黑的程式碼,父行程使用子行程的exit事件來監聽它的退出事件,當事件發生時,控制台顯示對應的輸出。子行程的退出碼會被當作第一個參數傳遞給回呼函數。有些程式使用一個非0的退出碼來代表某種失敗狀態。例如,如果你嘗試執行指令“ls –al click filename.txt”,但目前目錄沒有這個文件,你就會得到一個值為1的退出碼,見例8-9:
例8-9:取得子程序的退出碼 (chapter8/10_child_exit_code.js)
var spawn = require('child_process').spawn;
// 產生子進程,執行"ls does_not_exist.txt" 指令
var child = spawn('ls', ['does_not_exist.txt']);
// 當子行程退出
child.on('exit', function(code) {
console.log('child process terminated with code ' code);
});
這個例子裡,exit事件觸發了回呼函數,並把子行程的退出碼當作第一個參數傳遞給它。如果子進程是被訊號殺死而導致的非正常退出,那麼對應的訊號碼會被當作第二個參數傳遞給回呼函數,如例8-10:
LISTING 8-10: 取得子程序的退出訊號(chapter8/11_child_exit_signal.js)
var spawn = require('child_process').spawn;
// 產生子進程,執行"sleep 10"指令
var child = spawn('sleep', ['10']);
setTimeout(function() {
child.kill();
}, 1000);
child.on('exit', function(code, signal) {
if (code) {
console.log('child process terminated with code ' code);
} else if (signal) {
console.log('child process terminated because of signal ' signal);
}
});
這個例子裡,啟動一個子進程來執行sleep 10秒的操作,但是還沒到10秒就發送了一個SIGKILL訊號給子進程,這將會導致如下的輸出:
child process terminated because of signal SIGTERM
發送訊號並殺死進程
在這部分,你將學習如何使用訊號來管理子進程。訊號是父進程用來跟子進程通信,甚至殺死子進程的簡單方式。
不同的訊號代碼代表不同的意義,有很多訊號,其中最常見的一些是用來殺死進程的。如果一個行程接收到一個它不知道如何處理的訊號,程式就會被異常中斷。有些訊號會被子進程處理,有些則只能由作業系統處理。
一般情況下,你可以使用child.kill方法來傳送一個訊號,預設發送SIGTERM訊號:
var spawn = require('child_process').spawn;
var child = spawn('sleep', ['10']);
setTimeout(function() {
child.kill();
}, 1000);
也可以透過傳入一個識別訊號的字串作為kill方法的唯一參數,來傳送某個特定的訊號:
child.kill(‘SIGUSR2');
要注意的是,雖然這個方法的名字叫做kill,但是發送的訊號不一定會殺死子程序。如果子程序處理了訊號,預設的訊號行為就會被覆蓋。用Node寫的子程序可以像下面這樣重寫訊號處理器的定義:
process.on('SIGUSR2', function() {
console.log('Got a SIGUSR2 signal');
});
現在,你定義了SIGUSR2的訊號處理器,當你的行程再收到SIGUSR2訊號的時候就不會被殺死,而是輸出「Got a SIGUSR2 signal」這句話。使用這個機制,你可以設計一個簡單的方式來跟子行程溝通甚至命令它。雖然不像使用標準輸入功能那麼豐富,但這方式要簡單很多。
小結
這一章,學習了使用child_process.exec方法來執行外部命令,這種方式可以不使用命令列參數,而是透過定義環境變數的方式把參數傳遞給子進程。
也學習了透過呼叫child_process.spawn方法產生子進程的方式來呼叫外部命令,這種方式你可以使用輸入流,輸出流來跟子進程通信,或者使用信號來跟子進程通信以及殺死進程。

Vercel是什么?本篇文章带大家了解一下Vercel,并介绍一下在Vercel中部署 Node 服务的方法,希望对大家有所帮助!

gm是基于node.js的图片处理插件,它封装了图片处理工具GraphicsMagick(GM)和ImageMagick(IM),可使用spawn的方式调用。gm插件不是node默认安装的,需执行“npm install gm -S”进行安装才可使用。

本篇文章带大家详解package.json和package-lock.json文件,希望对大家有所帮助!

本篇文章给大家分享一个Nodejs web框架:Fastify,简单介绍一下Fastify支持的特性、Fastify支持的插件以及Fastify的使用方法,希望对大家有所帮助!

如何用pkg打包nodejs可执行文件?下面本篇文章给大家介绍一下使用pkg将Node.js项目打包为可执行文件的方法,希望对大家有所帮助!

node怎么爬取数据?下面本篇文章给大家分享一个node爬虫实例,聊聊利用node抓取小说章节的方法,希望对大家有所帮助!

本篇文章给大家分享一个Node实战,介绍一下使用Node.js和adb怎么开发一个手机备份小工具,希望对大家有所帮助!

先介绍node.js的安装,再介绍使用node.js构建一个简单的web服务器,最后通过一个简单的示例,演示网页与服务器之间的数据交互的实现。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Dreamweaver Mac版
視覺化網頁開發工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Dreamweaver CS6
視覺化網頁開發工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版