WebSocket 實戰

這一節裏我們用一個案例來演示怎麼使用 WebSocket 構建一個實時的 Web 應用。這是一個簡單的實時多人聊天系統,包括客戶端和服務端的實現。客戶端通過瀏覽器向聊天服務器發起請求,服務器端解析客戶端發出的握手請求併產生應答信息返回給客戶端,從而在客戶端和服務器之間建立連接通道。服務器支持廣播功能,每個聊天用戶發送的信息會實時的發送給所有的用戶,當用戶退出聊天室時,服務器端需要清理相應用戶的連接信息,避免資源的泄漏。以下我們分別從服務器端和客戶端來演示這個 Web 聊天系統的實現,在實現方式上我們採用了 C# 語言來實現 WebSocket 服務器,而客戶端是一個運行在瀏覽器裏的 HTML 文件。

WebSocket 服務器端實現
這個聊天服務器的實現和基於套接字的網絡應用程序非常類似,首先是服務器端要啓動一個套接字監聽來自客戶端的連接請求,關鍵的區別在於 WebSocket 服務器需要解析客戶端的 WebSocket 握手信息,並根據 WebSocket 規範的要求產生相應的應答信息。一旦 WebSocket 連接通道建立以後,客戶端和服務器端的交互就和普通的套接字網絡應用程序是一樣的了。所以在下面的關於 WebSocket 服務器端實現的描述中,我們主要闡述 WebSocket 服務器怎樣處理 WebSocket 握手信息,至於 WebSocket 監聽端口的建立,套接字信息流的讀取和寫入,都是一些常用的套接字編程的方式,我們就不多做解釋了,您可以自行參閱本文的附件源代碼文件。

在描述 WebSocket 規範時提到,一個典型的 WebSocket Upgrade 信息如下所示:

GET /demo HTTP/1.1 
Host: example.com 
Connection: Upgrade 
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 
Upgrade: WebSocket 
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5 
Origin: http://example.com 
[8-byte security key]

其中 Sec-WebSocket-Key1,Sec-WebSocket-Key2 和 [8-byte security key] 這幾個頭信息是 WebSocket 服務器用來生成應答信息的來源,依據 draft-hixie-thewebsocketprotocol-76 草案的定義,WebSocket 服務器基於以下的算法來產生正確的應答信息:

逐個字符讀取 Sec-WebSocket-Key1 頭信息中的值,將數值型字符連接到一起放到一個臨時字符串裏,同時統計所有空格的數量;
將在第 1 步裏生成的數字字符串轉換成一個整型數字,然後除以第 1 步裏統計出來的空格數量,將得到的浮點數轉換成整數型;
將第 2 步裏生成的整型值轉換爲符合網絡傳輸的網絡字節數組;
對 Sec-WebSocket-Key2 頭信息同樣進行第 1 到第 3 步的操作,得到另外一個網絡字節數組;
將 [8-byte security key] 和在第 3,第 4 步裏生成的網絡字節數組合併成一個 16 字節的數組;
對第 5 步生成的字節數組使用 MD5 算法生成一個哈希值,這個哈希值就作爲安全密鑰返回給客戶端,以表明服務器端獲取了客戶端的請求,同意創建 WebSocket 連接
至此,客戶端和服務器的 WebSocket 握手就完成了,WebSocket 通道也建立起來了。下面首先介紹一下服務器端實現是如何根據用戶傳遞的握手信息來生成網絡字節數組的。.NET 平臺提供了很方便的對字符串,數值以及數組操作的函數,所以生成字節數組的方法還是非常簡單明瞭的,代碼如下:

清單 4. 生成網絡字節數組的代碼

   private byte[]   BuildServerPartialKey(string clientKey) 
{ 
     string partialServerKey = ""; 
    byte[] currentKey; 
    int spacesNum = 0; 
    char[] keyChars = clientKey.ToCharArray(); 
    foreach (char currentChar in keyChars) 
    { 
        if (char.IsDigit(currentChar)) partialServerKey += currentChar; 
       if (char.IsWhiteSpace(currentChar)) spacesNum++; 
    } 
    try 
    { 
             currentKey = BitConverter.GetBytes((int)(Int64.Parse(partialServerKey) 
/ spacesNum)); 
       if (BitConverter.IsLittleEndian) Array.Reverse(currentKey); 
    } 
    catch 
    { 
       if (currentKey!= null) Array.Clear(currentKey, 0, currentKey.Length); 
    } 
    return currentKey; 
 }

得到網絡字節數組以後,服務器端生成 16 位安全密鑰的方法如下所示:

清單 5. 生成 16 位安全密鑰的代碼

private byte[] BuildCompleteServerKey(byte[] serverKey1, byte[] serverKey2, 
byte[] last8Bytes) 
{ 
    byte[] concatenatedKeys = new byte[16]; 
   Array.Copy(serverKey1, 0, concatenatedKeys, 0, 4); 
   Array.Copy(serverKey2, 0, concatenatedKeys, 4, 4); 
   Array.Copy(last8Bytes, 0, concatenatedKeys, 8, 8); 
   System.Security.Cryptography.MD5 MD5Service = 
System.Security.Cryptography.MD5.Create(); 
  return MD5Service.ComputeHash(concatenatedKeys); 
}

整個實現是非常簡單明瞭的,就是將生成的網絡字節數組和客戶端提交的頭信息裏的 [8-byte security key] 合併成一個 16 位字節數組並用 MD5 算法加密,然後將生成的安全密鑰作爲應答信息返回給客戶端,雙方的 WebSocekt 連接通道就建立起來了。實現了 WebSocket 握手信息的處理邏輯,一個具有基本功能的 WebSocket 服務器就完成了。整個 WebSocket 服務器由兩個核心類構成,一個是 WebSocketServer,另外一個是 SocketConnection,出於篇幅的考慮,我們不介紹每個類的屬性和方法了,文章的附件會給出詳細的源代碼,有興趣的讀者可以參考。

圖 3. WebSocket 服務器剛啓動的畫面
這裏寫圖片描述

客戶端可以依據這個信息填寫聊天服務器的連接地址,當有客戶端連接到聊天服務器上時,服務器會打印出客戶端和服務器的握手信息,每個客戶的聊天信息也會顯示在服務器的界面上,運行中的聊天服務器的界面如下:

圖 4. 有客戶端連接到 WebSocket 服務器的
這裏寫圖片描述

以上我們簡單描述了實現一個 WebSocket 服務器的最基本的要素,下一節我們會描述客戶端的實現。

客戶端實現

客戶端的實現相對於服務器端的實現來說要簡單得多了,我們只需要發揮想象去設計 HTML 用戶界面,然後呼叫 WebSocket JavaScript 接口來和 WebSocket 服務器端來交互就可以了。當然別忘了使用一個支持 HTML5 和 WebSocket 的瀏覽器,在筆者寫這篇文章的時候使用的瀏覽器是 Firefox。客戶端的頁面結構是非常簡潔的,初始運行界面如下:

圖 5. 聊天室客戶端初始頁面
這裏寫圖片描述

當頁面初次加載的時候,首先會檢測當前的瀏覽器是否支持 WebSocket 並給出相應的提示信息。用戶按下連接按鈕時,頁面會初始化一個到聊天服務器的 WebSocekt 連接,初始化成功以後,頁面會加載對應的 WebSocket 事件處理函數,客戶端 JavaScript 代碼如下所示:

清單 6. 初始化客戶端 WebSocket 對象的代碼

function ToggleConnectionClicked() {
         if (SocketCreated && (ws.readyState == 0 || ws.readyState == 1)) {  
               ws.close();
           } else {
               Log("準備連接到聊天服務器 ...");
               try {
                ws = 
                new WebSocket("ws://" + document.getElementById("Connection").value);
                 SocketCreated = true;
               } catch (ex) {
                 Log(ex, "ERROR");
                 return;
               }
               document.getElementById("ToggleConnection").innerHTML = "斷開";
               ws.onopen = WSonOpen;
               ws.onmessage = WSonMessage;
               ws.onclose = WSonClose;
               ws.onerror = WSonError;
           }
       };
       function WSonOpen() {
           Log("連接已經建立。", "OK");
           $("#SendDataContainer").show("slow");
       };

       function WSonMessage(event) {
           Log(event.data);            
       };

       function WSonClose() {
           Log("連接關閉。", "ERROR");
           document.getElementById("ToggleConnection").innerHTML = "連接";
           $("#SendDataContainer").hide("slow");
       };


       function WSonError() {
           Log("WebSocket錯誤。", "ERROR");
       };

當用戶按下發送按鈕,客戶端會調用WebSocket對象向服務器發送信息,並且這個消息會廣播給所有的用戶,實現代碼如下所示:

function SendDataClicked()
 {
            if (document.getElementById("DataToSend").value != "") {
                ws.send(document.getElementById("txtName").value + "說 :\"" + 
document.getElementById("DataToSend").value + "\"");
                document.getElementById("DataToSend").value = "";
            }
        };

如果有多個用戶登錄到聊天服務器,客戶端頁面的運行效果如下所示:

圖 6. 聊天客戶端運行頁面
這裏寫圖片描述

至此我們已經完成了一個完整的 WebSocket 客戶端實現,用戶可以體驗一下這個聊天室的簡單和快捷,完全不用考慮頁面的刷新和繁瑣的 Ajax 調用,享受桌面程序的用戶體驗。WebSocket 的強大和易用可見一斑,您完全可以在這個基礎上加入更多的功能,設計更加漂亮的用戶界面,切身體驗 WebSocket 的震撼力。完整的客戶端代碼請參閱附件提供的源代碼。

WebSocket 的侷限性

WebSocket 的優點已經列舉得很多了,但是作爲一個正在演變中的 Web 規範,我們也要看到目前用 Websocket 構建應用程序的一些風險。首先,WebSocket 規範目前還處於草案階段,也就是它的規範和 API 還是有變動的可能,另外的一個風險就是微軟的 IE 作爲佔市場份額最大的瀏覽器,和其他的主流瀏覽器相比,對 HTML5 的支持是比較差的,這是我們在構建企業級的 Web 應用的時候必須要考慮的一個問題。

總結

本文介紹了 HTML5 WebSocket 的橫空出世以及它嘗試解決的的問題,然後介紹了 WebSocket 規範和 WebSocket 接口,以及和傳統的實時技術相比在性能上的優勢,並且演示了怎樣使用 WebSocket 構建一個實時的 Web 應用,最後我們介紹了當前的主流瀏覽器對 HTML5 的支持情況和 WebSocket 的侷限性。不過,我們應該看到,儘管 HTML5 WebSocket 目前還有一些侷限性,但是已經是大勢所趨,微軟也明確表達了未來對 HTML5 的支持,而且這些支持我們可以在 Windows 8 和 IE10 裏看到,我們也在各種移動設備,平板電腦上看到了 HTML5 和 WebSocket 的身影。WebSocket 將會成爲未來開發實時 Web 應用的生力軍應該是毫無懸念的了,作爲 Web 開發人員,關注 HTML5,關注 WebSocket 也應該提上日程了,否則我們在新一輪的軟件革新的浪潮中只能做壁上觀了。

參考:https://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/index.html

代碼下載地址:
http://www.ibm.com/developerworks/apps/download/index.jsp?contentid=781302&filename=new-source.zip&method=http&locale=zh_CN

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章