在用 Node.js 起服務以前,咱們應該知道這些

網絡分層

瞭解計算機網絡的同窗都知道 OSI 七層網絡模型TCP/IP 模型。OSI 七層模型是理論上的網絡通訊模型,而 TCP/IP 是現實中的網絡通訊概念模型。它們之間的對比關係參考下圖。html

image

本文主旨不在於解釋網絡通訊模型,所以此處略去各層的介紹,只關注應用層與傳輸層,即 TCP/IP 模型中的應用層和傳輸層。前端

咱們知道 HTTP 協議工做在應用層,屬於應用層協議,TCP/IP 協議工做在傳輸層,屬於傳輸層協議。在實際的網絡通訊中,一端的數據以報文的形式,從上到下通過層層封裝發送給另外一端,而接收數據的一端則從下到上層層解析數據報文,最後將數據傳遞給應用層。
從編程的角度來看,上面的數據報文的傳輸是如何實現的呢?或者說,應用層是如何與傳輸層通訊的呢?這就須要說到 socket 了。node

socket

所謂 socket,也就是俗稱的套接字,是計算機網絡中能夠發送和接收數據的一個端點,包含源 IP 地址和目的 IP 地址以及源端口號和目的端口號。
其實,socket 相似於插排上的插孔,當插頭插入插孔時,即可實現通電。對於 socket 來講,當鏈接創建以後,至關於一條通訊線路鏈接了通訊雙方的 socket,通訊雙方就能夠經過 socket 進行通訊了。git

Node.js 中的 socket 被封裝在了 net 模塊和 dgram 模塊中,分別對應與 TCP 協議UDP 協議。下面以 net 模塊爲例,說明 socket 的使用。github

先看服務端代碼:express

'use strict';

const net = require('net');

const HOST = '127.0.0.1';
const PORT = 9999;

net.createServer(function(socket) {
  console.log(`接收到遠端訪問: ${socket.remoteAddress}:${socket.remotePort}`);
  
  // 接收客戶端發送的數據
  socket.on('data', function(data) {
    console.log(`from client: ${data}`);
    
    // 將接收到的數據返回給客戶端
    socket.write(data);
  });

  // 爲這個socketet實例添加一個"close"事件處理函數
  socket.on('close', function(data) {
      console.log(`來自 ${socket.remoteAddress}:${socket.remotePort} 的鏈接已經關閉。`);
  });
}).listen(PORT, HOST);

console.log('Server listening on ' + HOST +':'+ PORT);

在服務端,經過 net.createServer 添加對 connection 事件的監聽,獲取 socket 實例,並在 socket 實例上作數據收發操做。編程

如下是客戶端代碼:api

'use strict';

const net = require('net');

const HOST = '127.0.0.1';
const PORT = 9999;

let talk = 0;

const client = new net.Socket();
client.connect(PORT, HOST, function() {
    console.log(`已經創建與 ${HOST}:${PORT} 的鏈接`);
    // 創建鏈接後當即向服務器發送數據,服務器將收到這些數據 
    client.write('hello, I am client!');
});

// 爲客戶端添加「data」事件處理函數,接收服務端返回的數據
client.on('data', function(data) {
    console.log(`from server: ${data}`);
    talk++;
    if (talk < 3) {
      // 接受到數據後,再次發送數據給客戶端
      client.write(`talk: ${talk}`);
    } else {
      client.end();
    }
});

// 爲客戶端添加「close」事件處理函數
client.on('close', function() {
    console.log('鏈接已經關閉!');
});

客戶端直接建立 socket 實例,收發數據。服務器

分別執行服務端和客戶端代碼,分別獲取命令行輸出以下。網絡

image

服務端每次會將接收到的客戶端數據回發給客戶端,客戶端接收到三次數據後,主動關閉 socket 鏈接。

整個 net 模塊,提供了兩個類: net.Servernet.Socketnet.Socket 負責通訊雙方之間的通訊,而 net.Server 負責創建本地服務器,提供了對本地服務器的管理,每接收到一個鏈接就會創建一個 socket 與對端實現通訊。

http

http 協議前端同窗應該都很熟悉。在 Node.js 中,http 協議由 http 模塊(底層基於 net 模塊)實現。
http 模塊提供了以下幾個類:

  • http.Agent
  • http.ClientRequest
  • http.Server
  • http.ServerResponse
  • http.IncomingMessage

咱們經過一段代碼,簡述每一個類的做用,以及內部實現原理。

先看服務端代碼:

'use strict';

const http = require('http');

const server = http.createServer((req, res) => {
  req.on('data', (data) => {
    console.log(`接收到遠端請求數據: ${data}`);
  });
  req.on('end', () => {
    res.write('請求已收到');
    res.statusCode = 200;
    res.end();
  });
});

server.listen(9999);
console.log('server listening on 9999');

服務端經過 http.createServer 建立服務器並監聽 request 事件,在事件處理函數中獲取 reqres作請求處理和響應。其中 req 就是 http.IncomingMessage,用於描述遠端過來的請求,而res 就是 http.ServerResponse,用於描述服務端作出的響應。

在上面的代碼中,咱們經過 req 監聽 data 事件獲取請求的數據,並經過 res 向客戶端發送響應數據。一讀一寫,不由讓人聯想到 reqres 是否就對應於 socket 的讀和寫呢?查了下 Node.js 的源代碼,http.IncomingMessagehttp.ServerResponse 的確是共用了一個 socket,而這個 socket 正是 net.Server 在接收到請求鏈接時所建立。

再看客戶端代碼:

'use strict';

const http = require('http');

const postData = 'post_data';
const options = {
  hostname: 'localhost',
  port: 9999,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
};
const req = http.request(options, (res) => {
  console.log('獲取到服務端響應');
  res.on('data', (data) => {
    console.log(`服務端響應: ${data}`);
  });
});
req.write(postData);
req.end();

在上面代碼中,經過 http.request 建立 http.ClientRequest,用於描述一個進行中的請求,在 http.ClientRequest 之上添加 response 事件監聽,能夠獲取到服務端的響應描述 http.IncomingMessage,從而獲取服務端響應數據。

除以上各種之外,http 模塊還提供了一個 http.Agent 類,該類用於管理客戶端的鏈接,必要時會重用 socket。此處不作詳述。

小結

在平常開發過程當中,咱們不多直接使用 Node.js 原生的類,更多的是使用成熟的框架,好比 expresskoa,阿里集團內部也有成熟的框架,好比 midwayegg。這些框架或多或少的都對 Node.js 提供的原生能力進行了封裝,咱們在使用這些框架的同時,也應該清楚框架背後 Node.js 的運行機制以及能提供的能力。

相關文章
相關標籤/搜索