Node.js之網路通訊模組淺析

weixin_34253539發表於2017-03-31

前言

想必我們在用Node.js用的最多的應該是建立http服務,所以對於每個Web開發工程師而言,Node.js的網路相關模組學習是必不可少。


Node.js的網路模組架構

在Node.js的模組裡面,與網路相關的模組有NetDNSHTTPTLS/SSLHTTPSUDP/Datagram,除此之外,還有v8底層相關的網路模組有tcp_wrap.ccudp_wrap.ccpipe_wrap.ccstream_wrap.cc等等,在Javascript層以及C++層之間通過process.binding進行橋接相互通訊。

Node.js的網路模組架構


Net模組

Net模組提供了一些用於底層的網路通訊介面,包括建立伺服器以及客戶端,其中HTTP模組也是基於Net模型的上層封裝,在Net模組裡面主要提供net.Server以及net.Socket

建立TCP服務端

建立一個TCP伺服器,可以通過使用建構函式new net.Server或者使用工廠方法net.createServer,這兩個方法都會返回一個net.Server類,可接收兩個可選引數。

var net = require('net');

var server = net.createServer(function(socket){

    socket
        .on('data',function(data){
            console.log('socket data',data.toString());
            socket.write( data.toString() );
        })
        .on('end',function(){
            console.log('socket end')
        })
        .on('error',function(error){
            console.log('socket error',error);
        });
});

server.listen(56200,function(){
    console.log('server run at ',server.address());
});

server.on('error',function(err){
    throw err;
});
// 執行後:server run at { address: '::', family: 'IPv6', port: 56200 }

在listen監聽的時候沒有指定埠的話會自動隨意監聽一個埠,建立完成一個TCP伺服器後,使用tenlent 0.0.0.0 56200,連結後可與伺服器進行資料通訊。通過createServer例項化一個服務後,服務會去監聽客戶端請求,與客戶端建立了連結之後會在回撥裡面丟擲建鏈的net.Socket物件。

建立TCP客戶端

建立一個TCP客戶端連結可以使用建構函式new net.Socket或者其工廠方法net.createConnection,建立成功後都會返回一個net.Socket例項。

var net = require('net');

var client = net.createConnection({port:56200,host:'localhost'});

client.on('connect',function(){
    console.log('client connect');
});

client.on('data',function(data){
    console.log('client data',toString());
});

client.on('error',function(error){
    throw error;
});

client.on('close',function(){
    console.log('client close');
});

Socket

socket是啥這裡就不做詳細的闡述了,下面主要了解下net.Socket這個構造體主要有提供一些什麼方法、監聽事件的使用。

相關事件

  • connect : 當客戶端與服務端成功建立連結之後觸發,如果連結不上伺服器直接丟擲error事件錯誤然後退出node程式

  • data : 當客戶端收到伺服器傳送過來的資料或者是客戶端傳送給伺服器的資料的時候觸發回撥。

  • end : 當另外一側傳送FIN包斷開的時候觸發,預設情況下面 (allowHalfOpen == false)socket會自我銷燬(如果寫入待處理佇列裡面還沒正式響應回包),但是我們可以設定allowHalfOpen引數為true,這樣可以繼續往該socket裡面寫資料,但是我們需要自己去呼叫 end 方法去消耗這個socket,不然可能會造成控制程式碼洩漏。

  • close : 連結斷開的時候觸發,但是如果在傳輸的過程中有錯誤的話這裡會在回撥函式裡面丟擲 error

  • timeout : 當socket超時空閒的時候觸發,如果要在佇列裡面銷燬需要手動去調close方法

  • lookup : 域名解析完成的時候觸發。

  • drain : 寫完快取的時候觸發,可使用在上傳大小限制中。

相關方法

  • write() : 服務端給客戶端傳送資料或者是客戶端給服務端傳送資料。

  • address() : 獲取服務繫結的socket的IP地址,返回物件有三個屬性,分別為埠、host以
    及IPvX版本。

  • end() : 半關閉socket,會傳送一個FIN包,伺服器仍然可能傳送一些資料,也可以這樣呼叫socket.end(data,encoding)。

  • pause() : 暫停讀取資料,可以用作對資料上傳限制。

  • resume() : 繼續資料讀取。

  • setEncoding() : 設定資料流的獲取格式。

  • setKeepAlive() : 允許/禁止keep-alive功能。

  • setNoDelay() : 禁止Nagele演算法,TCP連結預設使用Nagle演算法,它們在傳送之前資料會被快取。這是為true的話在每次socket.write()的時候會立即傳送資料,預設為true。

  • setTimeout() : 當一個空閒的socket在多少秒後不活躍會被接受到timeout事件,但是該socket不會停止銷燬,需要手動呼叫end()或者destroy()。表示禁止空閒超時。

相關屬性

  • bufferSize : 當前快取的等待被髮送的字串的數量。

  • bytesRead : 收到的位元組的數量。

  • bytesWritten : 傳送的位元組的數量

  • destroyed : 標識連結是否已經被破壞,一旦被破環,就不用使用該連結來傳輸資料。

  • localAddress : 遠端客戶端連結本地地址的host。如果我們監聽服務的host是0.0.0.0,而客戶端連結的是'192.168.1.1',最後的值是後者。

  • localPort : 本地的埠。

  • remoteAddress : 客戶端IP,如果socket已經是destryed的話,該值為undefined

  • remoteFamily : 客戶端是IPvX

回包異常處理

伺服器從客戶端接受到需要處理的資料後進入處理環節,再業務邏輯處理完成之前如果socket以外斷開的話,待伺服器再給客戶端回報的時候會直接響應error事件並報錯Error : This socket has benn ended by the other part,所以在回報之前服務端需要先判斷該socket是否被銷燬,如果沒有被銷燬則回包,如果已經斷開則銷燬:

var net = require('net');
var biz = require('./biz');
var server = net.createServer(function(socket){

    socket
        .on('data',function(data){
            biz.do(data)
                .then(function(){
                    if( !socket.destroyed ) {
                        socket.write( data.toString() );
                    } else {
                        // do some report
                        socket.destry();
                    }
                })
                .catch(function(){
                    !socket.destroyed && socket.end('server handler error');
                });
            
        })
        .on('end',function(){
            console.log('socket end')
        })
        .on('error',function(error){
            console.log('socket error',error);
        });
});

server.listen(56200,function(){
    console.log('server run at ',server.address());
});

server.on('error',function(err){
    throw err;
});

限制客戶端資料大小

對請求大小限制是服務安全裡面比不可少的一個環節,服務端不能無限大小的去接受客戶端傳送過來的所有資料,而限制大小就是第一道門檻。

var net = require('net');
var MAX_REQUEST_BYTES = 2 * 1024 * 1024;  // 2M
var server = net.createServer(function(socket){

    socket
        .on('data',function(data){
           
            if(data.bytesRead > MAX_REQUEST_BYTES) {
                socket.pause();
                socket.end('data is too big, forbidden');
                // do some report
            }
        })
        .on('end',function(){
            console.log('socket end')
        })
        .on('error',function(error){
            console.log('socket error',error);
        });
});

server.listen(56200,function(){
    console.log('server run at ',server.address());
});

server.on('error',function(err){
    throw err;
});

相關文章