領先技術
瞭解 WebSocket 的功能
當今的萬維網並未設計成一個即時媒介。當通過諸如 SignalR 和 Comet 之類的臨時庫有效實現 Web 應用程式時,Web 應用程式通過傳統輪詢解決方案(通過 AJAX 實現,或者也可能是通過長輪詢請求實現)給人以連續的感觀印象。對於大部分應用程式的需求,輪詢是一個不錯的解決方案,即便它可能存在用戶端到伺服器和伺服器到用戶端的延遲。本文將探討一種新的替代方法,稱為 WebSocket。
隨著 Web 和移動應用程式與社交媒體之間的集成程度越來越高,人們能夠容忍的用戶端/伺服器交互中的延遲越來越低。當您更新 Facebook 狀態時,您希望該資訊能立即被您的好友看到。同樣,當有人喜歡您發佈的某個帖子時,您希望能夠立即獲得相應的通知。今天,所有這些功能都是切實存在的,這也正是 Facebook 在世界範圍內如此熱門,以及社交網路現象遍地開花的原因之一。所以,最終結果就是迫切需要開發人員開發出能夠通過 Web 實現即時通信的解決方案和工具。
在 Web 用戶端和伺服器之間實現零滯後連接不僅僅需要 HTTP 協定。這正是 WebSocket 協定所能提供的。當前存在針對 WebSocket 協定的互聯網工程任務組標準;您可以在 bit.ly/va6qSS 上瞭解此標準的相關資訊。實現該協定的標準 API 已由萬維網聯盟 (W3C) 正式確立,以便流覽器支援該協定(請參見 bit.ly/h1IsjB)。規範處於“候選推薦”狀態。
WebSocket 協定
新的 WebSocket 協定旨在解決 HTTP 協定的結構限制,該限制使得流覽器中託管的 Web 應用程式無法通過持久連接高效地與伺服器保持連接。WebSocket 協定允許通過單一 TCP 通訊端在 Web 應用程式和 Web 伺服器之間實現雙向通信。換句話說,該協定使得流覽器中託管的 Web 應用程式可以一直與 Web 端點保持連接,同時還能將成本(例如伺服器的壓力、記憶體和資源消耗)降到最低。實際效果是資料和通知在流覽器和 Web 伺服器之間能夠無延遲地發送和接收,並且無需安排額外的請求。不容置疑,WebSocket 協定為開發人員打開了一個全新的充滿無限可能的世界,讓基於輪詢的小把戲和框架都成為了歷史。其實並不完全是這樣。
當今的 WebSocket 應用
WebSocket 協定的流覽器支援將會很快得到改善,但是只有最新版本的流覽器支援 WebSocket。不經常升級流覽器的使用者(或者因嚴格的公司策略而不允許升級的使用者)將會落伍。
這意味著開發人員不能簡單地放棄基於 AJAX 輪詢的代碼或長輪詢解決方案。在這方面,請關注 SignalR(即將推出的 Microsoft 框架,用於在流覽器和 Web 伺服器之間實現零延遲消息傳遞),它在抽象化持久性連接、支援時自動切換到 WebSocket,以及在任何其他情況下使用長輪詢方面,效果非常不錯。我在近期的專欄中已經介紹了 SignalR,如果您還未試用過 SignalR,我再次邀請您儘早嘗試一下。SignalR 毋庸置疑是所有開發人員和 Web 應用程式首選的庫和工具。
當今有誰支援 WebSocket?
圖 1 簡單地列出了目前大多數熱門流覽器提供的 WebSocket 支援。
圖 1 WebSocket 的流覽器支援
流覽器 | WebSocket 支援 |
互聯網資源管理器 | Internet Explorer 10 將支援 WebSocket。使用 JavaScript 和 HTML5 編寫的 Metro 應用程式也將支援 WebSocket。 |
火狐 | 從 2011 年年中發佈的第 6 版流覽器開始支援 WebSocket。在很早以前的第 4 版中曾提供過一些支援,但是在第 5 版時卻棄用了這些支援。 |
鉻 | 從 2011 年 9 月發佈的第 14 版開始支援 WebSocket。 |
歌劇 | 在第 11 版中刪除了對 WebSocket 的支援。 |
野生動物園 | 支援早期版本的 WebSocket 協定。 |
除 Firefox 外,您可以使用程式設計的方式通過查看 window.WebSocket 物件來瞭解 WebSocket 支援。對於 Firefox,您當前應該查看 MozWebSocket 物件。您應該注意到,大多數 HTML5 相關的功能都可以在流覽器中通過諸如 Modernizr (modernizr.com) 的專用庫的方式進行查看。特別是,如果您將 Modernizr 庫連結到頁面,則下麵是您需要編寫的 JavaScript 代碼:
if (Modernizr.websockets)
{
...
}
如果您想要開始使用 WebSocket 實現,那麼 Modernizr 可能是當下一個非常不錯的選擇,因為它向您提供填充代碼,在當前流覽器不支援指定功能時會自動插入該代碼。
因此,WebSocket 功能魅力無窮,但當下供應商的支援尚不統一。 然而 Microsoft 在即將推出的 Internet Explorer 10,以及 IIS、ASP.NET、Windows Communication Foundation (WCF) 和 Windows Runtime (WinRT) 中廣泛支援 WebSocket。 請注意,尚不存在官方的標準 API,因此早期支援會引起業內極大的關注。 現如今您最多隻能通過某個抽象層使用 WebSocket。 如果您想更接近事物本質並編寫您自己的用於打開和關閉 WebSocket 的代碼,則可以選擇 Modernizr。 如果您要尋找一種框架以便以持久方式透明地連接流覽器和 Web 端點,而無需知道太多的底層詳細資訊,那麼 SignalR 是更好的選擇。
WebSocket 協定簡介
雙向通信的 WebSocket 協定要求用戶端和伺服器應用程式都瞭解協定詳細資訊。 這意味著您需要調用符合 WebSocket 的端點的符合 WebSocket 的網頁。
WebSocket 交互從一次握手開始,在這次握手中,雙方(流覽器和伺服器)互相確認它們想通過持久性連接通信的意圖。 接下來,在兩個方向上通過 TCP 發送大量消息包。 圖 2 概述了 WebSocket 協定的工作方式。
圖 2 WebSocket 協定架構
請注意,除了圖 2 中顯示的內容以外,當連接關閉時,兩個端點會交換一個關閉幀以乾淨俐落地關閉連接。 初次握手包含一個用戶端發送給 Web 伺服器的普通 HTTP 請求。 請求是配置為升級請求的 HTTP GET:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
在 HTTP 中,具有 Upgrade 標頭的用戶端請求表示用戶端想請求伺服器切換到其他協定。 通過 WebSocket 協定,發往伺服器的升級請求包含一個唯一的金鑰,伺服器會將其破壞並返回,作為它接受升級請求的證據。 這是表示伺服器理解 WebSocket 協定的實際證明。 下麵是握手請求的示例回應:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
成功的狀態碼始終是 101,而任何其他狀態碼將解釋為拒絕升級到 WebSocket 協定。 伺服器將收到的金鑰與固定的 GUID 字串連接,並計算生成的字串的雜湊值。 然後將雜湊值編碼為 Base64 並通過 Sec-WebSocket-Accept 標頭將其返回給用戶端。
用戶端還可以發送其他標頭(例如 Sec-WebSocket-Protocol)以表示它願意採用哪些子協定。 子協定是在基本 WebSocket 協定基礎之上構建的應用程式級別協定。 如果伺服器理解建議的某些子協定,那麼它將選擇一個子協定並通過同一標頭將子協定名稱發送回用戶端。
握手之後,用戶端和伺服器可以通過 WebSocket 協定自由發送消息。 負載從一個表示操作正在執行的操作碼開始。 這些操作碼的其中之一(具體說就是 0x8)表示關閉會話的請求。 請注意,WebSocket 消息非同步發送,所以發送請求不一定會立即收到回應,與在 HTTP 中一樣。 使用 WebSocket 協定,您最好從用戶端和伺服器相互發送的常規消息方面進行思考,忘記傳統的 HTTP 請求/回應模式。
WebSocket 端點常用的 URL 採用以下形式:
var myWebSocket =
new WebSocket("ws://www.websocket.org");
如果您想要保持安全通訊端連接,則可以使用 wss 協定首碼(當存在中間媒介時,安全連接通常會更成功)。 最後,WebSocket 協定承認並解決跨域通信的問題。 WebSocket 用戶端通常(但不總是)允許將請求發送至位於任何域的端點。 但是,接受還是拒絕握手請求的決定權在 WebSocket 伺服器。
WebSocket API 簡介
正如之前所提到的,W3C 當前正在標準化用於 WebSocket 協定的 API,各種流覽器也正在爭取符合推出的各種草案的要求。 您應該瞭解,今天能夠使用的任何代碼可能無法在所有的流覽器中使用,更重要的是,當同一流覽器的新版本問世時,甚至無法保證這些代碼能夠在該同一流覽器中使用。 在任何情況下,只要您具有有效的 WebSocket 代碼,您基本上就完成得差不多了,因為將來可能需要做出的任何更改可能都只是微小改動。
如果您想要試用 WebSocket 協定,可以使用支援該協定的流覽器訪問 websocket.org。 例如,您可以使用 Internet Explorer 10 的預覽版或 Google Chrome 的最新版本。 圖 3 顯示 Fiddler 跟蹤的握手過程。
圖 3 流覽器和伺服器之間的實際握手過程
實際上,當前版本的 Fiddler(版本 2.3.x)僅捕獲 HTTP 流量。 不過,處理 WebSocket 流量的 Fiddler 新版本當前處於 Beta 階段。
WebSocket API 相當簡單。 在流覽器端,您需要創建 WebSocket 流覽器類的實例。 該類公開您希望具有其相應處理常式的大量相關事件:
var wsUri = " ws://echo.websocket.org/";
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onerror = function(evt) { onError(evt) };
建立連接時觸發 onopen 事件。 每當用戶端收到來自伺服器的消息時都會觸發 onmessage 事件。 關閉連接時觸發 onclose。 最後,發生錯誤時觸發 onerror。
要向伺服器發送消息,您只需要調用 send 方法即可,如下所示:
var message = "Cutting Edge test: " +
new Date().toString();
websocket.send(message);
圖 4 顯示一個示例頁面,該頁面是根據 websocket.org 網站上找到的回顯示例改寫的。 在該示例中,伺服器只是將收到的消息回顯給用戶端。
圖 4 WebSocket 協定的實際應用
如果您對 Internet Explorer 10 的 WebSocket 程式設計感興趣,請參見 bit.ly/GNYWFh。
WebSocket 的伺服器端
在本文中,我主要介紹了 WebSocket 協定用戶端方面的事項。 有一點應該清楚,要使用 WebSocket 用戶端,您需要能夠理解請求並能適當回復的符合 WebSocket 標準的相應伺服器。 構建 WebSocket 伺服器的框架已開始出現。 例如,您可以嘗試用於 Java 和 Node.js 的 Socket.IO (socket.io)。 如果您要尋找某些 Microsoft .NET Framework 材料,不妨查看一下 The Code Project 中的“Web 通訊端伺服器”,網址為 bit.ly/lc0rjt。 此外,IIS、ASP.NET 和 WCF 中提供面向 WebSocket 的 Microsoft 伺服器支援。 有關詳細資訊,您可以觀看第 9 頻道的視頻“使用 IIS、ASP.NET 和 WCF 通過 WebSocket 構建即時的 Web 應用程式”(bit.ly/rnYaw5)。
切片面包、 熱水、 WebSockets
正如很多人所說的,WebSocket 是繼切片面包和熱水之後最有用的發明。 在瞭解 WebSocket 之後,您就會禁不住想知道如果沒有這些發明創造,軟體世界該如何繁榮昌盛。 WebSocket 對於許多應用程式都很有用,雖然不是對所有的應用程式都有用。 對於即時消息傳遞至關重要的任何應用程式,您都可以認真考慮構建 WebSocket 伺服器和大量用戶端,例如 Web、移動設備甚至是桌上型電腦。 遊戲和現場播放應用程式是能夠從 WebSocket 協定獲得極大利益的另外兩個行業領域。 是的,WebSocket 的確是繼熱水之後最棒的發明!
Dino Esposito 是《Programming ASP.NET 4》(Microsoft Press,2011 年)和《Programming ASP.NET MVC 3》(Microsoft Press,2010 年)的作者,同時也是《Microsoft .NET:規劃為企業應用程式"(微軟出版社,2008年)。 Esposito 定居於義大利,經常在世界各地的業內活動中發表演講。 有關他的情況,請訪問 Twitter 上的 twitter.com/despos。
衷心感謝以下技術專家對本文的審閱:Levi Broderick 和 Brian Raymor