詳解node child_process模塊學(xué)習(xí)筆記
NodeJs是一個(gè)單進(jìn)程的語(yǔ)言,不能像Java那樣可以創(chuàng)建多線程來(lái)并發(fā)執(zhí)行。當(dāng)然在大部分情況下,NodeJs是不需要并發(fā)執(zhí)行的,因?yàn)樗鞘录?qū)動(dòng)性永不阻塞。但單進(jìn)程也有個(gè)問(wèn)題就是不能充分利用CPU的多核機(jī)制,根據(jù)前人的經(jīng)驗(yàn),可以通過(guò)創(chuàng)建多個(gè)進(jìn)程來(lái)充分利用CPU多核,并且Node通過(guò)了child_process模塊來(lái)創(chuàng)建完成多進(jìn)程的操作。
child_process模塊給予node任意創(chuàng)建子進(jìn)程的能力,node官方文檔對(duì)于child_proces模塊給出了四種方法,映射到操作系統(tǒng)其實(shí)都是創(chuàng)建子進(jìn)程。但對(duì)于開(kāi)發(fā)者而已,這幾種方法的api有點(diǎn)不同
child_process.exec(command[, options][, callback]) 啟動(dòng)子進(jìn)程來(lái)執(zhí)行shell命令,可以通過(guò)回調(diào)參數(shù)來(lái)獲取腳本shell執(zhí)行結(jié)果
child_process.execfile(file[, args][, options][, callback]) 與exec類(lèi)型不同的是,它執(zhí)行的不是shell命令而是一個(gè)可執(zhí)行文件
child_process.spawn(command[, args][, options])僅僅執(zhí)行一個(gè)shell命令,不需要獲取執(zhí)行結(jié)果
child_process.fork(modulePath[, args][, options])可以用node執(zhí)行的.js文件,也不需要獲取執(zhí)行結(jié)果。fork出來(lái)的子進(jìn)程一定是node進(jìn)程
exec()與execfile()在創(chuàng)建的時(shí)候可以指定timeout屬性設(shè)置超時(shí)時(shí)間,一旦超時(shí)會(huì)被殺死
如果使用execfile()執(zhí)行可執(zhí)行文件,那么頭部一定是#!/usr/bin/env node
進(jìn)程間通信
node 與 子進(jìn)程之間的通信是使用IPC管道機(jī)制完成。如果子進(jìn)程也是node進(jìn)程(使用fork),則可以使用監(jiān)聽(tīng)message事件和使用send()來(lái)通信。
main.js
var cp = require('child_process'); //只有使用fork才可以使用message事件和send()方法 var n = cp.fork('./child.js'); n.on('message',function(m){ console.log(m); }) n.send({"message":"hello"});
child.js
var cp = require('child_process'); process.on('message',function(m){ console.log(m); }) process.send({"message":"hello I am child"})
父子進(jìn)程之間會(huì)創(chuàng)建IPC通道,message事件和send()便利用IPC通道通信.
句柄傳遞
學(xué)會(huì)如何創(chuàng)建子進(jìn)程后,我們創(chuàng)建一個(gè)HTTP服務(wù)并啟動(dòng)多個(gè)進(jìn)程來(lái)共同做到充分利用CPU多核。
worker.js
var http = require('http'); http.createServer(function(req,res){ res.end('Hello,World'); //監(jiān)聽(tīng)隨機(jī)端口 }).listen(Math.round((1+Math.random())*1000),'127.0.0.1');
main.js
var fork = require('child_process').fork; var cpus = require('os').cpus(); for(var i=0;i<cpus.length;i++){ fork('./worker.js'); }
上述代碼會(huì)根據(jù)你的cpu核數(shù)來(lái)創(chuàng)建對(duì)應(yīng)數(shù)量的fork進(jìn)程,每個(gè)進(jìn)程監(jiān)聽(tīng)一個(gè)隨機(jī)端口來(lái)提供HTTP服務(wù)。
上述就完成了一個(gè)典型的Master-Worker主從復(fù)制模式。在分布式應(yīng)用中用于并行處理業(yè)務(wù),具備良好的收縮性和穩(wěn)定性。這里需要注意,fork一個(gè)進(jìn)程代價(jià)是昂貴的,node單進(jìn)程事件驅(qū)動(dòng)具有很好的性能。此例的多個(gè)fork進(jìn)程是為了充分利用CPU的核,并非解決并發(fā)問(wèn)題.
上述示例有個(gè)不太好的地方就是占有了太多端口,那么能不能對(duì)于多個(gè)子進(jìn)程全部使用同一個(gè)端口從而對(duì)外提供http服務(wù)也只是使用這一個(gè)端口。嘗試將上述的端口隨機(jī)數(shù)改為8080,啟動(dòng)會(huì)發(fā)現(xiàn)拋出如下異常。
events.js:72 throw er;//Unhandled 'error' event Error:listen EADDRINUSE XXXX
拋出端口被占有的異常,這意味著只有一個(gè)worker.js才能監(jiān)聽(tīng)8080端口,而其余的會(huì)拋出異常。
如果要解決對(duì)外提供一個(gè)端口的問(wèn)題,可以參考nginx反向代理的做法。對(duì)于Master進(jìn)程使用80端口對(duì)外提供服務(wù),而對(duì)于fork的進(jìn)程則使用隨機(jī)端口,Master進(jìn)程接受到請(qǐng)求就將其轉(zhuǎn)發(fā)到fork進(jìn)程中
對(duì)于剛剛所說(shuō)的代理模式,由于進(jìn)程每收到一個(gè)連接會(huì)使用掉一個(gè)文件描述符,因此代理模式中客戶端連接到代理進(jìn)程,代理進(jìn)程再去連接fork進(jìn)程會(huì)使用掉兩個(gè)文件描述符,OS中文件描述符是有限的,為了解決這個(gè)問(wèn)題,node引入進(jìn)程間發(fā)送句柄的功能。
在node的IPC進(jìn)程通訊API中,send(message,[sendHandle])的第二個(gè)參數(shù)就是句柄。
句柄就是一種標(biāo)識(shí)資源的引用,它的內(nèi)部包含了指向?qū)ο蟮奈募枋龇?。句柄可以用?lái)描述一個(gè)socket對(duì)象,一個(gè)UDP套接子,一個(gè)管道主進(jìn)程向工作進(jìn)程發(fā)送句柄意味著當(dāng)主進(jìn)程接收到客戶端的socket請(qǐng)求后則直接將這個(gè)socket發(fā)送給工作進(jìn)程,而不需要再與工作進(jìn)程建立socket連接,則文件描述符的浪費(fèi)即可解決。我們來(lái)看示例代碼:
main.js
var cp = require('child_process'); var child = cp.fork('./child.js'); var server = require('net').createServer(); //監(jiān)聽(tīng)客戶端的連接 server.on('connection',function(socket){ socket.end('handled by parent'); }); //啟動(dòng)監(jiān)聽(tīng)8080端口 server.listen(8080,function(){ //給子進(jìn)程發(fā)送TCP服務(wù)器(句柄) child.send('server',server); });
child.js
process.on('message',function(m,server){ if(m==='server'){ server.on('connection',function(socket){ socket.end('handle by child'); }); } });
使用telnet或curl都可以測(cè)試:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handle by child
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
測(cè)試結(jié)果是每次對(duì)于客戶端的連接,有可能父進(jìn)程處理也有可能被子進(jìn)程處理?,F(xiàn)在我們嘗試僅提供http服務(wù),并且為了讓父進(jìn)程更加輕量,僅讓父進(jìn)程傳遞句柄給子進(jìn)程而不做請(qǐng)求處理:
main.js
var cp = require('child_process'); var child1 = cp.fork('./child.js'); var child2 = cp.fork('./child.js'); var child3 = cp.fork('./child.js'); var child4 = cp.fork('./child.js'); var server = require('net').createServer(); //父進(jìn)程將接收到的請(qǐng)求分發(fā)給子進(jìn)程 server.listen(8080,function(){ child1.send('server',server); child2.send('server',server); child3.send('server',server); child4.send('server',server); //發(fā)送完句柄后關(guān)閉監(jiān)聽(tīng) server.close(); });
child.js
var http = require('http'); var serverInChild = http.createServer(function(req,res){ res.end('I am child.Id:'+process.pid); }); //子進(jìn)程收到父進(jìn)程傳遞的句柄(即客戶端與服務(wù)器的socket連接對(duì)象) process.on('message',function(m,serverInParent){ if(m==='server'){ //處理與客戶端的連接 serverInParent.on('connection',function(socket){ //交給http服務(wù)來(lái)處理 serverInChild.emit('connection',socket); }); } });
當(dāng)運(yùn)行上述代碼,此時(shí)查看8080端口占有會(huì)有如下結(jié)果:
wang@wang ~/code/nodeStudy $ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 5120 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5126 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5127 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5133 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
運(yùn)行curl查看結(jié)果:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5127
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5120
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 利用NodeJS的子進(jìn)程(child_process)調(diào)用系統(tǒng)命令的方法分享
- 詳解從Node.js的child_process模塊來(lái)學(xué)習(xí)父子進(jìn)程之間的通信
- Node.js中child_process實(shí)現(xiàn)多進(jìn)程
- 詳解nodejs中的process進(jìn)程
- Node.js中的child_process模塊詳解
- node的process以及child_process模塊學(xué)習(xí)筆記
- nodejs 子進(jìn)程正確的打開(kāi)方式
- 利用node.js如何創(chuàng)建子進(jìn)程詳解
- 淺談Node.js 子進(jìn)程與應(yīng)用場(chǎng)景
- NodeJS父進(jìn)程與子進(jìn)程資源共享原理與實(shí)現(xiàn)方法
- node.js中process進(jìn)程的概念和child_process子進(jìn)程模塊的使用方法示例
相關(guān)文章
Node Mongoose用法詳解【Mongoose使用、Schema、對(duì)象、model文檔等】
這篇文章主要介紹了Node Mongoose用法,結(jié)合實(shí)例形式分析了Mongoose使用、Schema、對(duì)象、model文檔等基本原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-05-05探索node之事件循環(huán)的實(shí)現(xiàn)
這篇文章主要介紹了探索node之事件循環(huán)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10nodejs根據(jù)ip數(shù)組在百度地圖中進(jìn)行定位
本文主要介紹了nodejs根據(jù)ip數(shù)組在百度地圖中進(jìn)行定位的方法,具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03Node.js檢測(cè)端口(port)是否被占用的簡(jiǎn)單示例
大家有沒(méi)有遇到過(guò)在開(kāi)啟本地服務(wù)時(shí),有這么一種情況:當(dāng)前端口已經(jīng)被另一個(gè)項(xiàng)目使用了,導(dǎo)致服務(wù)開(kāi)啟失敗。那么接下來(lái),我們通過(guò)簡(jiǎn)簡(jiǎn)單單的示例代碼來(lái)檢測(cè)端口是否已經(jīng)被占用。有需要的朋友們可以參考借鑒。2016-09-09修改node.js默認(rèn)的npm安裝目錄實(shí)例
今天小編就為大家分享一篇修改node.js默認(rèn)的npm安裝目錄實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-05-05NodeJs之word文件生成與解析的實(shí)現(xiàn)代碼
這篇文章主要介紹了NodeJs之word文件生成與解析的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04Nodejs Post請(qǐng)求報(bào)socket hang up錯(cuò)誤的解決辦法
這篇文章主要介紹了Nodejs Post請(qǐng)求報(bào)socket hang up錯(cuò)誤的解決辦法,本文因少加了headers字段信息導(dǎo)致出現(xiàn)這個(gè)錯(cuò)誤,本文給出了一個(gè)完整的實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-09-09NodeJS 創(chuàng)建目錄和文件的方法實(shí)例分析
這篇文章主要介紹了NodeJS 創(chuàng)建目錄和文件的方法,涉及node.js中fs模塊mkdir、writeFile及目錄判斷existsSync等方法的功能與相關(guān)使用技巧,需要的朋友可以參考下2023-04-04