How Javascript works (Javascript工作原理) (五) 深入理解 WebSockets 和帶有 SSE 機制的HTTP/2 以及正確的使用姿勢

來源:互聯網
上載者:User

標籤:hello   不可   key   TE   均衡器   tom   develop   rom   over   

總結:

1.長串連機制——分清Websocket,http2,SSE:

HTTP/2 引進了 Server Push 技術用來讓伺服器主動向用戶端緩衝發送資料。然而,它並不允許直接向用戶端程式本身發送資料。服務端推送只能由瀏覽器處理而不能夠在程式碼中進行處理,意即程式碼沒有 API 可以用來擷取這些事件的通知。

通過SSE(Server Side Event)來實現服務端向用戶端的單向推送,SSE基於HTTP,是單向通訊。

WebSocket是在服務端和用戶端建立雙工通訊。基於TCP。

 

 

 

 

這是 JavaScript 工作原理的第五章。

現在,我們將會深入通訊協定的世界,繪製並討論它們的特點和內部構造。我們將會給出一份 WebSockets 和 HTTP/2 的快速比較 。在文末,我們將會分享如何正確地選擇網路通訊協定的一些見解。

 

簡介

現在,複雜的網頁程式擁有豐富的功能,這得多虧網頁的動態互動能力。而這並不令人感到驚訝-因為自互連網誕生,它經曆了一段相當長的時間。

起初,互連網並不是用來支援如此動態和複雜的網頁程式的。它本來設想是由大量的 HTML 頁面組成的,每個頁面連結到其它的頁面,這樣就形成了包含資訊的網頁的概念。一切都是極大地圍繞著所謂的 HTTP 要求/響應模式來建立的。用戶端載入一個網頁,直到使用者點擊頁面並導航到下一個網頁。

大約在 2005 年,引入了 AJAX,然後很多人開始探索用戶端和服務端雙向通訊的可能性。然而,所有的 HTTP 連結是由用戶端控制的,意即必須由使用者進行操作或者定期輪詢以從伺服器載入資料。

 

讓 HTTP 支援雙向通訊

支援伺服器主動向用戶端推送資料的技術已經出現了好一段時間了。比如 "Push" 和 "Comet" 技術。

長輪詢是服務端主動向用戶端發送資料的最常見的 hack 之一。通過長輪詢,用戶端開啟了一個到服務端的 HTTP 串連直到返迴響應資料。當服務端有新資料需要發送時,它會把新資料作為響應發送給用戶端。

讓我們看一下簡單的長輪詢程式碼片段:

(function poll(){   setInterval(function(){      $.ajax({         url: ‘https://api.example.com/endpoint‘,         success: function(data) {          // 處理 `data`          // ...          //遞迴調用下一個輪詢          poll();        },         dataType: ‘json‘      });  }, 10000);})();

這基本上是一個自執行函數,第一次會自動運行。它每隔 10 秒鐘非同步請求伺服器並且當每次發起對伺服器的非同步請求之後,會在回呼函數裡面再次調用 ajax 函數。

其它技術涉及到 Flash 和 XHR 多方請求以及所謂的 htmlfiles。

所有這些方案都有一個共同的問題:都帶有 HTTP 開銷,這樣就會使得它們無法滿足要求低延遲的程式。試想一下瀏覽器中的第一人稱射擊遊戲或者其它要求即時組件功能的線上遊戲。

WebSockets 的出現

WebSocket 規範定義了一個 API 用以在網頁瀏覽器和伺服器建立一個 "socket" 串連(TCP協議上)。通俗地講:在用戶端和伺服器保有一個持久的串連,兩邊可以在任意時間開始發送資料。

用戶端通過 WebSocket 握手的過程來建立 WebSocket 串連。在這一過程中,首先用戶端向伺服器發起一個常規的 HTTP 要求。請求中會包含一個 Upgrade 的要求標頭,通知伺服器用戶端想要建立一個 WebSocket 串連。

讓我們看下如何在用戶端建立 WebSocket 串連:

// 建立新的加密 WebSocket 串連var socket = new WebSocket(‘ws://websocket.example.com‘);

WebSocket 地址使用了 ws 方案。wss 是一個等同於 HTTPS 的安全的 WebSocket 串連。

該方案是開啟到 websocket.example.com 的 WebSocket 串連的開始。

下面是初始化要求標頭的簡化例子。

GET ws://websocket.example.com/ HTTP/1.1Origin: http://example.comConnection: UpgradeHost: websocket.example.comUpgrade: websocket

如果伺服器支援 WebSocket 通訊協定,它將會同意升級請求,然後通過在響應裡面返回 Upgrade 頭來進行通訊。

讓我們看下 Node.js 的實現:

// 我們將會使用 https://github.com/theturtle32/WebSocket-Node 來實現 WebSocketvar WebSocketServer = require(‘websocket‘).server;var http = require(‘http‘);var server = http.createServer(function(request, response) {  // 處理 HTTP 要求});server.listen(1337, function() { });// 建立伺服器wsServer = new WebSocketServer({  httpServer: server});// WebSocket 伺服器wsServer.on(‘request‘, function(request) {  var connection = request.accept(null, request.origin);  // 這是最重要的回調,在這裡處理所有使用者返回的資訊  connection.on(‘message‘, function(message) {      // 處理 WebSocket 資訊  });  connection.on(‘close‘, function(connection) {    // 關閉串連  });});

串連建立之後,伺服器使用升級來作為回複:

HTTP/1.1 101 Switching ProtocolsDate: Wed, 25 Oct 2017 10:07:34 GMTConnection: UpgradeUpgrade: WebSocket

一旦串連建立,會觸發用戶端 WebSocket 執行個體的 open 事件。

var socket = new WebSocket(‘ws://websocket.example.com‘);// WebSocket 串連開啟的時候,列印出 WebSocket 已串連的資訊socket.onopen = function(event) {  console.log(‘WebSocket is connected.‘);};

現在,握手結束了,最初的 HTTP 串連被替換為 WebSocket 串連,該串連底層使用同樣的 TCP/IP 串連。現在兩邊都可以開始發送資料了。

通過 WebSocket,你可以隨意發送資料而不用擔心傳統 HTTP 要求所帶來的相關開銷。資料是以訊息的形式通過 WebSocket 進行傳輸的,每條資訊是由包含你所傳輸的資料(有效載荷)的一個或多個幀所組成的。為了保證當訊息到達用戶端的時候被正確地重新組裝出來,每一幀都會前置關於有效載荷的 4-12 位元組的資料。使用這種基於幀的資訊系統可以協助減少非有效載荷資料的傳輸,從而顯著地減少資訊延遲。

**注意:**這裡需要注意的是只有當所有的訊息幀都被接收到而且原始的資訊有效載荷被重新組裝的時候,用戶端才會接收到新訊息的通知。

WebSocket 地址

前面我們簡要地談到 WebSockets 引進了一個新的地址協議。實際上,WebSocket 引進了兩種新協議:ws:// 和 wss://

URL 地址含有指定方案的文法。WebSocket 地址特別之處在於,它不支援錨(sample_anchor)。

WebSocket 和 HTTP 風格的地址使用相同的地址規則。ws 是未加密且預設是 80 連接埠,而 wss 要求 TSL 加密且預設 443 連接埠。

幀協議

讓我們深入瞭解下幀協議。這是 RFC 提供的:

0                   1                   2                   3      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1     +-+-+-+-+-------+-+-------------+-------------------------------+     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |     |N|V|V|V|       |S|             |   (if payload len==126/127)   |     | |1|2|3|       |K|             |                               |     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +     |     Extended payload length continued, if payload len == 127  |     + - - - - - - - - - - - - - - - +-------------------------------+     |                               |Masking-key, if MASK set to 1  |     +-------------------------------+-------------------------------+     | Masking-key (continued)       |          Payload Data         |     +-------------------------------- - - - - - - - - - - - - - - - +     :                     Payload Data continued ...                :     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +     |                     Payload Data continued ...                |     +---------------------------------------------------------------+

由於 WebSocket 版本是由 RFC 所規定的,所以每個包前面只有一個頭部資訊。然而,這個頭部資訊相當的複雜。這是其組成模組的說明:

  • fin(1 位):指示是否是組成資訊的最後一幀。大多數時候,資訊只有一幀所以該位通常有值。測試表明Firefox的第二幀資料在 32K 之後。

  • rsv1rsv2rsv3(每個一位):必須是 0 除非使用協商擴充來定義非 0 值的含義。如果收到一個非 0 值且沒有協商擴充來定義非零值的含義,接收端會中斷串連。

  • opcode(4 位):表示第幾幀。目前可用的值:

    0x00:該幀接續前面一幀的有效載荷。

    0x01:該幀包含文本資料。

    0x02:該幀包含位元據。

    0x08:該幀中斷串連。

    0x09:該幀是一個 ping。

    0x0a:該幀是一個pong。

    (正如你所看到的,有相當一部分值未被使用;它們是保留以備未來使用的)。

  • mask(1 位):指示該串連是否被遮罩。正其所表示的意義,每一條從用戶端發往伺服器的資訊都必須被遮罩,然後如果資訊未遮罩,根據規範會中斷該串連。

  • payload_len(7 位):有效載荷的長度。WebSocket 幀有以下幾類長度:

    0-125 表示有效載荷的長度。126 意味著接下來兩個位元組表示有效載荷長度,127 意味著接下來的 8 個位元組表示有效載荷長度。所以有效載荷的長度大概有 7 位,16 位和 64 位元這三類。

  • masking-key (32 位):所有從用戶端發往伺服器的幀都由幀內的一個 32 位值所遮罩。

  • payload:一般情況下都會被遮罩的實際資料。其長度取決於 payload_len 的長度。

為什麼 WebSocket 是基於幀而不是基於流的呢?我和你一樣一臉懵逼,我也想多學點,如果你有任何想法,歡迎在下面的評論區添加評論和資源。另外,HackerNews 上面有關於這方面的討論。

幀資料

正如之前提到的,資料可以被拆分為多個幀。第一幀所傳輸的資料裡面含有一個作業碼表示資料的傳輸順序。這是必須的,因為當規範完成的時候,JavaScript 並不能很好地支援位元據的傳輸。0x01 表示 utf-8 編碼的文本資料,0x02 表示位元據。大多數人在傳輸 JSON 資料的時候都會選擇文本作業碼。當你傳輸位元據的時候,它會以瀏覽器指定的 Blob來表示。

通過 WebSocket 來傳輸資料的 API 是非常簡單的:

var socket = new WebSocket(‘ws://websocket.example.com‘);socket.onopen = function(event) {  socket.send(‘Some message‘); // 向伺服器發送資料};

當 WebSocket 正在接收資料的時候(用戶端),會觸發 message 事件。該事件會帶有一個 data 屬性,裡麵包含了訊息的內容。

// 處理伺服器返回的訊息socket.onmessage = function(event) {  var message = event.data;  console.log(message);};

你可以很容易地利用 Chrome 開發人員工具的網路選項卡來檢查 WebSocket 串連中的每一幀的資料。

 

資料分區

有效載荷資料可以被分成多個獨立的幀。接收端會緩衝這些幀直到 fin 位有值。所以你可以把字串『Hello World』拆分為 11 個包,每個包由 6(頭長度) + 1 位元組組成。資料分區不能用來控制包。然而,規範想要你有能力去處理交錯控制幀。這是為了預防 TCP 包無序到達用戶端。

串連幀的大概邏輯如下:

  • 接收第一幀
  • 記住作業碼
  • 串連幀有效載荷直到 fin 位有值
  • 斷言每個包的作業碼都為 0

資料分區的主要目的在於允許開始時傳輸不明大小的資訊。通過資料分區,伺服器可能需要設定一個合理的緩衝區大小,然後當緩衝區滿,返回一個資料分區。資料分區的第二個用途即多工,邏輯通道上的大量資料佔據整個輸出通道是不合理的,所以利用多工技術把資訊拆分成更小的資料分區以更好地共用輸出通道。

心跳包

握手之後的任意時刻,用戶端和伺服器可以隨意地 ping 對方。當接收到 ping 的時候,接收方必須儘快回複一個 pong。此即心跳包。你可以用它來確保用戶端是否保持串連。

ping 或者 pong 雖然只是一個普通幀,但卻是一個控制幀。Ping 包含 0x9 作業碼,而 Pong 包含 0xA 作業碼。當你接收到 ping 的時候,返回一個和 ping 攜帶同樣有效載荷資料的 pong(ping 和 pong 最大有效載荷長度都為 125)。你可能接收到一個 pong 而不用發送一個 ping。忽略它如果有發生這樣的情況。

心跳包非常有用。利用服務(比如負載平衡器)來中斷閒置串連。另外,接收端不可能知道服務端是否已經中斷串連。只有在發送下一幀的時候,你才會意識到發生了錯誤。

錯誤處理

你可以通過監聽 error 事件來處理錯誤。

像這樣:

var socket = new WebSocket(‘ws://websocket.example.com‘);// 處理錯誤socket.onerror = function(error) {  console.log(‘WebSocket Error: ‘ + error);};

關閉串連

用戶端或伺服器可以發送一個包含 0x8 作業碼資料的控制幀來關閉串連。當接收到控制幀的時候,另一個節點會返回一個關閉幀。之後第一個節點會關閉串連。關閉串連之後,之後接收的任何資料都會被遺棄。

這是初始化關閉用戶端的 WebSocket 串連的代碼:

// 如果串連開啟著則關閉if (socket.readyState === WebSocket.OPEN) {    socket.close();}

同樣地,為了在完成關閉串連後運行任意的清理工作,你可以為 close 事件添加事件監聽函數:

// 運行必要的清理工作socket.onclose = function(event) {  console.log(‘Disconnected from WebSocket.‘);};

伺服器不得不監聽 close 事件以便在需要的時候處理:

connection.on(‘close‘, function(reasonCode, description) {    // 關閉串連});
WebSockets 和 HTTP/2 對比

雖然 HTTP/2 提供了很多的功能,但是它並不能完全取代當前的 push/streaming 技術。

關於 HTTP/2 需要注意的最重要的事即它並不能完全取代 HTTP。詞彙,狀態代碼以及大部分的頭部資訊都會保持和現在一樣。HTTP/2 只是提升了線路上的資料轉送效率。

現在,如果我們對比 WebSocket 和 HTTP/2,將會發現很多類似的地方:

 

正如以上所顯示的那樣,HTTP/2 引進了 Server Push 技術用來讓伺服器主動向用戶端緩衝發送資料。然而,它並不允許直接向用戶端程式本身發送資料。服務端推送只能由瀏覽器處理而不能夠在程式碼中進行處理,意即程式碼沒有 API 可以用來擷取這些事件的通知。

這時候服務端推送事件(SSE)就派上用場了。SSE 是這樣的機制一旦用戶端-伺服器串連建立,它允許伺服器非同步推送資料給用戶端。之後,每當伺服器產生新資料的時候,就推送資料給用戶端。這可以看成是單向的發布-訂閱模型。它也提供了一個被稱為 EventSource 的 標準 JavaScript 用戶端 API,該 API 作為 W3C 組織發布的 HTML5 標準的一部分已經在大多數的現代瀏覽器中實現。請注意不支援原生 EventSource API 的瀏覽器可以通過墊片實現。

由於 SSE 是基於 HTTP 的,所以它天然相容於 HTTP/2 並且可以混合使用以利用各自的優勢: HTTP/2 處理一個基於多工流的高效傳輸層而 SSE 為程式提供了 API 用來支援服務端推送。

為了完全理解流和多工技術,先讓我們來瞭解一下 IETF 的定義:『流』即是在一個 HTTP/2 串連中,在用戶端和服務端間進行交換傳輸的一個獨立的雙向幀序列。它的主要特點之一即單個的 HTTP/2 串連可以包含多個並發開啟的流,在每一終端交錯傳輸來自多個流的幀。

必須記住的是 SSE 是基於 HTTP 的。這意味著,通過使用 HTTP/2,不僅僅可以把多個 SSE 流交叉合并成單一的 TCP 串連,還可以把多個 SSE 流(服務端向用戶端推送)和多個用戶端請求(用戶端到服務端)合并成單一的 TCP 串連。多虧了 HTTP/2 和 SSE,現在我們有了一個純粹的 HTTP 雙向串連,該串連帶有一個簡單的 API 允許程式碼註冊監聽服務端的資料推送。缺乏雙向通訊能力一直被認為是 SSE 對比 WebSocket 的主要缺點。多虧了 HTTP/2,這不再是缺點。這就讓你有機會堅持使用基於 HTTP 的通訊系統而非 WebSockets。

 

WebSocket 和 HTTP/2 的使用情境

WebSockets 依然可以在 HTTP/2 + SSE 的統治下存在,主要是由於它是廣受好評的技術,在特殊情況下,和 HTTP/2 比較它有一個優點即它天生擁有更少的開銷(比如,頭部資訊)的雙向通訊能力。

假設你想要構建一個大型的多人線上遊戲,在各個串連終端會產生大量的資訊。在這樣的情況下,WebSockets 會表現得更加完美。

總之,當你需要在用戶端和服務端建立一個真正的低延遲的,接近即時串連的時候使用 WebSockets。記住這可能要求你重新考慮如何構建伺服器端程式,同時也需要你關注諸如事件隊列的技術。

如果你的使用情境要求顯示即時市場新聞,市場資料,聊天程式等等,HTTP/2 + SSE 將會為你提供一個高效的雙向通訊通道且你可以得到 HTTP 的所有益處:

  • 當考慮現有架構的相容性的時候,WebSockets 經常會是一個痛點,因為升級 HTTP 串連到一個完全和 HTTP 不相關的協議。
  • 可擴充性和安全:網路組件(防火牆,入侵檢測,負載平衡器)的建立,維護和配置都是為 HTTP 所考慮的,大型/重要的程式會更喜歡具有彈性,安全和延展性的環境。

同樣地,你不得不考慮瀏覽器安全色性。查看下 WebSocket 相容情況:

相容性還不錯。

然而,HTTP/2 的情況就不太妙了:

 

  • 僅支援 TLS(還不算壞)
  • 僅限於 Windows 10 的 IE 11 部分支援
  • 僅支援 OSX 10.11+ Safari 瀏覽器
  • 僅當你協商應用 ALPN(伺服器需要明確支援的東西)才會支援 HTTP/2

SSE 的支援情況要好些:

 

 

 僅 IE/Edge 不支援。(好吧,Opera Mini 即不支援 SSE 也不支援 WebSockets,因此我們把它完全排隊在外)。有一些優雅的墊片來讓 IE/Edge 支援 SSE。

 

How Javascript works (Javascript工作原理) (五) 深入理解 WebSockets 和帶有 SSE 機制的HTTP/2 以及正確的使用姿勢

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.