使用WebSocket構建實時WEB
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協議的雙工通道。
瀏覽器與伺服器通過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版本與瀏覽器支援
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;<br>
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。
5 WebSocket的伺服器實現
WebSocket在網上有非常多實現的例子。以下列出個人會學習研究的伺服器實現。
- Netty實現
- Tomcat7實現
- JavaEE7+Tomcat8實現
- Node.js實現
參考資料
為了防止無良網站的爬蟲抓取文章,特此標識,轉載請註明文章出處。LaplaceDemon/SJQ。
http://www.cnblogs.com/shijiaqi1066/p/3795075.html