原文在個人博客,轉載請註明出處,謝謝javascript
在構建網絡通訊服務方面,相比於其餘老牌後端語言,Node.js 一樣可以勝任(也許更勝一籌),而且有本身獨特的處理方式。node是一個面向網絡而生的平臺,它的事件驅動、非阻塞、單線程使node應用程序具備低內存、高併發、伸縮性強的優良特性,適合在分佈式網絡大展身手。Node底層實現了傳輸層TCP/UDP、應用層HTTP/HTTPS的功能並封裝成貼合網絡的API,而且能夠本身建立服務器而不依賴三方服務,使用起來很是方便、簡單、靈活。對於網絡編程,node提供了net、dgram、http、https 4個模塊,分別用於處理TCP、UDP、HTTP、HTTPS。本文將介紹這些模塊並利用這些模塊提供的API構建簡單的網絡服務。html
其實不管什麼語言、什麼平臺,實現網絡編程都須要遵循網絡標準規範,只不過具體實現或者提供的API不一樣而已。所以,在探討node網絡編程以前,咱們須要瞭解用於網絡通訊的網絡協議(推薦閱讀《圖解HTTP》)。理解了網絡通訊的規範和機制,再熟悉一下API就能夠了。java
TCP(Transmission Control Protocol)傳輸控制協議是面向鏈接的協議,也就是必須創建鏈接才能發送數據。TCP在傳輸以前須要與服務器端進行3次握手造成會話(SYN是同步信號,ACK是確認信號):node
TCP傳送數據比較可靠,若是丟失數據會重傳,而且會對傳送數據進行排序。適用於重要、有序數據的傳送。git
UDP(User Datagram Protocol)用戶數據報協議是無鏈接協議,不面向鏈接,面向事務,建立過程相對簡單,佔用內存底,處理快速且靈活,發送數據不須要與另外一端創建鏈接,且不分客戶端、服務器端,在一端便可以發送數據也能夠接收數據。傳送的數據是無序的,網絡中斷會致使丟包。UDP的簡單不可靠特性適用於丟失一部分數據不會形成太大影響的場景,如音視頻數據傳送等。web
socketexpress
網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket(套接字),所以創建網絡通訊鏈接至少要一對端口號(socket)。socket本質是對TCP/IP協議棧的封裝,它提供了一個針對TCP或者UDP編程的接口,並非另外一種協議。經過socket,你可使用TCP/IP協議。編程
Socket的英文原義是「孔」或「插座」。做爲BSD UNIX的進程通訊機制,取後一種意思。一般也稱做"套接字",用於描述IP地址和端口,是一個通訊鏈的句柄,能夠用來實現不一樣虛擬機或不一樣計算機之間的通訊。在Internet上的主機通常運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不一樣的端口對應於不一樣的服務。Socket正如其英文原意那樣,像一個多孔插座。一臺主機猶如佈滿各類插座的房間,每一個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不一樣編號的插座,就能夠獲得不一樣的服務。json
——百度百科後端
建立TCP、UDP客戶端和服務端
在node中,net模塊提供建立基於TCP協議的網絡通訊的API,net.Socket
類提供了 TCP 或 UNIX Socket 的抽象,net.createServer
用於建立服務端,net.Socket
和net.connect
用於建立客戶端。
dgram模塊用於建立基於UDP協議的網絡服務,建立不分客戶端不分客戶端、服務器端,在一端使用dgram.createSocket
便可發送數據也能夠接收數據。
http是應用層協議,創建在TCP/IP之上,https則創建在TLS、SSL加密層協議之上,現代web基本都是http/https應用。TCP在創建鏈接要發送報文,http也是,http報文分爲請求報文和響應報文,報文格式以下:
HTTP/1.0 200 OK //起始行 Content-type:text/plain //頭部 Content-length:19 //頭部 Hi I'm a message! //主體
其中最重要的莫過於頭部報文了,它定義了請求或響應的行爲方式,是客戶端與服務器端交流的重要信息。http報文頭部的屬性多達幾十個,並且愈來愈多,保證客戶端與服務器端充分交流。
現代瀏覽器,集成了HTTP代理功能,用戶點擊連接等行爲會由瀏覽器生成HTTP請求報文發送給服務器端,收到響應後會解析報文,渲染報文中的主體內容。
node中的http
node中http模塊提供建立基於http協議的網絡通訊應用的接口,繼承於net模塊,採用事件驅動機制,能與多個客戶端保持鏈接,並不爲每一個鏈接開啓新的進程或線程,低內存、高併發,性能優良。
「http模塊將鏈接所用套接字(socket)的讀寫抽象爲ServerRequest和ServerResponse對象,它們分別對應請求和響應操做。在請求產生的過程當中,http模塊拿到鏈接中傳來的數據,調用二進制模塊http_parser進行解析,在解析完請求報文的報頭後,觸發request事件,調用用戶的業務邏輯。」
——樸靈《深刻淺出Node.js》
從上圖能夠看到,node中http模塊所作的事情就是繼承net模塊使用TCP協議、封裝http請求、產生http事件、響應事件綁定的處理程序。
http代理
node中http模塊提供了一個類http.Agent
,它稱爲http代理,它的做用就是爲了重用TCP鏈接,減小資源浪費。那麼http代理是如何重用TCP鏈接呢?http代理維護一個鏈接池,從客戶端發起的http請求都經由代理管理:
它爲一個給定的主機與端口維護着一個等待請求的隊列,且爲每一個請求重複使用一個單一的 socket(TCP) 鏈接直到隊列爲空,此時 socket(TCP 鏈接) 會被銷燬或被放入一個鏈接池中,在鏈接池中等待被有着相同主機與端口的請求再次使用。 是否被銷燬或被放入鏈接池取決於
keepAlive
選項 ——Node.js 8.9.0中文文檔
鏈接池如何管理鏈接還得取決於服務器:
即使鏈接池中的鏈接的 TCP Keep-Alive 是開啓的,服務器仍然可能關閉閒置的鏈接,在這種狀況下,這些鏈接會被移出鏈接池,且當一個新的 HTTP 請求被建立時再爲指定的主機與端口建立一個新的鏈接。 服務器也可能拒絕容許同一鏈接上有多個請求,在這種狀況下,鏈接會爲每一個請求從新建立,且不能被放入鏈接池。
Agent
仍然會建立請求到服務器,但每一個請求會出如今一個新的鏈接。但一個鏈接被客戶端或服務器關閉時,它會被移出鏈接池。 鏈接池中任何未被使用的 socket 會被釋放,從而使 Node.js 進程在沒有請求時不用保持運行。
——Node.js 8.9.0中文文檔
http模塊除了提供代理類,還提供了:
net.Server
,並添加了一些事件建立http服務器
// node建立服務器很是簡單,不須要任何三方代理 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}) res.end('Hello World\n'); }).listen(8880, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8880/')
建立http請求
使用http.request
便可發送請求。
一問一答是HTTP協議的特色,然而服務器主動向客戶端推送數據的場景也是常見的、被須要的。在WebSocket出現以前,實現客戶端和服務器端雙工通訊通常只能經過多開幾個HTTP鏈接、以輪詢方式來實現。因爲HTTP一問一答的特色不適合這種場景,就算HTTP1.1新增的Keep-Alive也不能很好的解決這種問題,因而WebSocket協議就出現了。
WebSocket協議可讓客戶端與服務器端實現雙向通訊,服務端能夠主動發送數據到客戶端。創建WebSocket協議鏈接時,客戶端會發送一條HTTP請求,請求服務器端切換協議爲WebSocket,服務器端若是支持WebSocket協議,就會返回一條HTTP響應表示正在切換WebSocket協議並切換。以後就能夠互相發送數據了。
使用WebSocket協議構建應用有如下優勢:
目前大多數瀏覽器已經實現WebSocket,能夠直接使用:
var socket = new WebSocket('ws://localhost:3000/') // 路徑中的協議改成ws(WebSocket) socket.onopen = function () { // 鏈接打開要作的事 }; socket.onmessage = function (event) { // 接收到服務端的信息(event.data) };
調用WebSocket後瀏覽器會發送一個HTTP請求,請求報文以下:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket // 請求協議升級爲websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== //校驗值 Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
在Node原生模塊中沒有支持WebSocket協議鏈接功能的模塊,但Node有不少三方模塊來幫助作這件事,經常使用的ws模塊方法以下:
// 導入WebSocket模塊: const WebSocket = require('ws'); // 引用Server類: const WebSocketServer = WebSocket.Server; // 實例化: const wss = new WebSocketServer({ // 在本地3000端口打開一個WebSocket Server port: 3000 }); wss.on('connection', function (ws) { console.log(`[SERVER] connection()`); ws.on('message', function (message) { console.log(`[SERVER] Received: ${message}`); ws.send(`ECHO: ${message}`, (err) => { if (err) { console.log(`[SERVER] error: ${err}`); } }); }) });
服務器返回的響應報文以下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 返回通過計算得出的校驗值 Sec-WebSocket-Protocol: chat
Node網絡模塊中提供的API較爲底層,有時在構建網絡應用程序並不須要關心底層實現,這時就能夠藉助三方框架封裝好的API來幫助咱們,經常使用的框架包括express、koa、connect等。