Nodejs極簡入門教程(三):進程,nodejs極簡
Node 雖然自身存在多個線程,但是運行在 v8 上的 JavaScript 是單線程的。Node 的 child_process 模組用於建立子進程,我們可以通過子進程充分利用 CPU。範例:
複製代碼 代碼如下:
var fork = require('child_process').fork;
// 擷取當前機器的 CPU 數量
var cpus = require('os').cpus();
for (var i = 0; i < cpus.length; i++) {
// 產生新進程
fork('./worker.js');
}
這裡瞭解一下包括 fork 在內的幾個進程建立方法:
1.spawn(command, [args], [options]),啟動一個新進程來執行命令 command,args 為命令列參數
2.exec(command, [options], callback),啟動一個新進程來執行命令 command,callback 用於在進程結束時擷取標準輸入、標準輸出,以及錯誤資訊
3.execFile(file, [args], [options], [callback]),啟動一個新進程來執行可執行檔 file,callback 用於在進程結束時擷取標準輸入、標準輸出,以及錯誤資訊
4.fork(modulePath, [args], [options]),啟動一個新進程來執行一個 JavaScript 檔案模組,這時候建立的是 Node 子進程
Node 處理序間通訊
父進程
複製代碼 代碼如下:
// parent.js
var fork = require('child_process').fork;
// fork 返回子進程對象 n
var n = fork('./child.js');
// 處理事件 message
n.on('message', function(m) {
// 收到子進程發送的訊息
console.log('got message: ' + m);
});
// 向子進程發送訊息
n.send({hello: 'world'});
子進程
複製代碼 代碼如下:
// child.js
// 處理事件 message
process.on('message', function(m) {
console.log('got message: ' + m);
});
// process 存在 send 方法,用於向父進程發送訊息
process.send({foo: 'bar'});
需要注意的是,這裡的 send 方法是同步的,因此不建議用於發送大量的資料(可以使用 pipe 來代替,詳細見:http://nodejs.org/api/all.html#child_process_child_process_spawn_command_args_options)。
特殊的情況,訊息中 cmd 屬性值包含 NODE_ 首碼(例如:{cmd: ‘NODE_foo'} 訊息),那麼此訊息不會被提交到 message 事件(而是 internalMessage 事件),它們被 Node 內部使用。
send 方法的原型為:
複製代碼 代碼如下:
send(message, [sendHandle])
這裡,sendHandle(handle)可以被用於發送:
1.net.Native,原生的 C++ TCP socket 或者管道
2.net.Server,TCP 伺服器
3.net.Socket,TCP socket
4.dgram.Native,原生的 C++ UDP socket
5.dgram.Socket,UDP socket
send 發送 sendHandle 時實際上不是(也不能)直接發送 JavaScript 對象,而是傳送檔案描述符(最終以 JSON 字串發送),其他進程能夠通過這個檔案描述符還原出對應對象。
現在看一個例子:
父進程
複製代碼 代碼如下:
// parent.js
var fork = require('child_process').fork;
var n = fork('./child.js');
var server = require('net').createServer();
server.listen(7000, function() {
// 發送 TCP server 到子進程
n.send('server', server);
}).on('connection', function() {
console.log('connection - parent');
});
子進程
複製代碼 代碼如下:
process.on('message', function(m, h) {
if (m === 'server') {
h.on('connection', function() {
console.log('connection - child');
});
}
});
通過連接埠 7000 訪問此程式,得到輸出可能為 connection – parent 也可能得到輸出 connection – child。這裡子進程和父進程同時監聽了連接埠 7000。通常來說,多個進程監聽同一個連接埠會引起 EADDRINUSE 的異常,而此例的情況是,不同的兩個進程使用了相同的檔案描述符,且 Node 底層在監聽連接埠時對 socket 設定了 SO_REUSEADDR 選項,這使得此 socket 可以在不同的進程間複用。在多個進程監聽同一個連接埠時,同一時刻檔案描述符只能被一個進程使用,這些進程對 socket 的使用是搶佔式的。
cluster 模組
在 Node 的 v0.8 新增了 cluster 模組,通過 cluster 模組能夠輕鬆的在一台物理機器上構建一組監聽相同連接埠的進程。範例:
複製代碼 代碼如下:
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
// 檢查進程是否是 master 進程
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; ++i)
// 產生新的 worker 進程(只有 master 進程才可以調用)
cluster.fork();
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
}
我們在 worker 進程中調用 listen 方法,監聽請求將會傳遞給 master 進程。如果 master 進程已經存在一個正在監聽的 server 符合 worker 進程的要求,那麼此 server 的 handle 將會傳遞給 worker,如果不存在,master 進程則會建立一個,然後將 handle 傳遞給 worker 進程。
更多詳細的關於 cluster 的文檔:http://www.nodejs.org/api/cluster.html
JSON,JS,NODEJS三者的關係是怎?
三者性質完全不一樣
JS是JavaScript語言,是一種解釋性程式設計語言
JSON是JavaScript Object Notation,意思是JS語言中對象的表達法,常用於資料轉送(與XML的作用類似),常在AJAX中替代XML
NodeJS是一種服務端平台,可以在服務端運行用JavaScript寫的服務端指令碼
注意的是:JS中函數本身就是個對象,所以函數可以作為形參不是NodeJS專屬的,準確的說,NodeJS就是用Chrome瀏覽器的Google V8解譯器來解釋JS
總結來說:
JS是個程式設計語言
JSON是一種資料格式(沒有邏輯只有資料)
NodeJS是個軟體(JS服務端運行環境)
順帶一提:HTML是XML的派生(HTML是一種XML)但是為了適應其特定作用而有所改變,HTML(XML)與JSON同為資料表達語言,嚴格來講並不包含邏輯只包含資料。
什是nodejs的事件驅動編程
nodejs是單進程單線程,但是基於V8的強大驅動力,以及事件驅動模型,nodejs的效能非常高,而且想達到多核或者多進程也不是很難(現在已經有大量的第三方module來實現這個功能)。
這裡主要不是介紹nodejs具體應用代碼,而是想介紹一下事件驅動編程。
Dan York介紹了兩種典型的事件驅動執行個體。
第一個例子是關於醫生看病。
在美國去看醫生,需要填寫大量表格,比如保險、個人資訊之類,傳統的基於線程的系統(thread-based system),接待員叫到你,你需要在前台填寫完成這些表格,你站著填單,而接待員坐著看你填單。你讓接待員沒辦法接待下一個客戶,除非完成你的業務。
想讓這個系統能啟動並執行快一些,只有多加幾個接待員,人力成本需要增加不少。
基於事件的系統(event-based system)中,當你到視窗發現需要填寫一些額外的表格而不僅僅是掛個號,接待員把表格和筆給你,告訴你可以找個座位填寫,填完了以後再回去找他。你回去坐著填表,而接待員開始接待下一個客戶。你沒有阻塞接待員的服務。
第二個例子是快餐店點餐。
在基於線程的方式中(thread-based way)你到了櫃檯前,把你的點餐單給收銀員或者給收銀員直接點餐,然後等在那直到你要的食物準備好給你。收銀員不能接待下一個人,除非你拿到食物離開。想接待更多的客戶,容易!加更多的收銀員!
當然,我們知道快餐店其實不是這樣工作的。他們其實就是基於事件驅動方式,這樣收銀員更高效。只要你把點餐單給收銀員,某個人已經開始準備你的食物,而同時收銀員在進行收款,當你付完錢,你就站在一邊而收銀員已經開始接待下一個客戶。在一些餐館,甚至會給你一個號碼,如果你的食物準備好了,就呼叫你的號碼讓你去櫃檯取。關鍵的一點是,你沒有阻塞下一個客戶的訂餐請求。你訂餐的食物做好的事件會導致某個人做某個動作(某個服務員喊你的訂單號碼,你聽到你的號碼被喊到去取食物),在編程領域,我們稱這個為回調(callback function)。
相反的,Node.Js使用事件驅動模型,當web server接收到請求,就把它關閉然後進行處理,然後去服務下一個web請求。當這個請求完成,它被放回處理隊列,當到達隊列開頭,這個結果被返回給使用者。這個模型非常高效可擴充性非常強,因為webserver一直接受請求而不等待任何讀寫操作。(這也被稱之為非阻塞式IO或者事件驅動IO)。
考慮下面這個過程:
你用瀏覽器訪問nodejs伺服器上的"/about.html"
nodejs伺服器接收到你的請求,調用一個函數從磁碟上讀取這個檔案。
這段時間,nodejs webserver在服務後續的web請求。
當檔案讀取完畢,有一個回呼函數被插入到nodejs的服務隊列中。
nodejs webserver運行這個函數,實際上就是渲染(render)了about.html頁面返回給你的瀏覽器。
好像就節省了幾微秒時間,但是這很重要!特別是對於需要相應大量使用者的web server。