天天看點

大熊君大話NodeJS之------Net子產品

一,開篇分析

從今天開始,我們來深入具體的子產品學習,這篇文章是這個系列(大熊君大話NodeJS)文章的第三篇,前兩篇主要是以理論為主,相信大家在前兩篇的學習中,

對NodeJS也有一個基本的認識,沒事!!!趁熱打鐵,讓我們繼續将NodeJS進行到底,好了廢話不多說,直接進入今天的主題 “Net子產品” ,那麼”Net“應該如何了解那?

它是做什麼用的那?(

Net

子產品可用于建立Socket伺服器或Socket用戶端。 NodeJS 的資料通信,最基礎的兩個子產品是 Net 和 Http,前者是基于 Tcp 的封裝,後者本質還是 Tcp 層,隻不過做了比較多的資料封裝,我們視為表現層)。

這裡參考一下NodeJS “http.js” 中的源碼:

大熊君大話NodeJS之------Net子產品

從圖中不難看出 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。

運作結果:

大熊君大話NodeJS之------Net子產品

三,案例引入

(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,有興趣可以完善一下那個聊天室的例子

        哈哈哈,本篇結束,未完待續,希望和大家多多交流夠溝通,共同進步。。。。。。呼呼呼……(*^__^*)            

繼續閱讀