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

問題:低延遲的主從式用戶端與伺服器與用戶端之間的連線

網路大部分都是根據 HTTP 要求/回應模式而建置。用戶端先載入網頁,使用者接著點選下一頁,系統才會啟動瀏覽器。約從 2005 年起,Webhook 會使網路感覺更加生動。然而,所有 HTTP 通訊都會由用戶端負責,需要使用者互動或定期輪詢,從伺服器載入新資料。

讓伺服器知道可擷取新資料已有一段時間之久,可讓伺服器立即將資料傳送給用戶端的技術。名稱格式為「Push」或「Comet」。「長時間輪詢」是最常造成伺服器啟動連線的入侵。長時間輪詢時,用戶端會開啟 HTTP 連線至伺服器,在傳送回應前會保持開啟。每當伺服器實際收到新的資料時,就會傳送回應 (其他技術涉及 FlashXHR 多部分要求,稱為 htmlfiles)。長時間輪詢和其他技巧都能順暢運作。您每天 (例如 Gmail 即時通訊) 會使用這些資訊。

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

隆重推出 WebSocket:將通訊端引入網路

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

開始使用

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

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

請留意 ws:。這是 WebSocket 連線的新網址結構定義。安全 WebSocket 連線也有 wss: 採用的方式與用於安全 HTTP 連線的方式與 https: 相同。

為連線開啟、收到傳入訊息或發生錯誤,立即將部分事件處理常式附加至連線。

第二個引數接受選用的子通訊協定。可以是字串或字串陣列。每個字串都應代表一個子通訊協定名稱,而伺服器只接受陣列中已傳遞的任一子通訊協定。藉由存取 WebSocket 物件的 protocol 屬性,即可決定接受的子通訊協定。

子通訊協定名稱必須是 IANA 註冊資料庫中已註冊的其中一個子通訊協定名稱。目前只有一個子通訊協定名稱 (肥皂) 註冊於 2012 年 2 月。

// 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 物件的擴充功能屬性,找出伺服器已接受的擴充功能。截至 2012 年 2 月,我們還沒有正式發布的擴充功能規格。

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

跨來源通訊

做為新型通訊協定,跨來源通訊已內建至 WebSocket。雖然建議您只與信任的用戶端和伺服器進行通訊,但 WebSocket 可讓網域上的各方進行通訊。伺服器可決定是否開放所有用戶端使用服務,或僅開放位於明確定義網域的用戶端提供服務。

Proxy 伺服器

每項新技術都會有一組新的問題。在 WebSocket 中,它與處理大多數公司網路中 HTTP 連線的 Proxy 伺服器相容。WebSocket 通訊協定使用 HTTP 升級系統 (通常用於 HTTP/SSL) 將 HTTP 連線「升級」至 WebSocket 連線。部分 Proxy 伺服器會因此失去連線。因此,即使指定的用戶端使用 WebSocket 通訊協定,也可能無法建立連線。如此一來,下一節將更加重要 :)

立即使用 WebSocket

WebSocket 仍然是一項年輕技術,並未在所有瀏覽器中全面導入。不過,當 WebSocket 無法使用時,您現在可將 WebSocket 與使用上述其中一種備用方案的程式庫搭配使用。socket.io 是在這個領域變得很受歡迎的程式庫。這個程式庫包含用戶端和伺服器實作的通訊協定,並包含備用方案 (socket.io 自 2012 年 Februrary 2012 起,尚不支援二進位訊息傳送功能)。另有一些商業解決方案 (例如 PusherApp),可以提供 HTTP API 來傳送 WebSocket 訊息給用戶端,輕鬆整合至任何網路環境。相較於單純的 WebSocket,額外的 HTTP 要求會一律增加額外費用。

伺服器端

使用 WebSocket 可為伺服器端應用程式建立全新的使用模式。雖然 LAMP 等傳統伺服器堆疊是圍繞 HTTP 要求/回應週期所設計,但它們通常不太適用於大量開放的 WebSocket 連線。同時讓大量連線保持開啟需要一個能以低效能成本接收高度並行的架構。這類架構通常是針對執行緒或稱為非阻塞 IO 所設計。

伺服器端導入

通訊協定版本

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

用途

每當需要真正低延遲且近乎即時的方式,用戶端與伺服器之間的連線時,請使用 WebSocket。請注意,這可能需要重新思考您建構伺服器端應用程式的方式,而重點又放在事件佇列等技術。以下列舉幾個使用範例:

  • 多人線上遊戲
  • 即時通訊應用程式
  • 運動賽事即時動態表
  • 即時更新社交訊息串

試聽帶

參考資料