node 網(wǎng)絡(luò)(一)

TCP服務(wù)

  • 傳輸控制協(xié)議,網(wǎng)絡(luò)傳輸層
  • OSI模型: 物理層 數(shù)據(jù)鏈路層 網(wǎng)絡(luò)層 傳輸層 會(huì)話層 表示層 應(yīng)用層

傳輸之前先要三次握手形成會(huì)話,一個(gè)套接字socket只用于一個(gè)服務(wù)

創(chuàng)建TCP服務(wù)

 var net = require('net');
 
//使用net.createServer(listener)創(chuàng)建,**listener是連接事件connection的偵聽器**
var server = net.createServer(function(socket) {
    socket.on('data', function(data) {
        socket.write("hello");
    });
    socket.on('end', function() {
        console.log('斷開');
    });
    socket.write("hello111:\n");
});
server.listen(8124, function() {
    console.log('server bound');
}); 
//或者
var server = net.createServer();

server.on('connection', function(socket) { //新的連接
});
server.listen(8124);

//可以使用$ telnet 127.0.0.1 8124,或者使用net模塊構(gòu)建客戶端
var net = require('net');
var client = net.connect({  //
    port: 8124
}, function() { //'connect' listener
    console.log('client connected');
    client.write('world!\r\n');
});
client.on('data', function(data) {
    console.log(data.toString());
    client.end();
});
client.on('end', function() {
    console.log('client disconnected');
});

也可以對(duì)Domain Socket監(jiān)聽 server.listen('/tmp/echo.sock');
使用 $ nc -U /tmp/echo.sock 測(cè)試

服務(wù)器事件

使用net.createServer()創(chuàng)建的服務(wù)器是個(gè)EventEmitter實(shí)例

  1. listening 在調(diào)用server.lisren()綁定端口或者Domain Socket后觸發(fā)server.listen(port,listeningListener)
  2. connection 每個(gè)客戶端套接字連接到服務(wù)端時(shí)觸發(fā) net.createServer(),最后一個(gè)參數(shù)傳遞
  3. close 當(dāng)服務(wù)器關(guān)閉時(shí)觸發(fā) 在調(diào)用server.close()后,服務(wù)器會(huì)停止接受新的套接字,但當(dāng)前的連接不會(huì)斷,直到所有連接都斷后觸發(fā)
  4. error 服務(wù)器發(fā)生異常時(shí)觸發(fā),如果不幀聽error事件,會(huì)拋出異常

連接事件

服務(wù)器可以同時(shí)與多個(gè)客戶端保持連接,每個(gè)連接都是可讀寫的stream對(duì)象,用于服務(wù)器和客戶端通信

  1. data 當(dāng)一端調(diào)用write()發(fā)送數(shù)據(jù)時(shí),另一端觸發(fā)data事件
  2. end 任意一端發(fā)送了FIN數(shù)據(jù)時(shí)觸發(fā)
  3. connect 用于客戶端,當(dāng)套接字與服務(wù)端連接成功時(shí)觸發(fā)
  4. error 發(fā)生異常
  5. close 套接字完全關(guān)閉時(shí)觸發(fā)
  6. timeout 連接在一定時(shí)間內(nèi)部活躍,觸發(fā)
//使用pipe管道 管道提供了一個(gè)輸出流到輸入流的機(jī)制。通常我們用于從一個(gè)流中獲取數(shù)據(jù)并將數(shù)據(jù)傳遞到另外一個(gè)流中。

var net = require('net');
var server = net.createServer(function(socket) {
    socket.write('Echo server\r\n');
    socket.pipe(socket);//
});
server.listen(1337, '127.0.0.1');

Nagle算法:將tcp中的小數(shù)據(jù)包緩存合并到一定數(shù)量或時(shí)間后發(fā)出,避免浪費(fèi)網(wǎng)絡(luò)資源,但數(shù)據(jù)可能被延遲。tcp默認(rèn)啟動(dòng)Nagle算法,可以調(diào)用socket.setNoDelay(true)關(guān)閉,關(guān)閉后一端調(diào)用write(),另一端可能將多個(gè)小數(shù)據(jù)包合并后觸發(fā)一次data

UDP服務(wù)

  • 用戶數(shù)據(jù)包協(xié)議,網(wǎng)絡(luò)傳輸層
  • 一個(gè)套接字可以和多個(gè)UDP服務(wù)通信,無須連接,資源消耗低,處理快速靈活
  • 可能丟包,應(yīng)用在低丟包不影響的的場(chǎng)景,視頻、DNS

創(chuàng)建UDP套接字

UDP套接字可以做客戶端也可做服務(wù)端

  • 作為服務(wù)端 調(diào)用dgram.bind(port,[address])方法對(duì)網(wǎng)卡和端口綁定
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on("message", function(msg, rinfo) {
    console.log("server got: " + msg + " from " +
        rinfo.address + ":" + rinfo.port);
});
server.on("listening", function() {
    var address = server.address();
    console.log("server listening " + address.address + ":" + address.port);
});
server.bind(41234);//綁定完成后觸發(fā)listening事件
  • 作為客戶端:使用dgram.send(buf, offset, length, port, address, [callback])發(fā)送消息到網(wǎng)絡(luò);參數(shù)對(duì)應(yīng)含義,buffer,buffer偏移,buffer長(zhǎng)度,目標(biāo)端口,目標(biāo)地址,發(fā)送完成后的回調(diào)。它可以隨意發(fā)送數(shù)據(jù)到網(wǎng)絡(luò),tcp需要重新通過套接字構(gòu)建新連接
var dgram = require('dgram');
var message = new Buffer("Node.js");
var client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 41234, "localhost", function(err, bytes) {
    client.close();
});

UPD套接字事件

  • UDP是個(gè)EventEmitter實(shí)例使用更簡(jiǎn)單,TCP是個(gè)Stream實(shí)例
  1. message UDP套接字幀聽網(wǎng)卡端口后,接受數(shù)據(jù)時(shí)觸發(fā),觸發(fā)攜帶的數(shù)據(jù)為Buffer對(duì)象和遠(yuǎn)程地址
  2. listening UDP開始幀聽時(shí)觸發(fā)
  3. close 調(diào)用close()時(shí)觸發(fā),并不再觸發(fā)message事件
  4. error 異常時(shí)觸發(fā),不幀聽則直接拋出,進(jìn)程退出

構(gòu)建HTTP服務(wù)

  • 超文本傳輸協(xié)議 HyperText Transfer Protocol
  • 構(gòu)建在TCP之上 屬于應(yīng)用層協(xié)議 B/S模式,目前最知名的標(biāo)準(zhǔn)RFC 2616

通常的http通信的信息分三部分,TCP的3次握手,客戶端向服務(wù)器發(fā)送請(qǐng)求報(bào)文,服務(wù)器完成處理后向客戶端發(fā)送響應(yīng)內(nèi)容。
瀏覽器其實(shí)是http的代理,將用戶的行為轉(zhuǎn)化為http請(qǐng)求發(fā)送給服務(wù)端,服務(wù)端處理請(qǐng)求然后發(fā)送響應(yīng)報(bào)文給代理,代理解析報(bào)文再展示給用戶。http服務(wù)只做處理http請(qǐng)求和發(fā)送http響應(yīng)

http模塊

  • 繼承自TCP服務(wù)(net模塊)
  • 能夠與多個(gè)客戶端保持連接,因?yàn)椴捎檬录?qū)動(dòng),不用給每個(gè)連接創(chuàng)建額外的線程、進(jìn)程,內(nèi)存占用低,能高并發(fā)
  • tcp以connection為單位服務(wù),http以request為單位服務(wù)

http模塊將tcp連接的讀操作封裝為ServerRequest對(duì)象,報(bào)文頭部使用http_parser解析然后放在req.headers上傳遞給業(yè)務(wù)邏輯。報(bào)文體部分為只讀流對(duì)象,需要在數(shù)據(jù)流結(jié)束后轉(zhuǎn)字符串

function(req, res) {
    // console.log(req.headers);
    var buffers = [];
    req.on('data', function(trunk) {
        buffers.push(trunk);
    }).on('end', function() {
        var buffer = Buffer.concat(buffers); // TODO
        res.end('Hello world');
    });
}
//express中使用bodyParse,會(huì)自動(dòng)解析,但要求請(qǐng)求頭添加Content-Type=application/json

封裝對(duì)底層連接的寫操作為ServerResponse對(duì)象
影響響應(yīng)報(bào)文頭api為res.setHeader()res.writeHead(),可以調(diào)用res.setHeader()進(jìn)行多次設(shè)置,只要調(diào)用writeHead后,報(bào)頭才會(huì)寫入連接

res.writeHead(200, {'Content-Type': 'text/plain'});

設(shè)置報(bào)文體API為write(),end();end()會(huì)先調(diào)用write()發(fā)送數(shù)據(jù),再發(fā)信號(hào)告知響應(yīng)結(jié)束。

一旦開始了數(shù)據(jù)發(fā)送,writeHead()和setHeader()將不再生效
結(jié)束時(shí)要調(diào)用res.end(),否則客戶端將一直處于等待狀態(tài),無論異常與否

http服務(wù)端事件

http服務(wù)器是個(gè)EventEmitter實(shí)例

  • connection 客戶端與服務(wù)器建立底層的tcp連接時(shí)觸發(fā)。連接開啟keep-alive,可以在多次請(qǐng)求響應(yīng)之間使用
  • request 當(dāng)請(qǐng)求數(shù)據(jù)發(fā)送到服務(wù)端,在解析出http請(qǐng)求頭后觸發(fā)該事件
  • close 調(diào)用server.close()方法停止接受新連接,當(dāng)已有的連接也都斷開后觸發(fā),可以給server.close()傳個(gè)回調(diào)來快速注冊(cè)該事件
  • checkContinue 某些客戶端在發(fā)送大數(shù)據(jù)時(shí),會(huì)先發(fā)一個(gè)頭部帶Expect: 100-continue的請(qǐng)求,服務(wù)器接收后觸發(fā)checkContinue。如果不監(jiān)聽,就自動(dòng)回復(fù)100 Continue。如果不接收就響應(yīng)400 Bad Request。注意,在客戶端收到100 Continue后重新發(fā)起請(qǐng)求才會(huì)觸發(fā)request
  • connect 客戶端發(fā)起connect請(qǐng)求時(shí)觸發(fā),通常在http代理時(shí)才會(huì)發(fā)起connect,如果不監(jiān)聽,發(fā)起該請(qǐng)求的連接會(huì)關(guān)閉
  • upgrade 客戶端要升級(jí)連接協(xié)議時(shí)會(huì)在請(qǐng)求頭部帶上upgrade,服務(wù)端接收后觸發(fā),如果不監(jiān)聽,發(fā)起該請(qǐng)求的連接會(huì)關(guān)閉
  • clientError 連接的客戶端觸發(fā)error事件,會(huì)傳遞到服務(wù)器端并觸發(fā)

http客戶端

  • http.request(options, connect) 構(gòu)建http客戶端

var options = {
    hostname: '127.0.0.1',
    port: 1334,
    path: '/',
    method: 'GET',
    localAddress //建立連接的本地網(wǎng)卡
    socketPath //Domain套接字路徑
    headers //請(qǐng)求頭對(duì)象
    auth //Basic認(rèn)證 將被計(jì)算成請(qǐng)求頭的Authorization部分
};
// 用request 包請(qǐng)求 不用自己來處理on end 
var req = http.request(options, function(res) {
    console.log('STATUS: ' + res.statusCode);
    console.log('HEADERS: ' + JSON.stringify(res.headers));
    res.setEncoding('utf8');
    res.on('data', function(chunk) {
        console.log(chunk);
    });
});

req.end();

報(bào)文體的內(nèi)容通過請(qǐng)求對(duì)象的write()寫入,end()告知報(bào)文結(jié)束
客戶端clientRequest(req)解析完響應(yīng)頭就觸發(fā)response,并傳遞ClientResponse以供操作,之后報(bào)文體以只讀流提供)

http代理
  • 為了重用tcp連接(在keeplive時(shí),一個(gè)tcp連接可以多次用于請(qǐng)求)
  • http里包含一個(gè)客戶端代理對(duì)象http.globalAgent,它管理每個(gè)服務(wù)端(host+port)創(chuàng)建的連接并創(chuàng)建一個(gè)連接池,默認(rèn)每個(gè)服務(wù)器端5個(gè)連接

在通過ClientRequest調(diào)用http請(qǐng)求時(shí)會(huì)走代理??梢栽趏ptions中傳遞agentX修改連接限制

var agent = new http.Agent({
    maxSockets: 10 
});
var options = {
    hostname: '127.0.0.1',
    port: 1334,
    path: '/',
    method: 'GET',
    agent: agent //設(shè)置為false就不受連接池限制
};

http客戶端事件
  • response 客戶端得到響應(yīng)后觸發(fā)
  • socket 當(dāng)連接池中建立的連接分配給當(dāng)前的對(duì)象時(shí)觸發(fā)
  • upgrade 客戶端向服務(wù)端發(fā)起upgrade 服務(wù)端響應(yīng)101 Switching Protocols時(shí)觸發(fā)
  • continue 客戶端想發(fā)大數(shù)據(jù),頭部帶Expect: 100-continue,服務(wù)端同意并響應(yīng)100 continue時(shí)觸發(fā)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容