為了防止無良網站的爬蟲抓取文章,特此標識,轉載請注明文章出處。LaplaceDemon/SJQ。
http://www.cnblogs.com/shijiaqi1066/p/3795075.html
1 WebSocket與傳統Web實時通信技術
1.1 WebSocket
HTTP是一種典型的單工模式。即基於Request/Response的方式與服務器進行交互。HTML5提供了瀏覽器與服務端的雙工通信協議WebSocket。
1.2傳統Web實時通信技術
- 輪詢
- Comet
- 長輪詢
- Flash XML Socket
輪詢,對服務器壓力較大,實時性差,效率低下。
移動端對Flash支持的非常差。Flash經常全局性的崩潰,很不穩定。
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協議的雙工通道。
該圖引用自http://blog.sina.com.cn/s/blog_acddf95d0101beuj.html
瀏覽器與服務器通過TCP三次握手建立連接,如果連接建立失敗,則瀏覽器將收到錯誤消息通知。
TCP建立連接成功后,瀏覽器通過HTTP協議傳送WebSocket支持的版本號,協議的字版本號,原始地址,主機地址等等一些列字段給服務器端。
2.1 WebSocket 握手
WebSocket協議握手協議非常簡單。
實例:運行Tomcat7提供的WebSocket Echo示例程序。當發生Connect時,Chrome中攔截到信息與截包工具中所得的報文如下所示。
Chrome所示
截包工具所示
請求報文
說明:
Connection:Upgrade與Upgrade:WebSocket 表示本次請求是要進行WebSocket的握手動作。
Sec-WebSocket-Version首部的值,表示瀏覽器支持的WebSocket版本信息。
Sec-WebSocket-Key首部的值,是一個由客戶端隨機生成的字符串。
響應報文
說明:
在服務器響應的握手信息中Sec-WebSocket-Accept的值為服務器通過客戶端Header的Sec-WebSocket-Key的值進行計算並加密的結果。
服務器的響應狀態為101,表示服務器端已經理解了客戶端的需求,並且客戶端需要根據Upgrade中的協議類型,切換為新的協議來完成后續的通信。
經過以上報文的通信候,基於WebSocket的TCP/IP雙工通道就已經建立了。
2.2 握手驗證
最老的WebSocket草案標准中是沒有安全key。草案7.5、7.6中有兩個安全key。草案10中只有一個安全key。
7.5、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.5、7.6服務器返回給客戶端的握手驗證,原來是以內容的形式返回,現在是放到了HTTP頭中。
服務器生成驗證的方式變化較大。
舊版WebSocket
舊版生成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
新版生成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)
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版本與瀏覽器支持
表格來自於:http://zh.wikipedia.org/wiki/WebSocket
3 Web前端相關的WebSocket
3.1 W3C API定義
Websocket的API非法簡單,以下是W3C的定義
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, onclose和onerror。這樣不會阻塞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