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

問題:低延遲用戶端與伺服器與用戶端連線

網路大部分是以 HTTP 所謂的要求/回應模式建立而成。用戶端載入網頁後,直到使用者點擊下一頁前,系統才會開始處理動作。AJAX 大約 2005 年開始讓網路變得更生動有趣。不過,用戶端引導所有 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 連線的新網址結構定義。此外,還有 wss: 用於安全 WebSocket 連線,方法與使用 https: 建立安全 HTTP 連線的方式相同。

立即將部分事件處理常式附加至連線,可讓您瞭解連線開放時間、收到訊息,或是發生錯誤。

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

子通訊協定名稱必須是 IANA 註冊資料庫中已註冊的子通訊協定名稱。目前只有一個子通訊協定名稱 (soap) 註冊為 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 是與 Proxy 伺服器的相容性,後者支援大多數公司網路的 HTTP 連線。WebSocket 通訊協定會使用 HTTP 升級系統 (通常用於 HTTP/SSL),將 HTTP 連線「升級」至 WebSocket 連線。部分 Proxy 伺服器不喜歡這一點,因此會中斷連線。因此,即使特定用戶端使用 WebSocket 通訊協定,也可能無法建立連線。這讓下一節變得更加重要 :)

立即使用 WebSocket

WebSocket 仍是一項年輕技術,並未在所有瀏覽器中完整實作。不過,在無法使用 WebSocket 時,您還是可以將 WebSocket 與上述備用方案搭配使用。因為 socket.io 在這個網域中已經非常熱門,它隨附用戶端和伺服器實作的通訊協定,並且包含備用項 (socket.io 尚未支援二進位訊息傳送,截至 2012 年 2 月)。此外,也有商業解決方案 (例如 PusherApp),您只要提供 HTTP API 來傳送 WebSocket 訊息給用戶端,就能輕鬆將這類解決方案整合至任何網路環境。由於有額外的 HTTP 要求,相較於單純的 WebSocket,系統一律會額外負擔額外的 HTTP 要求。

伺服器端

使用 WebSocket 可為伺服器端應用程式建立新的使用模式。雖然傳統的伺服器堆疊 (例如 LAMP) 是根據 HTTP 要求/回應循環設計,但通常無法妥善處理大量的開放 WebSocket 連線。要同時開啟大量連線,需要能以低效能成本接收高度並行的架構。這類架構通常是以執行緒或非阻塞 IO 設計而成。

伺服器端實作

通訊協定版本

WebSocket 的線路通訊協定 (握手及用戶端和伺服器之間的資料傳輸) 現在是 RFC6455。最新版 Chrome 和 Android 版 Chrome 完全相容於 RFC6455,包含二進位檔訊息。另外,Firefox 可支援 11 版,也就是 Internet Explorer 10 版。您仍可使用較舊的通訊協定版本,但因已知這些版本有安全漏洞,因此不建議使用。如果您的伺服器實作採用舊版 WebSocket 通訊協定,建議您升級至最新版本。

用途

當您需要實現真正低延遲且接近即時在用戶端和伺服器之間的連線時,請使用 WebSocket。請記住,這可能包括重新思考如何建構伺服器端應用程式,並將焦點放在事件佇列等技術。以下是一些應用實例:

  • 多人線上遊戲
  • 即時通訊應用程式
  • 體育賽事直播貼圖
  • 即時更新社群訊息串

試聽帶

參考資料