摘要:幀協議讓我們深入了解下幀協議。目前可用的值該幀接續前面一幀的有效載荷。該幀包含二進制數據。幀有以下幾類長度表示有效載荷的長度。數據分片有效載荷數據可以被分成多個獨立的幀。接收端會緩沖這些幀直到位有值。
原文請查閱這里,略有改動,本文采用知識共享署名 3.0 中國大陸許可協議共享,BY Troland。
本系列持續更新中,Github 地址請查閱這里。
這是 JavaScript 工作原理的第五章。
現在,我們將會深入通信協議的世界,繪制并討論它們的特點和內部構造。我們將會給出一份 WebSockets 和 HTTP/2 的快速比較 。在文末,我們將會分享如何正確地選擇網絡協議的一些見解。
簡介現在,復雜的網頁程序擁有豐富的功能,這得多虧網頁的動態交互能力。而這并不令人感到驚訝-因為自互聯網誕生,它經歷了一段相當長的時間。
起初,互聯網并不是用來支持如此動態和復雜的網頁程序的。它本來設想是由大量的 HTML 頁面組成的,每個頁面鏈接到其它的頁面,這樣就形成了包含信息的網頁的概念。一切都是極大地圍繞著所謂的 HTTP 請求/響應模式來建立的。客戶端加載一個網頁,直到用戶點擊頁面并導航到下一個網頁。
大約在 2005 年,引入了 AJAX,然后很多人開始探索客戶端和服務端雙向通信的可能性。然而,所有的 HTTP 鏈接是由客戶端控制的,意即必須由用戶進行操作或者定期輪詢以從服務器加載數據。
讓 HTTP 支持雙向通信支持服務器主動向客戶端推送數據的技術已經出現了好一段時間了。比如 "Push" 和 "Comet" 技術。
長輪詢是服務端主動向客戶端發送數據的最常見的 hack 之一。通過長輪詢,客戶端打開了一個到服務端的 HTTP 連接直到返回響應數據。當服務端有新數據需要發送時,它會把新數據作為響應發送給客戶端。
讓我們看一下簡單的長輪詢代碼片段:
(function poll(){ setTimeout(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" 連接。通俗地講:在客戶端和服務器保有一個持久的連接,兩邊可以在任意時間開始發送數據。
客戶端通過 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.1 Origin: http://example.com Connection: Upgrade Host: websocket.example.com Upgrade: websocket
如果服務器支持 WebSocket 協議,它將會同意升級請求,然后通過在響應里面返回 Upgrade 頭來進行通信。
讓我們看下 Node.js 的實現:
// 我們將會使用 https://github.com/theturtle32/WebSocket-Node 來實現 WebSocket var 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 Protocols Date: Wed, 25 Oct 2017 10:07:34 GMT Connection: Upgrade Upgrade: 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 位):指示是否是組成信息的最后一幀。大多數時候,信息只有一幀所以該位通常有值。測試表明火狐的第二幀數據在 32K 之后。
rsv1,rsv2,rsv3(每個一位):必須是 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。
SessionStack 是如何選擇的?SessionStack? 同時使用 WebSockets 和 HTTP,這取決于使用場景。
一旦整合 SessionStack 進網頁程序,它會開始記錄 DOM 變化,用戶交互,JavaScript 異常,堆棧追蹤,失敗的網絡請求以及調試信息,允許你用視頻回放網頁程序中的問題及發生在用戶身上的一切事情。全部都是實時發生的并且要求對網頁程序不會產生任何的性能影響。
這意味著你可以實時加入到用戶會話,而用戶仍然在瀏覽器中。這樣的情況下,我們會選擇使用 HTTP,因為這并不需要雙向通信(服務端把數據傳輸到瀏覽器端)。當前情況下,使用 WebSocket 就是過度使用,難以維護和擴展。
然而,整合進網頁程序的 SessionStack 庫應用了 WebSocket(優先使用,否則回滾到 HTTP)。它會打包并且向我們的服務器發送數據,這是單向通信。在這種情況下,之所以選擇 WebSocket 是因為計劃中的某些產品功能可能需要進行雙向通信。
本系列持續更新中,Github 地址請查閱這里。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94895.html
摘要:數據作為消息通過傳輸,每個消息由一個或多個幀組成,其中包含正在發送的數據有效負載。幀數據如上所述,數據可以被分割成多個幀。但是,規范希望能夠處理交錯的控制幀。 文章底部分享給大家一套 react + socket 實戰教程 這是專門探索 JavaScript 及其所構建的組件的系列文章的第5篇。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 如果你錯過了前面的章...
摘要:注意值得注意的是,一旦接收到所有幀并且原始消息有效載荷已被重建,客戶端將僅被通知關于新消息。實驗表明,在之后創建了第二個幀。以下值目前正在使用中代表繼續幀。 這一次,我們將深入到通信協議的世界中,對比并討論它們的屬性并構建部件。我們將提供WebSockets和HTTP / 2的快速比較。 最后,我們分享一些關于如何選擇網絡協議。 概述 如今,擁有豐富動態用戶界面的復雜網絡應用程序被視為...
摘要:下面我們從前端基礎和底層原理開始講起。對于和這三個對應于矢量圖位圖和圖的渲染來說,給前端開發帶來了重武器,很多小游戲也因此蓬勃發展。這篇文章受眾之大,后來被人重新整理并發布為,其中還包括中文版。 showImg(https://segmentfault.com/img/bVbjM5r?w=1142&h=640); 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! 這...
摘要:由兩部分組成回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數,回調函數的名字一般是在請求中指定的。下面是以個的例子回調函數的名字就是是通過動態的元素來使用的,使用時可以為屬性指定一個跨域。是為與其他傳遞消息的很相似。 圖像Ping技術 根據一個網頁可以從任何網頁中加載圖像而不用擔心使用跨域的原理, 我們可以動態的創建圖像, 使用他們的onload和onerror事件處理程序...
閱讀 3538·2021-11-22 15:22
閱讀 3328·2019-08-30 15:54
閱讀 2724·2019-08-30 15:53
閱讀 783·2019-08-29 11:22
閱讀 3529·2019-08-29 11:14
閱讀 2073·2019-08-26 13:46
閱讀 2210·2019-08-26 13:24
閱讀 2277·2019-08-26 12:22