標籤:title 自己的 127.0.0.1 console 函數 相互 實現 node char
零、前言
為什麼要用Node?
Node把非阻塞IO作為提高應用效能的方式。而在JS中,天生擁有著非同步編程機制: 事件機制。同時JS中不存在多進程。這樣當你執行相對較慢需要花費時間長的IO操作時並不會阻塞主進程的任務。
在NodeJS中流行兩種響應邏輯的管理方式: 回調, 事件監聽。
回調通常用來定義一次性響應的邏輯。事件監聽器本質上也是一個回調,不同的是它跟事件相互關聯。
一、使用回調來處理一次性事件
回調是一個函數,被當做參數傳遞給非同步函數,描述了非同步作業完成後要做什麼。
案例: 建立一個簡單的http伺服器實現如下功能
1. 非同步擷取存放在JSON檔案中的文章標題
2. 非同步擷取簡單的HTML模版
3. 將文章標題組裝到HTML頁面中
4. 將HTML頁面發送給使用者
var http = require("http");var fs = require("fs");var srcFilename = "./titles.json";var distFilename = "./index.html";http.createServer(function(req, res){ if(req.url == ‘/‘){ fs.readFile(srcFilename, function(err, data){ if(err){ console.log(err); res.end("server end"); }else{ var titles = JSON.parse(data); fs.readFile(distFilename, "utf-8", function(err, data){ if(err){ console.log(err); res.end("server end"); }else{ var html = data.replace("%", titles.join("</li><li>")); res.writeHead(200, {"Content-type":"text/html"}); res.write(html); res.end(); } }); } }); }}).listen(8080, "127.0.0.1");
以上是NodeJS主程式,通過http模組建立一個簡單的HTTP伺服器。監聽指定連接埠8080和127.0.0.1主機地址。另外通過判斷request.url請求路徑來使用fs讀取不同檔案中的內容,最後將這些內容填充到相應常值內容中,通過response響應對象將資料發送給使用者。json資料來源和html模板檔案如下:
<!DOCTYPE html><html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta id="viewport" name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"> <title>Node課程</title> </head> <body> <h1>最新Node課程內容:</h1> <ul> <li>%</li> </ul> </body></html>
[ "NodeJS模組原理講解", "NodeJS常用模組簡單講解", "NodeJS非同步編程基礎"]
二、使用事件發射器處理重複性事件
通過給事件綁定一個觸發時的回呼函數,那麼當事件發射器觸發事件時,會在事件觸發時執行這些回呼函數。NodeJS中很多內建核心組件都是事件發射器的子類。如HTTP伺服器,Net伺服器和Stream流對象等。
簡單案例
var net = require("net");var server = net.createServer(function(socket){ socket.on("data", function(data){ socket.write("輸入的內容是:"); socket.write(data); });});server.listen(8888, "127.0.0.1");
監聽器可以針對某些特殊事件的監聽只調用一次事件處理的回呼函數。使用 .once() 方式
接下來我們就來建立一個屬於自己的事件發射器。
首先我們先回顧一下events核心模組 具體可以參考這篇文章: Events核心模組講解
接下來建立一個簡單的發布訂閱系統,實現以下功能:
1. 使用者串連服務後,可以看到當前正處於串連狀態的其他使用者
2. 使用者串連服務後,可以給所有使用者發送訊息
3. 使用者斷開服務後,系統會將該使用者從串連使用者池中移除
4. 處於某種原因需要暫停服務時,可以通過指定的命令停止服務
var events = require("events");var net = require("net");// 建立一個頻道發射器,用來管理所有的使用者,使用者的行為事件及其響應var channel = new events.EventEmitter();channel.clients = {};channel.subscriptions = {};channel.setMaxListeners(50);// 註冊使用者串連服務的事件channel.on(‘join‘, function(id, client) { channel.clients[id] = client; channel.subscriptions[id] = function(senderId, message){ if(id != senderId){ channel.clients[id].write(message); } }; channel.on(‘broadcast‘, channel.subscriptions[id]); // 串連服務後先友好地提示當前房間內的人數 var welcome = "Welcome! Guests online: " + this.listeners("broadcast").length; client.write(welcome);});// 註冊使用者斷開服務的事件channel.on(‘leave‘, function(id) { // 移除該使用者的廣播訊息事件響應 channel.removeListener("broadcast", channel.subscriptions[id]); // 將訊息廣播給其他使用者 channel.emit("broadcast", id, id + " has left the chat.\n");});// 註冊暫停服務的事件channel.on(‘shutdown‘, function() { // 先發訊息提醒所有使用者,服務已經暫停 channel.emit("broadcast", ‘‘, ‘Chat has shut down.\n‘); // 移除所有的廣播事件 channel.removeAllListener("broadcast");});net.createServer(function(socket){ var id = socket.remoteAddress + " : " + socket.remotePort; // 觸發使用者串連服務的事件 channel.emit("join", id, socket); // 註冊使用者發送訊息的事件 socket.on(‘data‘, function(data){ var data = data.toString(); // 這裡先簡單設定為暫停服務的指令為shutdown if(data == "shutdown"){ channel.emit("shutdown"); }else{ channel.emit("broadcast", id, data.toString()); } }); // 註冊使用者離開服務的事件 socket.on(‘close‘, function() { channel.emit("leave", id); });}).listen(8080, ‘127.0.0.1‘);
** 看了前面的兩個事件發射器案例,你還可以利用事件發射器來建立一個檔案監聽器。
通常的做法都是建立一個JS類,通過繼承EventEmitter類來處理檔案目錄下的所有檔案。通過監視目錄中的檔案變化從而將變化的檔案進行處理。
var events = require("events");var util = require("util");var fs = require("fs");function Watcher(watcherDir, processedDir){ this.watcherDir = watcherDir; this.processedDir = processedDir;}util.inherit(Watcher, events.EventEmitter);Watcher.prototype.watch = function(){ var watcher = this; fs.readdir(watcher.watcherDir, function(err, files){ if(err){ throw err; }else{ for(var index in files){ if(files[index].isFile()){ watcher.emit("process", files[index]); } } } });};Watcher.prototype.start = function(){ var watcher = this; fs.watchFile(watcherDir, function(){ watcher.watch(); });};// 建立這樣一個檔案監聽器執行個體var watchDir = "./watch";var processedDir = "./done";var watcher = new Watcher(watcherDir, processedDir);// 註冊檔案處理函數watcher.on(‘process‘, function(file) { var watcherFile = this.watcherDir + "/" + file; var processedFile = this.processedDir + "/" + file.toLowerCase(); // 通過重新命名的方式來移動檔案 fs.rename(watchFile, processedFile, function(err){ if(err){ throw err; } });});watcher.start();
三、非同步邏輯順序化
非同步編程的代碼,回調越多,格式化的代碼形狀看起來就像格鬥遊戲中的角色發出的波。很顯然這是大家不願意看到的。
那麼如何讓非同步任務能夠順序執行呢?程式流程式控制制被分為了兩類: 串列和並行。
串列就是任務一個接著一個的執行,執行完前一個才能執行接下來的一個。
序列化流程式控制制的本質在於如何將多個非同步任務按照預期的順序放入一個數組隊列中。這樣當一個任務執行結束後會從隊列中取出下一個任務依次執行。
並行就是任務不需要一個接著一個來執行,而是可以交叉執行,使得任務看起來就像是同時在執行一樣。
var fs = require("fs");var path = require("path");// 已經完成的任務數var completedTasks = 0;// 待完成的任務數組var tasks = [];// 所有單詞的統計結果var wordCounts = {};// 讀取的目錄var filesDir = ‘./text‘;function checkIfComplete(){ completedTasks++; if(completedTasks == tasks.length){ for(var word in wordCounts){ console.log("word = " + word + " ; count = " + wordCounts[word]); } }}function countWordsInText(text){ var words = text.toString().toLowerCase().split(/\W+/);//.sort(); console.log(words); for(var i in words){ var word = words[i]; if(word){ wordCounts[word] = (wordCounts[word])? (wordCounts[word] + 1) : 1; } }}fs.readdir(filesDir, function(err, fileList){ if(err){ throw err; }else{ for(var index in fileList){ console.log(path.join(filesDir, fileList[index])); var task = (function(file){ return function(){ fs.readFile(file, function(err, text){ if(err){ throw err; }else{ countWordsInText(text); checkIfComplete(); } }); }; })(path.join(filesDir, fileList[index])); tasks.push(task); } for(var i in tasks){ tasks[i](); } }});
[NodeJS]Node非同步編程基礎