使用WebSocket構建實時WEB

爲了防止無良網站的爬蟲抓取文章,特此標識,轉載請註明文章出處。LaplaceDemon/SJQ。javascript

http://www.cnblogs.com/shijiaqi1066/p/3795075.htmlhtml

 

 

1 WebSocket與傳統Web實時通訊技術前端

1.1 WebSockethtml5

HTTP是一種典型的單工模式。即基於Request/Response的方式與服務器進行交互。HTML5提供了瀏覽器與服務端的雙工通訊協議WebSocket。java

 

 

1.2傳統Web實時通訊技術git

  • 輪詢
  • Comet
  • 長輪詢
  • Flash XML Socket

輪詢,對服務器壓力較大,實時性差,效率低下。github

移動端對Flash支持的很是差。Flash常常全局性的崩潰,很不穩定。web

Comet與長輪詢應該是傳統方案中最好的Web實時交互的技術。這兩種技術本質是一種Hack技術。Comet與長輪詢相比有許多缺點。但這不影響它們在傳統實時Web中的應用。這兩項技術最大的問題在於每次交互的HTTP包的header內容過多,真正有實際意義的信息可能不多,從而致使網絡利用率很是低。算法

對於長輪詢技術,因爲離不開請求/響應的模式,因此服務器壓力也會較大。瀏覽器

 

 

 

2 WebSocket協議

WebSocket與HTTP協議都是基於TCP的,都是可靠的協議。WebSocket和Http協議同樣都屬於應用層的協議。WebSocket使用標準的80和443端口,這兩個端口都是防火牆的友好端口因此不須要防火牆的容許。

WebSocket協議的格式爲 "ws://IP:Port" 或者"wss://IP:Port"。其中wss表示進行加密傳輸的WebSocket協議。

WebSocket協議須要進行"握手"。該"握手"階段是經過HTTP協議進行的,"握手"行爲經過Request/Response的Header完成,只須要交換不多的數據,即可以建立基於TCP/IP協議的雙工通道。

wps_clip_image-32524

該圖引用自http://blog.sina.com.cn/s/blog_acddf95d0101beuj.html

 

瀏覽器與服務器經過TCP三次握手創建鏈接,若是鏈接創建失敗,則瀏覽器將收到錯誤消息通知。

TCP創建鏈接成功後,瀏覽器經過HTTP協議傳送WebSocket支持的版本號,協議的字版本號,原始地址,主機地址等等一些列字段給服務器端。

 

 

2.1 WebSocket 握手

WebSocket協議握手協議很是簡單。

實例:運行Tomcat7提供的WebSocket Echo示例程序。當發生Connect時,Chrome中攔截到信息與截包工具中所得的報文以下所示。

 

Chrome所示

wps_clip_image-25577

 

截包工具所示

請求報文

wps_clip_image-27367

說明:

Connection:Upgrade與Upgrade:WebSocket 表示本次請求是要進行WebSocket的握手動做。

Sec-WebSocket-Version首部的值,表示瀏覽器支持的WebSocket版本信息。

Sec-WebSocket-Key首部的值,是一個由客戶端隨機生成的字符串。

 

響應報文

wps_clip_image-13404

說明:

在服務器響應的握手信息中Sec-WebSocket-Accept的值爲服務器經過客戶端Header的Sec-WebSocket-Key的值進行計算並加密的結果。

服務器的響應狀態爲101,表示服務器端已經理解了客戶端的需求,而且客戶端須要根據Upgrade中的協議類型,切換爲新的協議來完成後續的通訊。

通過以上報文的通訊候,基於WebSocket的TCP/IP雙工通道就已經創建了。

 

 

2.2 握手驗證

最老的WebSocket草案標準中是沒有安全key。草案7.五、7.6中有兩個安全key。草案10中只有一個安全key。

7.五、7.6中HTTP頭中的"Sec-WebSocket-Key1"與"Sec-WebSocket-Key2"在10中合併爲了一個"Sec-WebSocket-Key"。HTTP頭中Upgrade的值由"WebSocket"修改成了"websocket"。HTTP頭中的"-Origin"修改成了"Sec-WebSocket-Origin"。

增長了HTTP頭"Sec-WebSocket-Accept",用來返回原來草案7.五、7.6服務器返回給客戶端的握手驗證,原來是之內容的形式返回,如今是放到了HTTP頭中。

服務器生成驗證的方式變化較大。

舊版WebSocket

wps_clip_image-32548

舊版生成Token的方法以下:

取出Sec-WebSocket-Key1中的全部數字字符造成一個數值,這裏是1427964708,而後除以Key1中的空格數目,獲得一個數值,保留該數值整數位,獲得數值N1;對Sec-WebSocket-Key2採起一樣的算法,獲得第二個整數N2;把N1和N2按照Big-Endian字符序列鏈接起來,而後再與另一個Key3鏈接,獲得一個原始序列ser_key。Key3是指在握手請求最後,有一個8字節的奇怪的字符串「;」######」,這個就是Key3。而後對ser_key進行一次md5運算得出一個16字節長的digest,這就是老版本協議須要的token,而後將這個token附在握手消息的最後發送回Client,便可完成握手。

新版WebSocket

wps_clip_image-8713

新版生成Token的方法以下:

首先服務器將key(長度24)截取出來,如4tAjitqO9So2Wu8lkrsq3w==,用它和自定義的一個字符串(長度36)258EAFA5-E914-47DA-95CA-C5AB0DC85B11鏈接起來,而後把這一字符串進行SHA-1算法加密,獲得長度爲20字節的二進制數據,再將這些數據通過Base64編碼,最終獲得服務端的密鑰,也就是ser_key。服務器將ser_key附在返回值Sec-WebSocket-Accept後,至此握手成功。

 

 

2.3 數據報文格式

舊版協議比較簡單,僅僅是在原始數據前加了個’\x00′,在最後面加了個’\xFF’,即假如Client發送一個字符串’test’,實際上WebSocket Server收到的數據是:’x00test\xFF’,因此只須要剝離掉首尾那兩個字符就能夠了。

新版的協議對這部分規定比較複雜,如下是其格式標準:(下圖在Firefox可能會出現錯亂,請換用Chrome)

wps_clip_image-1427

FIN:1位,用來代表這是一個消息的最後的消息片段,固然第一個消息片段也多是最後的一個消息片段;

RSV1, RSV2, RSV3:分別都是1位,若是雙方之間沒有約定自定義協議,那麼這幾位的值都必須爲0,不然必須斷掉WebSocket鏈接;

Opcode:4位操做碼,定義有效負載數據,若是收到了一個未知的操做碼,鏈接也必須斷掉,如下是定義的操做碼:

%x0 表示連續消息片段

%x1 表示文本消息片段

%x2 表未二進制消息片段

%x3-7 爲未來的非控制消息片段保留的操做碼

%x8 表示鏈接關閉

%x9 表示心跳檢查的ping

%xA 表示心跳檢查的pong

%xB-F 爲未來的控制消息片段的保留操做碼

Mask:1位,定義傳輸的數據是否有加掩碼,若是設置爲1,掩碼鍵必須放在masking-key區域,客戶端發送給服務端的全部消息,此位的值都是1;

Payload length:傳輸數據的長度,以字節的形式表示:7位、7+16位、或者7+64位。若是這個值以字節表示是0-125這個範圍,那這個值就表示傳輸數據的長度;若是這個值是126,則隨後的兩個字節表示的是一個16進制無符號數,用來表示傳輸數據的長度;若是這個值是127,則隨後的是8個字節表示的一個64位無符合數,這個數用來表示傳輸數據的長度。多字節長度的數量是以網絡字節的順序表示。負載數據的長度爲擴展數據及應用數據之和,擴展數據的長度可能爲0,於是此時負載數據的長度就爲應用數據的長度。

Masking-key:0或4個字節,客戶端發送給服務端的數據,都是經過內嵌的一個32位值做爲掩碼的;掩碼鍵只有在掩碼位設置爲1的時候存在。 Payload data: (x+y)位,負載數據爲擴展數據及應用數據長度之和。 Extension data:x位,若是客戶端與服務端之間沒有特殊約定,那麼擴展數據的長度始終爲0,任何的擴展都必須指定擴展數據的長度,或者長度的計算方式,以及在握手時如何肯定正確的握手方式。若是存在擴展數據,則擴展數據就會包括在負載數據的長度以內。 Application data:y位,任意的應用數據,放在擴展數據以後,應用數據的長度=負載數據的長度-擴展數據的長度。

 

 

2.4 WebSocket版本與瀏覽器支持

wps_clip_image-13432

表格來自於:http://zh.wikipedia.org/wiki/WebSocket

 

 

 

3 Web前端相關的WebSocket

3.1 W3C API定義

Websocket的API非法簡單,如下是W3C的定義

wps_clip_image-8369

enum BinaryType { "blob", "arraybuffer" };
[Constructor(DOMString url, optional (DOMString or DOMString[]) protocols)]
interface WebSocket : EventTarget {
  readonly attribute DOMString url;
  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSING = 2;
  const unsigned short CLOSED = 3;
readonly attribute unsigned short readyState; readonly attribute unsigned long bufferedAmount; // networking attribute EventHandler onopen; attribute EventHandler onerror; attribute EventHandler onclose; readonly attribute DOMString extensions; readonly attribute DOMString protocol; void close([Clamp] optional unsigned shortcode, optional DOMString reason); // messaging attribute EventHandler onmessage; attribute BinaryType binaryType; void send(DOMString data); void send(Blob data); void send(ArrayBuffer data); voidsend(ArrayBufferView data); };

 

 

3.2 實際使用的簡單例子

例1

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);
}

 

 

例2

<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript"type="text/javascript">

    var wsUri ="ws://echo.websocket.org/";
    var output;
function init() { output = document.getElementById("output"); testWebSocket(); } function testWebSocket() { websocket = new WebSocket(wsUri); 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) { writeToScreen("CONNECTED"); doSend("WebSocket rocks"); } function onClose(evt) { writeToScreen("DISCONNECTED"); } function onMessage(evt) { writeToScreen('<span style="color: blue;">RESPONSE: '+ evt.data+'</span>'); websocket.close(); } function onError(evt) { writeToScreen('<span style="color: red;">ERROR:</span> '+ evt.data); } function doSend(message) { writeToScreen("SENT: " + message); websocket.send(message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load", init, false); </script> <h2>WebSocket Test</h2> <div id="output"></div> </html>

 

代碼簡述

使用new建立WebSocket對象。

var wsUri ="ws://echo.websocket.org/";
websocket = new WebSocket(wsUri);

 

 

WebSocket對象一共支持四個事件響應方法 onopen, onmessage, oncloseonerror。這樣不會阻塞UI,獲得更好的用戶體驗。

當瀏覽器和服務器鏈接成功後,會觸發onopen事件。

websocket.onopen = function(evt) {};

 

若是鏈接失敗,發送、接收數據失敗或者處理數據出現錯誤,會觸發onerror事件。

websocket.onerror = function(evt) {};

 

當瀏覽器接收到服務器發送過來的數據時,就會觸發onmessage事件。參數evt中包含服務器傳輸過來的數據;

websocket.onmessage = function(evt) {};

 

當瀏覽器接收到服務器發送的關閉鏈接請求時,就會觸發onclose事件。

websocket.onclose = function(evt) {};

 

 

在實際應用中還須要考慮心跳包的問題。

 

 

 

4 WebSocket的缺點

最大的問題就是瀏覽器兼容性問題。低版本IE瀏覽器不支持該技術,直到IE10纔開始支持WebSocket技術。

固然,解決方案是對於低版本瀏覽器可使用Flash來模擬WebSocket。

如:web-socket-js 地址:https://github.com/gimite/web-socket-js/

 

 

 

5 WebSocket的服務器實現

WebSocket在網上有很是多實現的例子。如下列出我的會學習研究的服務器實現。

  • Netty實現
  • Tomcat7實現
  • JavaEE7+Tomcat8實現
  • Node.js實現

 

 

 

 

參考資料

http://zh.wikipedia.org/wiki/WebSocket

http://www.web-tinker.com/search/websocket/1.html

http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/

http://hackecho.com/2012/04/new-hybi-10-protocol-of-websocket/

http://www.zendstudio.net/archives/web-socket-heartbeat-package/

 

 

 

爲了防止無良網站的爬蟲抓取文章,特此標識,轉載請註明文章出處。LaplaceDemon/SJQ。

http://www.cnblogs.com/shijiaqi1066/p/3795075.html

相關文章
相關標籤/搜索