from:http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/html
websocket 規範升級過,在該連接的文章內未說起,後面補充了一些 更新的信息html5
做爲下一代的 Web 標準,HTML5 擁有許多引人注目的新特性,如 Canvas、本地存儲、多媒體編程接口、WebSocket 等等。這其中有「Web 的 TCP 」之稱的 WebSocket 格外吸引開發人員的注意。WebSocket 的出現使得瀏覽器提供對 Socket 的支持成爲可能,從而在瀏覽器和服務器之間提供了一個基於 TCP 鏈接的雙向通道。Web 開發人員能夠很是方便地使用 WebSocket 構建實時 web 應用,開發人員的手中今後又多了一柄神兵利器。本文首先介紹 HTML5 WebSocket 的基本概念以及這個規範試圖解決的問題,而後介紹 WebSocket 的基本原理和編程接口。接下來會經過一個簡單案例來示範怎樣實現一個 WebSocket 應用,而且展現 WebSocket 如何在功能強大和編程簡單易用上達到的完美統一。最後介紹了目前主流瀏覽器對 WebSocket 支持的情況、侷限性以及將來的展望。node
實時 Web 應用的窘境python
Web 應用的信息交互過程一般是客戶端經過瀏覽器發出一個請求,服務器端接收和審覈完請求後進行處理並返回結果給客戶端,而後客戶端瀏覽器將信息呈現出來,這種機制對於信息變化不是特別頻繁的應用尚能相安無事,可是對於那些實時要求比較高的應用來講,好比說在線遊戲、在線證券、設備監控、新聞在線播報、RSS 訂閱推送等等,當客戶端瀏覽器準備呈現這些信息的時候,這些信息在服務器端可能已通過時了。因此保持客戶端和服務器端的信息同步是實時 Web 應用的關鍵要素,對 Web 開發人員來講也是一個難題。在 WebSocket 規範出來以前,開發人員想實現這些實時的 Web 應用,不得不採用一些折衷的方案,其中最經常使用的就是輪詢 (Polling) 和 Comet 技術,而 Comet 技術其實是輪詢技術的改進,又可細分爲兩種實現方式,一種是長輪詢機制,一種稱爲流技術。下面咱們簡單介紹一下這幾種技術:git
輪詢:web
這是最先的一種實現實時 Web 應用的方案。客戶端以必定的時間間隔向服務端發出請求,以頻繁請求的方式來保持客戶端和服務器端的同步。這種同步方案的最大問題是,當客戶端以固定頻率向服務器發起請求的時候,服務器端的數據可能並無更新,這樣會帶來不少無謂的網絡傳輸,因此這是一種很是低效的實時方案。ajax
長輪詢:算法
長輪詢是對定時輪詢的改進和提升,目地是爲了下降無效的網絡傳輸。當服務器端沒有數據更新的時候,鏈接會保持一段時間週期直到數據或狀態改變或者時間過時,經過這種機制來減小無效的客戶端和服務器間的交互。固然,若是服務端的數據變動很是頻繁的話,這種機制和定時輪詢比較起來沒有本質上的性能的提升。編程
流:數組
流技術方案一般就是在客戶端的頁面使用一個隱藏的窗口向服務端發出一個長鏈接的請求。服務器端接到這個請求後做出迴應並不斷更新鏈接狀態以保證客戶端和服務器端的鏈接不過時。經過這種機制能夠將服務器端的信息源源不斷地推向客戶端。這種機制在用戶體驗上有一點問題,須要針對不一樣的瀏覽器設計不一樣的方案來改進用戶體驗,同時這種機制在併發比較大的狀況下,對服務器端的資源是一個極大的考驗。
綜合這幾種方案,您會發現這些目前咱們所使用的所謂的實時技術並非真正的實時技術,它們只是在用 Ajax 方式來模擬實時的效果,在每次客戶端和服務器端交互的時候都是一次 HTTP 的請求和應答的過程,而每一次的 HTTP 請求和應答都帶有完整的 HTTP 頭信息,這就增長了每次傳輸的數據量,並且這些方案中客戶端和服務器端的編程實現都比較複雜,在實際的應用中,爲了模擬比較真實的實時效果,開發人員每每須要構造兩個 HTTP 鏈接來模擬客戶端和服務器之間的雙向通信,一個鏈接用來處理客戶端到服務器端的數據傳輸,一個鏈接用來處理服務器端到客戶端的數據傳輸,這不可避免地增長了編程實現的複雜度,也增長了服務器端的負載,制約了應用系統的擴展性。
HTML5 WebSocket 設計出來的目的就是要取代輪詢和 Comet 技術,使客戶端瀏覽器具有像 C/S 架構下桌面系統的實時通信能力。 瀏覽器經過 JavaScript 向服務器發出創建 WebSocket 鏈接的請求,鏈接創建之後,客戶端和服務器端就能夠經過 TCP 鏈接直接交換數據。由於 WebSocket 鏈接本質上就是一個 TCP 鏈接,因此在數據傳輸的穩定性和數據傳輸量的大小方面,和輪詢以及 Comet 技術比較,具備很大的性能優點。Websocket.org 網站對傳統的輪詢方式和 WebSocket 調用方式做了一個詳細的測試和比較,將一個簡單的 Web 應用分別用輪詢方式和 WebSocket 方式來實現,在這裏引用一下他們的測試結果圖:
圖 1. 輪詢和 WebSocket 實現方式的網絡負載對比圖
經過這張圖能夠清楚的看出,在流量和負載增大的狀況下,WebSocket 方案相比傳統的 Ajax 輪詢方案有極大的性能優點。這也是爲何咱們認爲 WebSocket 是將來實時 Web 應用的首選方案的緣由。
WebSocket 協議本質上是一個基於 TCP 的協議。爲了創建一個 WebSocket 鏈接,客戶端瀏覽器首先要向服務器發起一個 HTTP 請求,這個請求和一般的 HTTP 請求不一樣,包含了一些附加頭信息,其中附加頭信息」Upgrade: WebSocket」代表這是一個申請協議升級的 HTTP 請求,服務器端解析這些附加的頭信息而後產生應答信息返回給客戶端,客戶端和服務器端的 WebSocket 鏈接就創建起來了,雙方就能夠經過這個鏈接通道自由的傳遞信息,而且這個鏈接會持續存在直到客戶端或者服務器端的某一方主動的關閉鏈接。
下面咱們來詳細介紹一下 WebSocket 規範,因爲這個規範目前仍是處於草案階段,版本的變化比較快,咱們選擇 draft-hixie-thewebsocketprotocol-76版原本描述 WebSocket 協議。由於這個版本目前在一些主流的瀏覽器上好比 Chrome,、FireFox、Opera 上都獲得比較好的支持,您若是參照的是新一些的版本話,內容可能會略有差異。
一個典型的 WebSocket 發起請求和獲得響應的例子看起來以下:
(最新的 websocket 協議已經修改了,再也不有 Sec-WebSocket-Key1 和 Sec-WebSocket-Key2 )
客戶端到服務端:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Origin: http://example.com
[8-byte security key]
服務端到客戶端:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
這些請求和一般的 HTTP 請求很類似,可是其中有些內容是和 WebSocket 協議密切相關的。咱們須要簡單介紹一下這些請求和應答信息,」Upgrade:WebSocket」表示這是一個特殊的 HTTP 請求,請求的目的就是要將客戶端和服務器端的通信協議從 HTTP 協議升級到 WebSocket 協議。從客戶端到服務器端請求的信息裏包含有」Sec-WebSocket-Key1」、「Sec-WebSocket-Key2」和」[8-byte securitykey]」這樣的頭信息。這是客戶端瀏覽器須要向服務器端提供的握手信息,服務器端解析這些頭信息,並在握手的過程當中依據這些信息生成一個 16 位的安全密鑰並返回給客戶端,以代表服務器端獲取了客戶端的請求,贊成建立 WebSocket 鏈接。一旦鏈接創建,客戶端和服務器端就能夠經過這個通道雙向傳輸數據了。
在實際的開發過程當中,爲了使用 WebSocket 接口構建 Web 應用,咱們首先須要構建一個實現了 WebSocket 規範的服務器,服務器端的實現不受平臺和開發語言的限制,只須要聽從 WebSocket 規範便可,目前已經出現了一些比較成熟的 WebSocket 服務器端實現,好比:
Kaazing WebSocket Gateway — 一個 Java 實現的 WebSocket Server
mod_pywebsocket — 一個 Python 實現的 WebSocket Server
Netty —一個 Java 實現的網絡框架其中包括了對 WebSocket 的支持
node.js —一個 Server 端的 JavaScript 框架提供了對 WebSocket 的支持
若是以上的 WebSocket 服務端實現還不能知足您的業務需求的話,開發人員徹底能夠根據 WebSocket 規範本身實現一個服務器。在「WebSocket 實戰」這一節,咱們將使用 Microsoft .NET 平臺上的 C# 語言來打造一個簡單的 WebSocket 服務器,繼而構建一個簡單的實時聊天系統。
上一節介紹了 WebSocket 規範,其中主要介紹了 WebSocket 的握手協議。握手協議一般是咱們在構建 WebSocket 服務器端的實現和提供瀏覽器的 WebSocket 支持時須要考慮的問題,而針對 Web 開發人員的 WebSocket JavaScript 客戶端接口是很是簡單的,如下是 WebSocket JavaScript 接口的定義:
清單 2. WebSocket JavaScript 定義
[Constructor(in DOMString url, in optional DOMString protocol)]
interface WebSocket {
readonly attribute DOMString URL; // ready state
const unsigned short CONNECTING = 0;
const unsigned short OPEN = 1;
const unsigned short CLOSED = 2;
readonly attribute unsigned short readyState;
readonly attribute unsigned long bufferedAmount; //networking
attribute Function onopen;
attribute Function onmessage;
attribute Function onclose;
boolean send(in DOMString data);
void close();
};
WebSocket implements EventTarget;
其中 URL 屬性表明 WebSocket 服務器的網絡地址,協議一般是」ws」(安全鏈接是 wss),send 方法就是發送數據到服務器端,close 方法就是關閉鏈接。除了這些方法,還有一些很重要的事件:onopen,onmessage,onerror 以及 onclose。咱們借用 Nettuts網站上的一張圖來形象的展現一下 WebSocket 接口:
下面是一段簡單的 JavaScript 代碼展現了怎樣創建 WebSocket 鏈接和獲取數據:
清單 3. 創建 WebSocket 鏈接的實例 JavaScript 代碼
var wsServer = 'ws://localhost:8888/Demo';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt) { onOpen(evt) };
websocket.onclose = function (evt) { onClose(evt) };
websocket.onmessage = function (evt) { onMessage(evt) };
websocket.onerror = function (evt) { onError(evt) };
function onOpen(evt) { console.log("Connected to WebSocket server."); }
function onClose(evt) { console.log("Disconnected"); }
function onMessage(evt) { console.log('Retrieved data from server: ' + evt.data); }
function onError(evt) { console.log('Error occured: ' + evt.data); }
下面是主流瀏覽器對 HTML5 WebSocket 的支持狀況:
瀏覽器支持狀況
ChromeSupported in version 4+
FirefoxSupported in version 4+ (firefox 6 以後,WebSocket 對象被更名爲 mozWebSocket,但最新版本的 firefox 又改回 WebSocket)
Internet ExplorerSupported in version 10+
OperaSupported in version 10+
SafariSupported in version 5+
這一節裏咱們用一個案例來演示怎麼使用 WebSocket 構建一個實時的 Web 應用。這是一個簡單的實時多人聊天系統,包括客戶端和服務端的實現。客戶端經過瀏覽器向聊天服務器發起請求,服務器端解析客戶端發出的握手請求併產生應答信息返回給客戶端,從而在客戶端和服務器之間創建鏈接通道。服務器支持廣播功能,每一個聊天用戶發送的信息會實時的發送給全部的用戶,當用戶退出聊天室時,服務器端須要清理相應用戶的鏈接信息,避免資源的泄漏。如下咱們分別從服務器端和客戶端來演示這個 Web 聊天系統的實現,在實現方式上咱們採用了 C# 語言來實現 WebSocket 服務器,而客戶端是一個運行在瀏覽器裏的 HTML 文件。
這個聊天服務器的實現和基於套接字的網絡應用程序很是相似,首先是服務器端要啓動一個套接字監聽來自客戶端的鏈接請求,關鍵的區別在於 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,出於篇幅的考慮,咱們不介紹每一個類的屬性和方法了,文章的附件會給出詳細的源代碼,有興趣的讀者能夠參考。
服務器剛啓動時的畫面以下:
客戶端能夠依據這個信息填寫聊天服務器的鏈接地址,當有客戶端鏈接到聊天服務器上時,服務器會打印出客戶端和服務器的握手信息,每一個客戶的聊天信息也會顯示在服務器的界面上,運行中的聊天服務器的界面以下:
以上咱們簡單描述了實現一個 WebSocket 服務器的最基本的要素,下一節咱們會描述客戶端的實現。
客戶端的實現相對於服務器端的實現來講要簡單得多了,咱們只須要發揮想象去設計 HTML 用戶界面,而後呼叫 WebSocket JavaScript 接口來和 WebSocket 服務器端來交互就能夠了。固然別忘了使用一個支持 HTML5 和 WebSocket 的瀏覽器,在筆者寫這篇文章的時候使用的瀏覽器是 Firefox。客戶端的頁面結構是很是簡潔的,初始運行界面以下:
當頁面初次加載的時候,首先會檢測當前的瀏覽器是否支持 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 = "";
}
};
若是有多個用戶登陸到聊天服務器,客戶端頁面的運行效果以下所示:
至此咱們已經完成了一個完整的 WebSocket 客戶端實現,用戶能夠體驗一下這個聊天室的簡單和快捷,徹底不用考慮頁面的刷新和繁瑣的 Ajax 調用,享受桌面程序的用戶體驗。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 也應該提上日程了,不然咱們在新一輪的軟件革新的浪潮中只能作壁上觀了。
描述名字大小下載方法示例代碼source.zip9KBHTTP
學習
W3C 的 HTML 規範:HTML5 的官方網站
WebSocket.Org:一個專門介紹和推廣 WebSocket 技術的網站
維基百科 WebSocket:維基百科上關於 WebSocket 的介紹
nettuts WebSocket:nettut 網站上關於 WebSocket 的介紹
developerWorks Web development 專區:經過專門關於 Web 技術的文章和教程,擴展您在網站開發方面的技能。
developerWorks Ajax 資源中心:這是有關 Ajax 編程模型信息的一站式中心,包括不少文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這裏找到。
developerWorks Web 2.0 資源中心,這是有關 Web 2.0 相關信息的一站式中心,包括大量 Web 2.0 技術文章、教程、下載和相關技術資源。您還能夠經過 Web 2.0 新手入門欄目,迅速瞭解 Web 2.0 的相關概念。
查看 HTML5 專題,瞭解更多和 HTML5 相關的知識和動向.
討論
加入 developerWorks 中文社區。查看開發人員推進的博客、論壇、組和維基,並與其餘 developerWorks 用戶交流。
做者簡介
黃曉安,IBM 高級軟件工程師,來自 Decision Management Team,具備多年的 Web 開發經驗,熟悉 .Net 平臺和 Java 平臺上的 Web 開發,最近對 HTML5 比較關注。
何亮,IBM CDL 西安軟件工程師,來自 Decision Management Team,具備多年的 Java開發經驗,熟悉 Java web 開發,關注開源軟件和敏捷方法。
許寧,IBM CDL 西安軟件工程師,來自 Decision Management Team,熟悉 Java 平臺上 Web 應用程序開發,瞭解開源軟件和敏捷開發。關注軟件生命週期模型及理論。
協議規範的更新
//================================================
76 草案的 websocket 協議已通過期了,最新的 協議 再也不使用 Sec-WebSocket-Key1 和 Sec-WebSocket-Key2,只有一個base64編碼過的 Sec-WebSocket-Key ,服務器返回安全密鑰的方法是:
將該編碼加上一個固定的字符串 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ,將結果使用 SHA1 產生摘要的二進制值,而後將該二進制值使用 base64 編碼 ,將編碼後的字符串放在名爲 Sec-WebSocket-Accept 的信息頭中返回。
新協議發送的請求還附帶一了一個名爲 Sec-WebSocket-Version 的信息頭來聲明協議版本,例如 :
Sec-WebSocket-Version: 13
一篇不錯的參考:
http://www.cnblogs.com/smark/archive/2012/11/26/2789812.html
下面是一個基於新協議的 websocket 例子,服務器使用 python
http://pan.baidu.com/share/link?shareid=113088&uk=3775016211
=======================client.html==============================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
<
html
>
<
head
>
<
title
>WebSocket</
title
>
<
style
>
html,body{font:normal 0.9em arial,helvetica;}
#log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}
#msg {width:330px;}
</
style
>
<
script
>
var socket;
function init(){
var host = "";
try{
socket = new WebSocket(host);
log('WebSocket - status '+socket.readyState);
socket.onopen = function(msg){ log("Welcome - status "+this.readyState); };
socket.onmessage = function(msg){ log("Received: "+msg.data); };
socket.onclose = function(msg){ log("Disconnected - status "+this.readyState); };
}
catch(ex){ log(ex); }
$("msg").focus();
}
function send(){
var txt,msg;
txt = $("msg");
msg = txt.value;
if(!msg){ alert("Message can not be empty"); return; }
txt.value="";
txt.focus();
try{ socket.send(msg); log('Sent: '+msg); } catch(ex){ log(ex); }
}
function quit(){
log("Goodbye!");
socket.close();
socket=null;
}
// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="<
br
>"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</
script
>
</
head
>
<
body
onload
=
"init()"
>
<
h3
>WebSocket v2.00</
h3
>
<
div
id
=
"log"
></
div
>
<
input
id
=
"msg"
type
=
"textbox"
onkeypress
=
"onkey(event)"
/>
<
button
onclick
=
"send()"
>Send</
button
>
<
button
onclick
=
"quit()"
>Quit</
button
>
</
body
>
</
html
>
|
=================serverWS.py 預先在服務器啓動============================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
import
socket,threading,struct,base64,hashlib
#啓動websocket server
def
InitWebSocketServer():
sock
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try
:
sock.bind((
"localhost"
,
3398
))
#綁定本地地址,端口3398
sock.listen(
100
)
#容許排隊的最大鏈接數爲100,隊列超過100後,請求將被拒絕
except
:
print
(
"Server is already running,quit"
)
sys.exit()
while
True
:
#建立一個死循環,接受客戶端
connection,address
=
sock.accept()
#進入守護狀態
if
(handshake(connection) !
=
False
):
#若是握手失敗,不啓動任務
t
=
threading.Thread(target
=
DoRemoteCommand,args
=
(connection,))
t.start()
#鏈接成功後迴應給客戶端進行握手
def
handshake(client):
headers
=
{}
shake
=
client.recv(
1024
)
if
not
len
(shake):
return
False
header, data
=
shake.split(
'\r\n\r\n'
,
1
)
for
line
in
header.split(
"\r\n"
)[
1
:]:
key, value
=
line.split(
": "
,
1
)
headers[key]
=
value
if
(headers.has_key(
"Sec-WebSocket-Key"
)
=
=
False
):
print
(
"this socket is not websocket,close"
)
client.close()
return
False
szOrigin
=
headers[
"Origin"
]
szKey
=
base64.b64encode(hashlib.sha1(headers[
"Sec-WebSocket-Key"
]
+
'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
).digest())
szHost
=
headers[
"Host"
]
our_handshake
=
"HTTP/1.1 101 Switching Protocols\r\n"
\
"Upgrade:websocket\r\n"
\
"Connection: Upgrade\r\n"
\
"Sec-WebSocket-Accept:"
+
szKey
+
"\r\n"
\
"WebSocket-Origin:"
+
szOrigin
+
"\r\n"
\
"WebSocket-Location: "
+
szHost
+
"/WebManagerSocket\r\n"
\
"\r\n"
# end as \r\n
client.send(our_handshake)
return
True
#接收客戶端發送過來的消息,而且解包
def
RecvData(nNum,client):
try
:
pData
=
client.recv(nNum)
if
not
len
(pData):
return
False
except
:
return
False
else
:
code_length
=
ord
(pData[
1
]) &
127
if
code_length
=
=
126
:
masks
=
pData[
4
:
8
]
data
=
pData[
8
:]
elif
code_length
=
=
127
:
masks
=
pData[
10
:
14
]
data
=
pData[
14
:]
else
:
masks
=
pData[
2
:
6
]
data
=
pData[
6
:]
raw_str
=
""
i
=
0
for
d
in
data:
raw_str
+
=
chr
(
ord
(d) ^
ord
(masks[i
%
4
]))
i
+
=
1
return
raw_str
#打包發送數據給客戶端
def
SendData(pData,client):
if
(pData
=
=
False
):
return
False
else
:
pData
=
str
(pData)
token
=
"\x81"
length
=
len
(pData)
if
length <
126
:
token
+
=
struct.pack(
"B"
, length)
elif
length <
=
0xFFFF
:
token
+
=
struct.pack(
"!BH"
,
126
, length)
else
:
token
+
=
struct.pack(
"!BQ"
,
127
, length)
pData
=
'%s%s'
%
(token,pData)
client.send(pData)
return
True
#這算是客戶端一個循環接受數據而且處理數據的線程
def
DoRemoteCommand(connection):
while
1
:
szBuf
=
RecvData(
8196
,connection)
SendData(
"receive data from client>>> "
+
str
(szBuf),connection)
if
(szBuf
=
=
False
):
break
#qidong :
InitWebSocketServer()
|