所周知,Web 應用的交互過程一般是客戶端經過瀏覽器發出一個請求,服務器端接收請求後進行處理並返回結果給客戶端,客戶端瀏覽器將信息呈現,這種機制對於信息變化不是特別頻繁的應用尚可,但對於實時要求高、海量併發的應用來講顯得捉襟見肘,尤爲在當前業界移動互聯網蓬勃發展的趨勢下,高併發與用戶實時響應是 Web 應用常常面臨的問題,好比金融證券的實時信息,Web 導航應用中的地理位置獲取,社交網絡的實時消息推送等。
傳統的請求-響應模式的 Web 開發在處理此類業務場景時,一般採用實時通信方案,常見的是:
輪詢,原理簡單易懂,就是客戶端經過必定的時間間隔以頻繁請求的方式向服務器發送請求,來保持客戶端和服務器端的數據同步。問題很明顯,當客戶端以固定頻率向服務器端發送請求時,服務器端的數據可能並無更新,帶來不少無謂請求,浪費帶寬,效率低下。
基於 Flash,AdobeFlash 經過本身的 Socket 實現完成數據交換,再利用 Flash 暴露出相應的接口爲 JavaScript 調用,從而達到實時傳輸目的。此方式比輪詢要高效,且由於 Flash 安裝率高,應用場景比較普遍,但在移動互聯網終端上 Flash 的支持並很差。IOS 系統中沒有 Flash 的存在,在 Android 中雖然有 Flash 的支持,但實際的使用效果差強人意,且對移動設備的硬件配置要求較高。2012 年 Adobe 官方宣佈再也不支持 Android4.1+系統,宣告了 Flash 在移動終端上的死亡。
從上文能夠看出,傳統 Web 模式在處理高併發及實時性需求的時候,會遇到難以逾越的瓶頸,咱們須要一種高效節能的雙向通訊機制來保證數據的實時傳輸。在此背景下,基於 HTML5 規範的、有 Web TCP 之稱的 WebSocket 應運而生。
早期 HTML5 並無造成業界統一的規範,各個瀏覽器和應用服務器廠商有着各異的相似實現,如 IBM 的 MQTT,Comet 開源框架等,直到 2014 年,HTML5 在 IBM、微軟、Google 等巨頭的推進和協做下終於塵埃落地,正式從草案落實爲實際標準規範,各個應用服務器及瀏覽器廠商逐步開始統一,在 JavaEE7 中也實現了 WebSocket 協議,從而不管是客戶端仍是服務端的 WebSocket 都已完備,讀者能夠查閱
HTML5 規範,熟悉新的 HTML 協議規範及 WebSocket 支持。
WebSocket 機制
如下簡要介紹一下 WebSocket 的原理及運行機制。
WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與服務器全雙工通訊,能更好的節省服務器資源和帶寬並達到實時通信,它創建在 TCP 之上,同 HTTP 同樣經過 TCP 來傳輸數據,可是它和 HTTP 最大不一樣是:
WebSocket 是一種雙向通訊協議,在創建鏈接後,WebSocket 服務器和 Browser/Client Agent 都能主動的向對方發送或接收數據,就像 Socket 同樣;
WebSocket 須要相似 TCP 的客戶端和服務器端經過握手鍊接,鏈接成功後才能相互通訊。
非 WebSocket 模式傳統 HTTP 客戶端與服務器的交互以下圖所示:
圖 1. 傳統 HTTP 請求響應客戶端服務器交互圖
使用 WebSocket 模式客戶端與服務器的交互以下圖:
圖 2.WebSocket 請求響應客戶端服務器交互圖
上圖對比能夠看出,相對於傳統 HTTP 每次請求-應答都須要客戶端與服務端創建鏈接的模式,WebSocket 是相似 Socket 的 TCP 長鏈接的通信模式,一旦 WebSocket 鏈接創建後,後續數據都以幀序列的形式傳輸。在客戶端斷開 WebSocket 鏈接或 Server 端斷掉鏈接前,不須要客戶端和服務端從新發起鏈接請求。在海量併發及客戶端與服務器交互負載流量大的狀況下,極大的節省了網絡帶寬資源的消耗,有明顯的性能優點,且客戶端發送和接受消息是在同一個持久鏈接上發起,實時性優點明顯。
在客戶端,new WebSocket 實例化一個新的 WebSocket 客戶端對象,鏈接相似 ws://yourdomain:port/path 的服務端 WebSocket URL,WebSocket 客戶端對象會自動解析並識別爲 WebSocket 請求,從而鏈接服務端端口,執行雙方握手過程,客戶端發送數據格式相似:
清單 1.WebSocket 客戶端鏈接報文
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
能夠看到,客戶端發起的 WebSocket 鏈接報文相似傳統 HTTP 報文,」Upgrade:websocket」參數值代表這是 WebSocket 類型請求,「Sec-WebSocket-Key」是 WebSocket 客戶端發送的一個 base64 編碼的密文,要求服務端必須返回一個對應加密的「Sec-WebSocket-Accept」應答,不然客戶端會拋出「Error during WebSocket handshake」錯誤,並關閉鏈接。
服務端收到報文後返回的數據格式相似:
清單 2.WebSocket 服務端響應報文
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
「Sec-WebSocket-Accept」的值是服務端採用與客戶端一致的密鑰計算出來後返回客戶端的,「HTTP/1.1 101 Switching Protocols」表示服務端接受 WebSocket 協議的客戶端鏈接,通過這樣的請求-響應處理後,客戶端服務端的 WebSocket 鏈接握手成功, 後續就能夠進行 TCP 通信了。讀者能夠查閱
WebSocket 協議棧瞭解 WebSocket 客戶端和服務端更詳細的交互數據格式。
在開發方面,WebSocket API 也十分簡單,咱們只須要實例化 WebSocket,建立鏈接,而後服務端和客戶端就能夠相互發送和響應消息,在下文 WebSocket 實現及案例分析部分,能夠看到詳細的 WebSocket API 及代碼實現。
WebSocket 實現
如上文所述,WebSocket 的實現分爲客戶端和服務端兩部分,客戶端(一般爲瀏覽器)發出 WebSocket 鏈接請求,服務端響應,實現相似 TCP 握手的動做,從而在瀏覽器客戶端和 WebSocket 服務端之間造成一條 HTTP 長鏈接快速通道。二者之間後續進行直接的數據互相傳送,再也不須要發起鏈接和相應。
清單 3.WebSocket 服務端 API 示例
@ServerEndpoint("/echo")
public class EchoEndpoint {
@OnOpen
public void onOpen(Session session) throws IOException {
//如下代碼省略...
}
@OnMessage
public String onMessage(String message) {
//如下代碼省略...
}
@Message(maxMessageSize=6)
public void receiveMessage(String s) {
//如下代碼省略...
}
@OnError
public void onError(Throwable t) {
//如下代碼省略...
}
@OnClose
public void onClose(Session session, CloseReason reason) {
//如下代碼省略...
}
}
清單 5.WebSocket 客戶端 API 示例
var ws = new WebSocket(「ws://echo.websocket.org」);
ws.onopen = function(){ws.send(「Test!」); };
ws.onmessage = function(evt){console.log(evt.data);ws.close();};
ws.onclose = function(evt){console.log(「WebSocketClosed!」);};
ws.onerror = function(evt){console.log(「WebSocketError!」);};