問題:低延遲的客戶端-服務器和服務器-客戶端連接
一直以來,網絡在很大程度上都是圍繞著所謂 HTTP 的請求/響應模式而構建的。客戶端加載一個網頁,然后直到用戶點擊下一頁之前,什么都不會發生。在 2005 年左右,AJAX 開始讓網絡變得更加動態了。但所有 HTTP 通信仍然是由客戶端控制的,這就需要用戶互動或定期輪詢,以便從服務器加載新數據。
長期以來存在著各種技術,可讓服務器得知有新數據可用時,立即將數據發送到客戶端。這些技術名目繁多,例如“推送”或 Comet。最普遍的一種黑客手段是對服務器發起的鏈接創建假象,這稱為長輪詢。利用長輪詢,客戶端可打開指向服務器的 HTTP 連接,而服務器會一直保持連接打開,直到發送響應。服務器只要實際擁有新數據,就會發送響應(其他技術包括 Flash、XHR multipart 請求和所謂的 htmlfiles)。長輪詢和其他技術非常好用,您在 Gmail 聊天等應用中經常使用它們。
但是,這些解決方案都存在一個共同的問題:它們帶有 HTTP 的開銷,導致它們不適用于低延遲應用。可以想象一下瀏覽器中的多人第一人稱射擊游戲,或者其他任何帶有即時要素的在線游戲。
WebSocket 簡介:將套接字引入網絡
WebSocket 規范定義了一種 API,可在網絡瀏覽器和服務器之間建立“套接字”連接。簡單地說:客戶端和服務器之間存在持久的連接,而且雙方都可以隨時開始發送數據。
使用入門
只需調用 WebSocket 構造函數即可打開 WebSocket 連接:
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
請注意 ws:
。這是 WebSocket 連接的新網址架構。對于安全 WebSocket 連接還有 wss:
,就像 https:
用于安全 HTTP 連接一樣。
立即向連接附加一些事件處理程序可讓您知道連接打開、收到傳入訊息或出現錯誤的時間。
第二個參數可接受可選子協議,它既可以是字符串,也可以是字符串數組。每個字符串都應代表一個子協議名稱,而服務器只能接受數組中通過的一個子協議。訪問 WebSocket 對象的 protocol
屬性可確定接受的子協議。
子協議名稱必須是 IANA 注冊表中的某個注冊子協議名稱。截止 2012 年 2 月,只有一個注冊子協議名稱 (soap)。
// When the connection is open, send some data to the serverconnection.onopen = function () { connection.send('Ping'); // Send the message 'Ping' to the server};// Log errorsconnection.onerror = function (error) { console.log('WebSocket Error ' + error);};// Log messages from the serverconnection.onmessage = function (e) { console.log('Server: ' + e.data);};
與服務器通信
與服務器建立連接后(啟動 open
事件時),我們可以開始對連接對象使用 send('your message')
方法,向服務器發送數據。該方法以前只支持字符串,但根據最新的規范,現在也可以發送二進制訊息了。要發送二進制數據,您可以使用 Blob
或 ArrayBuffer
對象。
// Sending Stringconnection.send('your message');// Sending canvas ImageData as ArrayBuffervar 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 Blobvar file = document.querySelector('input[type="file"]').files[0];connection.send(file);
同樣,服務器也可能隨時向我們發送訊息。只要發生這種情況,就會啟動onmessage
回調。該回調接收的是事件對象,而實際的訊息可通過 data
屬性進行訪問。
在最新規范中,WebSocket 也可以接收二進制訊息。接收的二進制幀可以是 Blob
或 ArrayBuffer
格式。要指定收到的二進制數據的格式,可將 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 的另一個新增功能是擴展。利用擴展,可以發送壓縮幀、多路復用幀等。您可以檢查 open 事件后的 WebSocket 對象的 extensions 屬性,查找服務器所接受的擴展。截止 2012 年 2 月,還沒有官方發布的擴展規范。
// Determining accepted extensionsconsole.log(connection.extensions);
跨源通信
作為現代協議,跨源通信已內置在 WebSocket 中。雖然您仍然應該確保只與自己信任的客戶端和服務器通信,但 WebSocket 可實現任何域上多方之間的通信。服務器將決定是向所有客戶端,還是只向駐留在一組指定域上的客戶端提供服務。
代理服務器
每一種新技術在出現時,都會伴隨著一系列問題。WebSocket 也不例外,它與大多數公司網絡中用于調解 HTTP 連接的代理服務器不兼容。WebSocket 協議使用 HTTP 升級系統(通常用于 HTTP/SSL)將 HTTP 連接“升級”為 WebSocket 連接。某些代理服務器不支持這種升級,并會斷開連接。因此,即使指定的客戶端使用了 WebSocket 協議,可能也無法建立連接。這就使得下一部分的內容更加重要了。
立即使用 WebSocket
WebSocket 仍是一項新興技術,并未在所有瀏覽器中全面實施。而在無法使用 WebSocket 的情況下,只要通過一些使用了上述某個回調的庫,就可以立即使用 WebSocket 了。在這一方面使用非常普遍的庫是 socket.io,其中自帶了協議的客戶端和服務器實施,并包含回調(截止 2012 年 2 月,socket.io 還不支持二進制訊息傳輸)。還有一些商業解決方案,例如 PusherApp,通過提供向客戶端發送 WebSocket 訊息的 HTTP API,可輕松地集成到任何網絡環境中。由于額外的 HTTP 請求,這些解決方案與純 WebSocket 相比總是會有額外的開銷。
服務器端
使用 WebSocket 為服務器端應用帶來了全新的用法。雖然 LAMP 等傳統服務器堆棧是圍繞 HTTP 請求/響應循環而設計的,但是通常無法很好地處理大量打開的 WebSocket 連接。要同時維持大量連接處于打開狀態,就需要能以低性能開銷接收高并發數據的架構。此類架構通常是圍繞線程或所謂的非阻塞 IO 而設計的。
服務器端實施
Node.js
Socket.IO
WebSocket-Node
ws
Java
Jetty
Ruby
EventMachine
Python
pywebsocket
Tornado
Erlang
Shirasu
C++
libwebsockets
.NET
SuperWebSocket
協議版本
現在,WebSocket 的單線協議(客戶端與服務器之間的握手和數據傳輸)是 RFC6455。最新版的 Chrome 瀏覽器和 Android 版 Chrome 瀏覽器與 RFC6455 完全兼容(包括二進制訊息傳輸)。另外,Firefox 11 和 Internet Explorer 10 也會實現兼容。您仍可以使用舊版協議,但由于它們已知存在漏洞,我們不建議使用。如果您有舊版 WebSocket 協議的服務器實施,我們建議您將其更新到最新版本。
用例
如果您需要在客戶端與服務器之間建立極低延遲、近乎即時的連接,則可使用 WebSocket。請記住,這可能需要您重新考慮構建服務器端應用的方式,將新的關注點放在事件隊列等技術上。以下是一些用例:
多人在線游戲
聊天應用
體育賽況直播
即時更新社交信息流
演示
Plink
Paint With Me
Pixelatr
Dashed
大型多人在線填字游戲
Ping 服務器(在以上示例中使用)
HTML5demos 示例