一,開篇分析
從今天開始,我們來深入具體的模塊學習,這篇文章是這個系列(大熊君大話NodeJS)文章的第三篇,前兩篇主要是以理論為主,相信大家在前兩篇的學習中,
對NodeJS也有一個基本的認識,沒事!!!趁熱打鐵,讓我們繼續將NodeJS進行到底,好了廢話不多說,直接進入今天的主題 “Net模塊” ,那么”Net“應該如何理解那?
它是做什么用的那?(Net模塊可用於創建Socket服務器或Socket客戶端。 NodeJS 的數據通信,最基礎的兩個模塊是 Net 和 Http,前者是基於 Tcp 的封裝,后者本質還是 Tcp 層,只不過做了比較多的數據封裝,我們視為表現層)。
這里參考一下NodeJS “http.js” 中的源碼:

從圖中不難看出 HttpServer繼承了Net類,具有了相關的通信能力,做了比較多的數據封裝,我們視為更高級的表現層。
擴展知識(以下是“inherits”的源碼):
1 exports.inherits = function(ctor, superCtor) { 2 ctor.super_ = superCtor; 3 ctor.prototype = Object.create(superCtor.prototype, { 4 constructor: { 5 value: ctor, 6 enumerable: false, 7 writable: true, 8 configurable: true 9 } 10 }); 11 };
功能是實現繼承復用。
剛才做了一個簡要的概述,里面有一些常用的概念,這里做個簡短的概念普及介紹:
(1),TCP/IP------TPC/IP協議是傳輸層協議,主要解決數據如何在網絡中傳輸。
(2),Socket------socket則是對TCP/IP協議的封裝和應用(程序層面)。
(3),Http------HTTP是應用層協議,主要解決如何包裝數據。
(4),網絡七層模型------物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。
總結一下:Socket是對TCP/IP協議的封裝,Socket本身並不是協議,而是一個調用接口(API)。
從而形成了我們知道的一些最基本的函數接口,比如Create、Listen、Connect、Accept、Send、Read和Write等等。
TCP/IP只是一個協議棧,就像操作系統的運行機制一樣,必須要具體實現,同時還要提供對外的操作接口
實際上,傳輸層的TCP是基於網絡層的IP協議的,而應用層的HTTP協議又是基於傳輸層的TCP協議的,而Socket本身不算是協議,就像上面所說,它只是提供了一個針對TCP或者UDP編程的接口。
二,體驗一把
好了,概念我們也有了,來個例子:
1,建立server.js
1 var net = require('net') ; 2 var server = net.createServer(function(c) { // Connection監聽器 3 console.log("服務器已連接") ; 4 c.on("end", function() { 5 console.log("服務器已斷開") ; 6 }) ; 7 c.write("Hello,Bigbear !\r\n") ; 8 c.pipe(c) ; 9 }) ; 10 server.listen(8124, function() { // Listening監聽器 11 console.log("服務器已綁定") ; 12 }) ;
2,建立client.js
1 var net = require('net') ; 2 var client = net.connect({ 3 port: 8124 4 },function(){ // connect監聽器 5 console.log("客戶端已連接") ; 6 client.write('Hello,Baby !\r\n') ; 7 }); 8 client.on("data", function(data) { 9 console.log(data.toString()) ; 10 client.end() ; 11 }); 12 client.on("end", function(){ 13 console.log("客戶端斷開連接") ; 14 }) ;
分析一下:
服務端------net.createServer 創建一個 TCP 服務,這個服務綁定(server.listen)在 8124 這個端口上,創建 Server 后我們看到有一個回調函數,
在調用上面函數的時候傳入一個參數,這個參數也是函數,並且接受了 socket ,這個由其他方法構造的一個管道(pipe),他的作用就是用來數據交互的。
pipe 是需要 Client 跟 Server 打招呼才能建立的,如果此刻沒有客戶端訪問 Server,這個 socket 就不會存在了。
客戶端------net.connect 顧名思義,就是連接到服務端,第一個參數是對象,設置端口(port)為 8124,也就是我們服務器監聽的端口,由於沒有設置 host 參數,那默認就是 localhost (本地)。
在 Server 中,socket 是管道的一端,而在 client 中,client 本身就是管道的一端,如果是多個客戶端連接 Server,Server 會新建多個 socket,每個 socket 對應一個 client。
運行結果:

三,案例引入
(1),下面代碼僅僅是服務器向客戶端輸出一段文本,完成服務端到客戶端的單向通訊。
1 // Sever --> Client 的單向通訊 2 var net = require('net'); 3 4 var chatServer = net.createServer(); 5 6 chatServer.on('connection', function(client) { 7 client.write('Hi!\n'); // 服務端向客戶端輸出信息,使用 write() 方法 8 client.write('Bye!\n'); 9 10 client.end(); // 服務端結束該次會話 11 }); 12 13 chatServer.listen(9000);
Telnet測試一下:telnet 127.0.0.1:9000
執行 telnet 后,與服務點連接,反饋 Hi! Bye! 的字符,並立刻結束服務端程序終止連接。
如果我們要服務端接到到客戶端的信息?
可以監聽 server.data 事件並且不要中止連接(否則會立刻結束無法接受來自客戶端的消息)。
(2),監聽 server.data 事件並且不要中止連接(否則會立刻結束無法接受來自客戶端的消息)。
1 // 在前者的基礎上,實現 Client --> Sever 的通訊,如此一來便是雙向通訊 2 var net = require('net'); 3 var chatServer = net.createServer(), 4 clientList = []; 5 6 chatServer.on('connection', function(client) { 7 // JS 可以為對象自由添加屬性。這里我們添加一個 name 的自定義屬性,用於表示哪個客戶端(客戶端的地址+端口為依據) 8 client.name = client.remoteAddress + ':' + client.remotePort; 9 client.write('Hi ' + client.name + '!\n'); 10 clientList.push(client); 11 client.on('data', function(data) { 12 broadcast(data, client);// 接受來自客戶端的信息 13 }); 14 }); 15 function broadcast(message, client) { 16 for(var i=0;i<clientList.length;i+=1) { 17 if(client !== clientList[i]) { 18 clientList[i].write(client.name + " says " + message); 19 } 20 } 21 } 22 chatServer.listen(9000);
上面是不是一個完整功能的代碼呢?我們說還有一個問題沒有考慮進去:那就是一旦某個客戶端退出,卻仍保留在 clientList 里面,這明顯是一個空指針。
(3),處理clientList
1 chatServer.on('connection', function(client) { 2 client.name = client.remoteAddress + ':' + client.remotePort 3 client.write('Hi ' + client.name + '!\n'); 4 5 clientList.push(client) 6 7 client.on('data', function(data) { 8 broadcast(data, client) 9 }) 10 11 client.on('end', function() { 12 clientList.splice(clientList.indexOf(client), 1); // 刪除數組中的制定元素。 13 }) 14 })
Node TCP API 已經為我們提供了 end 事件,即客戶端中止與服務端連接的時候發生。
(4),優化broadcast
1 function broadcast(message, client) { 2 var cleanup = [] 3 for(var i=0;i<clientList.length;i+=1) { 4 if(client !== clientList[i]) { 5 6 if(clientList[i].writable) { // 先檢查 sockets 是否可寫 7 clientList[i].write(client.name + " says " + message) 8 } else { 9 cleanup.push(clientList[i]) // 如果不可寫,收集起來銷毀。銷毀之前要 Socket.destroy() 用 API 的方法銷毀。 10 clientList[i].destroy() 11 } 12 13 } 14 } //Remove dead Nodes out of write loop to avoid trashing loop index 15 for(i=0;i<cleanup.length;i+=1) { 16 clientList.splice(clientList.indexOf(cleanup[i]), 1) 17 } 18 }
注意的是一旦 “end” 沒有被觸發,會出現異常,所以才做優化工作。
(5),Net API 中還提供一個 error 事件,用於捕捉客戶端的異常
client.on('error', function(e) {
console.log(e);
});
四,總結一下
1,理解開篇的相關概念
2,認識Http與Net模塊之間的關系
3,結合本文的例子,查閱相關api去實踐
4,socket客戶端與服務器端之間的通信思想
5,有興趣可以完善一下那個聊天室的例子
哈哈哈,本篇結束,未完待續,希望和大家多多交流夠溝通,共同進步。。。。。。呼呼呼……(*^__^*)
