WebSocket 簡介 - 將通訊端帶入網路

問題:用戶端與伺服器之間的連線延遲時間過長

網路大部分都是根據 HTTP 要求/回應模式而建置。用戶端先載入網頁,使用者接著點選下一頁,系統才會啟動瀏覽器。大約在 2005 年左右,AJAX 開始讓網頁變得更具動態性。不過,所有 HTTP 通訊都由用戶端引導,因此需要使用者互動或定期輪詢,才能從伺服器載入新資料。

讓伺服器在知道有新資料可用時,立即將資料傳送至用戶端的技術,早已存在一段時間了。名稱格式為「Push」或「Comet」。其中一個最常見的駭客攻擊手法,就是讓使用者誤以為是伺服器啟動的連線,這類攻擊手法稱為長時間輪詢。使用長時間輪詢時,用戶端會開啟與伺服器的 HTTP 連線,並在傳送回應前保持連線狀態。每當伺服器實際收到新的資料時,就會傳送回應 (其他技術涉及 FlashXHR 多部分要求,稱為 htmlfiles)。長時間輪詢和其他技巧都能順暢運作。您每天在 Gmail 聊天等應用程式中使用這些字詞。

不過,所有這些解決方法都存在一個問題:它們都會產生 HTTP 的額外負擔,因此不適合用於低延遲應用程式。您可以用瀏覽器或是任何其他具備即時元件的線上遊戲,來設計多人對戰的第一人稱射擊遊戲。

WebSocket 簡介:將網路連線引進網路

WebSocket 規格定義了在網路瀏覽器和伺服器之間建立「Socket」連線的 API。簡單來說,用戶端與伺服器之間維持永久連線,且雙方都能隨時開始傳送資料。

開始使用

只要呼叫 WebSocket 建構函式,即可開啟 WebSocket 連線:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

請注意 ws:。這是 WebSocket 連線的新網址結構定義。wss: 也適用於安全的 WebSocket 連線,就像 https: 可用於安全的 HTTP 連線一樣。

您可以立即將部分事件處理常式附加至連線,以便瞭解連線何時開啟、收到傳入訊息或發生錯誤。

第二個引數接受選用的子通訊協定。可以是字串或字串陣列。每個字串都應代表子通訊協定名稱,且伺服器只會接受陣列中傳遞的其中一個子通訊協定。您可以存取 WebSocket 物件的 protocol 屬性,判斷系統接受哪種子通訊協定。

子協定名稱必須是 IANA 註冊資料庫中已註冊的子協定名稱之一。截至 2012 年 2 月,目前只註冊了一個子通訊協定名稱 (soap)。

// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};

與伺服器通訊

只要我們與伺服器建立連線 (open 事件觸發時),就可以開始使用連線物件的 send('your message') 方法,將資料傳送至伺服器。它原本只支援字串,但在最新規格中,現在也能傳送二進位訊息。如要傳送二進位資料,您可以使用 BlobArrayBuffer 物件。

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);

同樣地,伺服器也可能隨時傳送訊息給我們。發生這種情況時,系統會觸發 onmessage 回呼。回呼會接收事件物件,您可以透過 data 屬性存取實際訊息。

WebSocket 也可以接收符合最新規格的二進位訊息。支援二進位影格的接收格式為 BlobArrayBuffer。如要指定收到的二進位檔格式,請將 WebSocket 物件的 binaryType 屬性設為「blob」或「arraybuffer」。預設格式為「blob」。(您不需要在傳送時對齊 binaryType 參數。)

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};

WebSocket 的另一項新功能是擴充功能。使用擴充功能,您可以傳送壓縮多工序等影格。您可以在開啟事件後檢查 WebSocket 物件的 extensions 屬性,找出伺服器接受的擴充功能。截至 2012 年 2 月,尚未發布任何官方的擴充功能規格。

// Determining accepted extensions
console.log(connection.extensions);

跨來源通訊

做為新型通訊協定,跨來源通訊已內建至 WebSocket。雖然您仍應確保只與信任的用戶端和伺服器通訊,但 WebSocket 可讓任何網域中的各方進行通訊。伺服器會決定是否要將服務提供給所有用戶端,或是只提供給位於一組明確定義的網域的用戶端。

Proxy 伺服器

每項新技術都會帶來一連串新問題。就 WebSocket 而言,這指的是與 Proxy 伺服器的相容性,後者會在大多數公司網路中調解 HTTP 連線。WebSocket 通訊協定會使用 HTTP 升級系統 (通常用於 HTTP/SSL),將 HTTP 連線「升級」為 WebSocket 連線。部分 Proxy 伺服器會因此失去連線。因此,即使特定用戶端使用 WebSocket 通訊協定,也可能無法建立連線。因此,下一節就顯得更加重要了 :)

立即使用 WebSocket

WebSocket 仍然是一項年輕技術,並未在所有瀏覽器中全面導入。不過,您現在可以使用 WebSocket,並搭配使用上述備用方案之一的程式庫,以便在 WebSocket 無法使用時進行備用。在這個領域中,socket.io 程式庫相當受歡迎,其中包含用戶端和伺服器的通訊協定實作方式,並提供備用方案 (截至 2012 年 2 月,socket.io 尚不支援二進位訊息傳送)。另外還有 PusherApp 等商業解決方案,只要提供 HTTP API 來傳送 WebSocket 訊息給用戶端,即可輕鬆整合至任何網路環境。由於需要額外的 HTTP 要求,因此相較於純 WebSocket,這項方法一律會產生額外的額外負擔。

伺服器端

使用 WebSocket 可為伺服器端應用程式建立全新的使用模式。雖然 LAMP 等傳統伺服器堆疊是以 HTTP 要求/回應週期為設計基礎,但通常無法妥善處理大量的開放 WebSocket 連線。如要同時保持大量連線,就必須採用可在低效能成本下接收大量並行作業的架構。這類架構通常是圍繞著執行緒或所謂的非阻斷式 I/O 設計。

伺服器端導入

通訊協定版本

WebSocket 的線路通訊協定 (握手和用戶端與伺服器之間的資料傳輸) 現為 RFC6455。最新版 Chrome 和 Chrome 適用於 Android 的版本,完全相容於 RFC6455,包括二進位訊息。此外,Firefox 將支援 11 版,Internet Explorer 則支援 10 版。您仍然可以使用舊版通訊協定,但我們不建議這麼做,因為已知版本很有安全漏洞。如果您已安裝適用於舊版 WebSocket 通訊協定的伺服器,建議將其升級至最新版本。

用途

只要需要在用戶端和伺服器之間建立低延遲、近乎即時的連線,就請使用 WebSocket。請注意,這可能需要重新思考如何建構伺服器端應用程式,並將重點放在事件佇列等新技術上。以下是一些用途範例:

  • 多人線上遊戲
  • 即時通訊應用程式
  • 即時運動賽事資訊列
  • 即時更新社群媒體動態串流

示範

參考資料