[譯] WebSockets 與長輪詢的較量

WebSockets 與長輪詢的較量

咱們要如何在二者之間作出選擇?php

有時候,當信息一旦準備就緒,咱們就須要從服務器獲取它們。而咱們一般使用的 AJAX 請求/響應模式沒法爲這類應用場景保持請求鏈接的創建。相反地,咱們須要一種基於推送的方法,例如 WebSockets 協議、長輪詢、服務器推送事件(SSE)以及最近的 HTTP2 服務器推送。在本文中,咱們將對比兩種方法:WebSockets 與長輪詢。html

長輪詢概述

1995 年,網景公司 聘請 Brendan Eich 爲 Netscape Navigator 實現腳本功能,通過 10 天的時間,JavaScript 誕生了。做爲一門編程語言,與現代 JavaScript 語言相比,那時誕生的 JavaScript 功能很是有限,而它與瀏覽器文檔對象模型(DOM)的交互就更加有限了。JavaScript 主要用於提供有限的加強功能來豐富瀏覽器文檔的使用性。例如,在瀏覽器中驗證表單、將動態 HTML 輕便地插入現有文檔。前端

隨着 瀏覽器大戰 的升溫,微軟的 Internet Explorer 版本到達了版本 4 及以上,對瀏覽器強大特性集的爭奪戰致使微軟在 Internet Explorer 中引入了一個新特性,這一特性最終成爲了 XMLHttpRequest 。十多年來,全部瀏覽器都廣泛支持 XMLHttpRequest。html5

長輪詢 本質上是原始輪詢技術的一種更有效的形式。向服務器發送重複請求會浪費資源,由於必須爲每一個新傳入的請求創建鏈接,必須解析請求的 HTTP 頭部,必須執行對新數據的查詢,而且必須生成和交付響應(一般不提供新數據)。而後必須關閉鏈接並清除全部資源。長輪詢是一種服務器選擇儘量長的時間保持和客戶端鏈接打開的技術,僅在數據變得可用或達到超時闕值後才提供響應,而不是在給到客戶端的新數據可用以前,讓每一個客戶端屢次發起重複的請求。node

WebSockets 概述

大約在 2008 年中期,開發人員 Michael CarterIan Hickson 特別敏銳地感受到在實現真正健壯的東西時使用 Comet 的痛苦和侷限性。經過 在 IRCW3C 郵件列表 上的合做,他們制定了一項計劃,在網絡上引入了現代實時雙向通訊的新標準,從而 創造了「WebSocket」這個名字android

這個想法進入了 W3C HTML 草案標準,不久以後,Michael Carter 寫了一篇文章,在 Comet 社區中介紹了 WebSockets。2010 年,谷歌的 Chrome 4 成爲第一款徹底支持 WebSockets 的瀏覽器,其餘瀏覽器供應商也在接下來的幾年中紛紛效仿。2011 年,RFC 6455 —— WebSocket 協議 —— 在 IETF 網站上發佈。ios

簡而言之,WebSockets 是一個構建在設備 TCP/IP 協議棧之上的傳輸層。其目的是向 Web 開發人員提供本質上儘量接近原始的 TCP 通訊層,同時添加一些抽象概念,以消除 Web 工做中存在的一些阻力。它們還知足了這樣一個事實:即網絡具備額外須要考慮的安全因素,這些安全因素必須考慮在內以保護消費者和服務提供商。git

長輪詢的利與弊

優勢github

  • 長輪詢是在 XMLHttpRequest 以後實現的,它幾乎獲得了設備的廣泛支持,所以一般不多須要有進一步的備選方案。可是,在必須處理異常的狀況下,或者在服務器可查詢新數據但不支持長輪詢(更不用說其餘更現代的技術標準)的狀況下,基本輪詢有時仍然有些用處,而且可使用 XMLHttpRequest 或經過 JSONP 利用簡單的 HTML 腳本標籤。

缺點web

  • 長輪詢大量佔據服務器資源。
  • 可靠的消息排序 多是長輪詢的一個問題,由於來自同一個客戶端的多個 HTTP 請求可能同時運行。舉個例子,若是一個客戶端打開兩個瀏覽器選項卡,使用相同的服務器資源,而且客戶端應用程序正在將數據持久化到本地存儲區(如 localStorage 或 IndexedDb),則沒法保證重複數據不會被屢次寫入。
  • 根據服務端實現的不一樣,一個客戶端對消息的確認接收也可能致使另外一個客戶端根本不會收到預期的消息,由於服務端可能錯誤地認爲客戶端已經收到了它所指望的數據。

WebSockets 的利與弊

優勢

  • WebSockets 保持一個惟一的鏈接打開,同時消除長輪詢的延遲問題。
  • WebSockets 一般不使用 XMLHttpRequest,所以,當咱們每次須要從服務器獲取更多的信息時,無需發送頭部數據。反過來講,這又減小了數據發送到服務器時須要付出的高昂的數據負載代價。

缺點

  • 當鏈接終止時,WebSockets 沒法自動恢復鏈接 —— 這是須要你本身實現的部分,也是致使存在許多 客戶端庫 的緣由。
  • 早於 2011 年的瀏覽器沒法支持 WebSocket 鏈接 —— 但這一點愈來愈可有可無。

爲何 WebSocket 協議是更好的選擇?

通常來講,WebSockets 會是更好的選擇。

長輪詢在服務器上佔用更多的資源,而 WebSockets 在服務器上佔用的空間不多。長輪詢還須要在服務器與許多設備之間進行屢次通訊。而不一樣的網關對於一個常規鏈接容許保持打開的時間有不一樣的標準。若是鏈接打開時間過久,其進程可能會被殺死,甚至當這個進程正在處理一些重要的事情時。

使用 WebSockets 構建應用的理由:

  • 全雙工異步消息傳送。換句話說,客戶端和服務器均可以獨立地相互傳輸消息。
  • WebSockets 無需任何配置便可經過大多數防火牆。
  • 良好的安全模式(基於原始的安全模式)。

WebSockets 開源解決方案

WebSocket 庫有兩個主要分類:一種只實現協議部分,把其他部分留給開發人員實現,另外一種構建在協議之上,它們具備實時消息通訊應用程序一般須要的各類附加功能,例如丟失鏈接的恢復,發佈/訂閱頻道、身份認證、受權等。

後者一般要求開發人員在客戶端使用本身的庫,而不只僅是使用瀏覽器提供的原始 WebSocket API。所以,確保你對所選擇方案的工做方式和所提供的服務感到滿意就變得很是重要。一旦將所選擇的解決方案集成到體系結構裏,你可能會發現本身陷入了該方案的工做方式中,任何可靠性、性能和可擴展性方面的問題均可能會反過來影響你。

讓咱們從第一類提及。

注意: 如下全部內容均是開源庫。

ws

ws 是一個「簡單易用、快速且通過全面測試的 WebSocket 客戶端和 Node.js 服務器」。它絕對是一個準系統級別的實現,旨在完成執行協議上全部艱難的工做,可是恢復鏈接、發佈/訂閱等附加功能,必須由你本身來管理。

客戶端 (綁定前的瀏覽器):

const WebSocket = require('ws');
const ws = new WebSocket('ws://www.host.com/path');
ws.on('open', function open() {
    ws.send('something');
});

ws.on('message', function incoming(data) {
    console.log(data);
});
複製代碼

服務端 (Node.js):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
    ws.on('message', function incoming(message) {
    console.log('received: %s', message);
    });
    ws.send('something');
});
複製代碼

μWebSockets

μWSws 的直接替代品,它特別注重性能和穩定性。據我所知,μWS 離最快的 WebSocket 服務器僅有一步之遙。SocketCluster 就是由它驅動的,關於 SocketCluster 我將在下面說到。

因爲做者出於哲學上的緣由試圖將 μWS 從 NPM 中提取出來,近來圍繞 μWS 引起了一些爭議,但 μWS 最新的可運行版本仍然在 NPM 上,而且能夠從 NPM 安裝時明確指定該版本。也就是說,做者正在開發 一個新版本,其附帶的 node.js 綁定 也在 開發中

var WebSocketServer = require('uws').Server;
var wss = new WebSocketServer({ port: 3000 });
function onMessage(message) {
    console.log('received: ' + message);
}

wss.on('connection', function(ws) {
    ws.on('message', onMessage);
    ws.send('something');
});
複製代碼

客戶端 —— 在瀏覽器中使用 WebSockets

WebSocket API 定義於 WHATWG HTML Living Standard,它使用起來很是簡單。構建 WebSocket 只須要一行代碼:

JS

const ws = new WebSocket('ws://example.org');
複製代碼

注意,在一般使用 HTTP 方案的地方使用 ws。在一般使用 https 方案的地方,還能夠選擇 wss。這些協議是和 WebSocket 規範一塊兒引入的,旨在表示一個 HTTP 鏈接,該鏈接中包括一個升級鏈接以使用 WebSockets 的請求。

建立 WebSocket 對象自己並無太大的做用。鏈接是異步創建的,因此在發送任何消息以前,你必須監聽握手的完成狀況,還須要一個從服務器接收消息的監聽器:

ws.addEventListener('open', () => {
    // 向 WebSocket 服務器發送消息
    ws.send('Hello!');
});

ws.addEventListener('message', event => {
// `event` 對象是一個典型的 DOM 事件對象,
// 服務器發送的消息數據存儲在 `data` 屬性中
    console.log('Received:', event.data);
});
複製代碼

還有錯誤事件和關閉事件。當鏈接終止時,WebSockets 不會自動恢復鏈接 —— 這須要你本身實現,這也是存在許多客戶端庫的緣由之一。雖然 WebSocket 類簡單易用,但它實際上只是一個基本的構建塊。對於不一樣子協議或附加功能的支持,例如消息傳輸通道,必須單獨實現。

長輪詢 —— 開源解決方案

大多數庫不會單獨使用長輪詢,由於長輪詢一般與其餘傳輸策略一塊兒使用,或做爲其餘傳輸策略的備選方案,或是當長輪詢不起做用時,將其餘傳輸策略做爲備選。在 2018 年及之後,獨立的長輪詢庫尤爲罕見,面對更先進的替代品對傳輸的普遍支持,長輪詢這種技術很快就失去了相關性。不過,你能夠將它做爲傳輸的備選方案,如下是一些不一樣語言的可選項:

Ably、WebSockets 與長輪詢

大多數 Ably 的客戶端庫 SDK 使用 WebSocket 創建與 Ably 的實時鏈接,而後對包括身份驗證在內的全部其餘 REST 操做使用簡單的 HTTP 請求。

可是,客戶端庫 SDK(例如咱們的 Javascript 瀏覽器庫)被設計爲根據可用瀏覽器和鏈接選擇可用且最佳的傳輸方式。經過支持附加的傳輸方式,使其可以回退到最低的公共標準,Ably 確保如今幾乎全部的瀏覽器都能與 Ably 創建實時鏈接。咱們的 Javascript 瀏覽器庫目前支持如下傳輸方式,按照性能從優到劣排列:

在實現對 WebSocket 的支持且將長輪詢做爲備選方案時,須要涉及到不少方面 —— 不只涉及客戶端和服務器實現細節,還涉及對其餘傳輸方式的支持,以確保對不一樣客戶端環境的可靠支持,也涉及到更普遍的關注點,例如 身份驗證和受權保證消息可交付可靠的消息排序歷史消息保留,還有 更多方面

參考資料與擴展閱讀

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索