WebSocket介紹,與Socket的區別

NO IMAGE

WebSocket介紹與原理

WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊(full-duplex)。一開始的握手需要藉助HTTP請求完成。

——百度百科

目的:即時通訊,替代輪詢

網站上的即時通訊是很常見的,比如網頁的QQ,聊天系統等。按照以往的技術能力通常是採用輪詢、Comet技術解決。

HTTP協議是非持久化的,單向的網路協議,在建立連線後只允許瀏覽器向伺服器發出請求後,伺服器才能返回相應的資料。當需要即時通訊時,通過輪詢在特定的時間間隔(如1秒),由瀏覽器向伺服器傳送Request請求,然後將最新的資料返回給瀏覽器。這樣的方法最明顯的缺點就是需要不斷的傳送請求,而且通常HTTP request的Header是非常長的,為了傳輸一個很小的資料 需要付出巨大的代價,是很不合算的,佔用了很多的寬頻。

缺點:會導致過多不必要的請求,浪費流量和伺服器資源,每一次請求、應答,都浪費了一定流量在相同的頭部資訊上

然而WebSocket的出現可以彌補這一缺點。在WebSocket中,只需要伺服器和瀏覽器通過HTTP協議進行一個握手的動作,然後單獨建立一條TCP的通訊通道進行資料的傳送。

原理

WebSocket同HTTP一樣也是應用層的協議,但是它是一種雙向通訊協議,是建立在TCP之上的。

連線過程 —— 握手過程

  • 1. 瀏覽器、伺服器建立TCP連線,三次握手。這是通訊的基礎,傳輸控制層,若失敗後續都不執行。
  • 2. TCP連線成功後,瀏覽器通過HTTP協議向伺服器傳送WebSocket支援的版本號等資訊。(開始前的HTTP握手
  • 3. 伺服器收到客戶端的握手請求後,同樣採用HTTP協議回饋資料。
  • 4. 當收到了連線成功的訊息後,通過TCP通道進行傳輸通訊。

WebSocket與HTTP的關係

相同點

  • 1. 都是一樣基於TCP的,都是可靠性傳輸協議。
  • 2. 都是應用層協議。

不同點

  • 1. WebSocket是雙向通訊協議,模擬Socket協議,可以雙向傳送或接受資訊。HTTP是單向的。
  • 2. WebSocket是需要握手進行建立連線的。

聯絡

WebSocket在建立握手時,資料是通過HTTP傳輸的。但是建立之後,在真正傳輸時候是不需要HTTP協議的。

WebSocket與Socket的關係

Socket其實並不是一個協議,而是為了方便使用TCP或UDP而抽象出來的一層,是位於應用層和傳輸控制層之間的一組介面。

Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

當兩臺主機通訊時,必須通過Socket連線,Socket則利用TCP/IP協議建立TCP連線。TCP連線則更依靠於底層的IP協議,IP協議的連線則依賴於鏈路層等更低層次。

WebSocket則是一個典型的應用層協議。

區別

Socket是傳輸控制層協議,WebSocket是應用層協議。

HTML5與WebSocket的關係

WebSocket API 是 HTML5 標準的一部分, 但這並不代表 WebSocket 一定要用在 HTML 中,或者只能在基於瀏覽器的應用程式中使用。

實際上,許多語言、框架和伺服器都提供了 WebSocket 支援,例如:

  • * 基於 C 的 libwebsocket.org
  • * 基於 Node.js 的 Socket.io
  • * 基於 Python 的 ws4py
  • * 基於 C 的 WebSocket
  • * Apache 對 WebSocket 的支援: Apache Module mod_proxy_wstunnel
  • * Nginx 對 WebSockets 的支援: NGINX as a WebSockets Proxy 、 NGINX Announces Support for WebSocket Protocol 、WebSocket proxying
  • * lighttpd 對 WebSocket 的支援:mod_websocket

WebSocket 機制

以下簡要介紹一下 WebSocket 的原理及執行機制。

WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與伺服器全雙工通訊,能更好的節省伺服器資源和頻寬並達到實時通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸資料,但是它和 HTTP 最大不同是:

  • WebSocket 是一種雙向通訊協議,在建立連線後,WebSocket 伺服器和 Browser/Client Agent 都能主動的向對方傳送或接收資料,就像 Socket 一樣;
  • WebSocket 需要類似 TCP 的客戶端和伺服器端通過握手連線,連線成功後才能相互通訊。

非 WebSocket 模式傳統 HTTP 客戶端與伺服器的互動如下圖所示:

圖 1. 傳統 HTTP 請求響應客戶端伺服器互動圖

圖 1. 傳統 HTTP 請求響應客戶端伺服器互動圖

使用 WebSocket 模式客戶端與伺服器的互動如下圖:

圖 2.WebSocket 請求響應客戶端伺服器互動圖

圖 2.WebSocket 請求響應客戶端伺服器互動圖

上圖對比可以看出,相對於傳統 HTTP 每次請求-應答都需要客戶端與服務端建立連線的模式,WebSocket 是類似 Socket 的 TCP 長連線的通訊模式,一旦 WebSocket 連線建立後,後續資料都以幀序列的形式傳輸。在客戶端斷開 WebSocket 連線或 Server 端斷掉連線前,不需要客戶端和服務端重新發起連線請求。在海量併發及客戶端與伺服器互動負載流量大的情況下,極大的節省了網路頻寬資源的消耗,有明顯的效能優勢,且客戶端傳送和接受訊息是在同一個持久連線上發起,實時性優勢明顯。

我們再通過客戶端和服務端互動的報文看一下 WebSocket 通訊與傳統 HTTP 的不同:

在客戶端,new WebSocket 例項化一個新的 WebSocket 客戶端物件,連線類似 ws://yourdomain:port/path 的服務端 WebSocket URL,WebSocket 客戶端物件會自動解析並識別為 WebSocket 請求,從而連線服務端埠,執行雙方握手過程,客戶端傳送資料格式類似:

清單 1.WebSocket 客戶端連線報文
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 API 也十分簡單,我們只需要例項化 WebSocket,建立連線,然後服務端和客戶端就可以相互傳送和響應訊息,在下文 WebSocket 實現及案例分析部分,可以看到詳細的 WebSocket API 及程式碼實現。

WebSocket 實現

如上文所述,WebSocket 的實現分為客戶端和服務端兩部分,客戶端(通常為瀏覽器)發出 WebSocket 連線請求,服務端響應,實現類似 TCP 握手的動作,從而在瀏覽器客戶端和 WebSocket 服務端之間形成一條 HTTP 長連線快速通道。兩者之間後續進行直接的資料互相傳送,不再需要發起連線和相應。

以下簡要描述 WebSocket 服務端 API 及客戶端 API。

WebSocket 服務端 API

WebSocket 服務端在各個主流應用伺服器廠商中已基本獲得符合 JEE JSR356 標準規範 API 的支援,以下列舉了部分常見的商用及開源應用伺服器對 WebSocket Server 端的支援情況:

表 1.WebSocket 服務端支援
廠商 應用伺服器 備註
IBM WebSphere WebSphere 8.0 以上版本支援,7.X 之前版本結合 MQTT 支援類似的 HTTP 長連線
甲骨文 WebLogic WebLogic 12c 支援,11g 及 10g 版本通過 HTTP Publish 支援類似的 HTTP 長連線
微軟 IIS IIS 7.0 支援
Apache Tomcat Tomcat 7.0.5+支援,7.0.2X 及 7.0.3X 通過自定義 API 支援
  Jetty Jetty 7.0+支援

以下我們使用 Tomcat7.0.5 版本的服務端示例程式碼說明 WebSocket 服務端的實現:

JSR356 的 WebSocket 規範使用 javax.websocket.*的 API,可以將一個普通 Java 物件(POJO)使用 @ServerEndpoint 註釋作為 WebSocket 伺服器的端點,程式碼示例如下:

清單 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) {
//以下程式碼省略...
} 
}

程式碼解釋:

上文的簡潔程式碼即建立了一個 WebSocket 的服務端,@ServerEndpoint(“/echo”) 的 annotation 註釋端點表示將 WebSocket 服務端執行在 ws://[Server 端 IP 或域名]:[Server 埠]/websockets/echo 的訪問端點,客戶端瀏覽器已經可以對 WebSocket 客戶端 API 發起 HTTP 長連線了。

使用 ServerEndpoint 註釋的類必須有一個公共的無引數建構函式,@onMessage 註解的 Java 方法用於接收傳入的 WebSocket 資訊,這個資訊可以是文字格式,也可以是二進位制格式。

OnOpen 在這個端點一個新的連線建立時被呼叫。引數提供了連線的另一端的更多細節。Session 表明兩個 WebSocket 端點對話連線的另一端,可以理解為類似 HTTPSession 的概念。

OnClose 在連線被終止時呼叫。引數 closeReason 可封裝更多細節,如為什麼一個 WebSocket 連線關閉。

更高階的定製如 @Message 註釋,MaxMessageSize 屬性可以被用來定義訊息位元組最大限制,在示例程式中,如果超過 6 個位元組的資訊被接收,就報告錯誤和連線關閉。

注意:早期不同應用伺服器支援的 WebSocket 方式不盡相同,即使同一廠商,不同版本也有細微差別,如
Tomcat 伺服器 7.0.5 以上的版本都是標準 JSR356 規範實現,而 7.0.2x/7.0.3X 的版本使用自定義 API (WebSocketServlet 和 StreamInbound, 前者是一個容器,用來初始化 WebSocket 環境;後者是用來具體處理 WebSocket 請求和響應,詳見案例分析部分),且 Tomcat7.0.3x
與 7.0.2x 的 createWebSocketInbound 方法的定義不同,增加了一個 HttpServletRequest 引數,使得可以從 request 引數中獲取更多 WebSocket 客戶端的資訊,如下程式碼所示:

清單 4.Tomcat7.0.3X 版本 WebSocket API
public class EchoServlet extends WebSocketServlet {
@Override
protected StreamInbound createWebSocketInbound(String subProtocol,
HttpServletRequest request) {
//以下程式碼省略....
return new MessageInbound() {
//以下程式碼省略....
}
protected void onBinaryMessage(ByteBuffer buffer)
throws IOException {
//以下程式碼省略...
}
protected void onTextMessage(CharBuffer buffer) throws IOException {
getWsOutbound().writeTextMessage(buffer);
//以下程式碼省略...
}
};
}
}

因此選擇 WebSocket 的 Server 端重點需要選擇其版本,通常情況下,更新的版本對 WebSocket 的支援是標準 JSR 規範 API,但也要考慮開發易用性及老版本程式移植性等方面的問題,如下文所述的客戶案例,就是因為客戶要求統一應用伺服器版本所以使用的 Tomcat 7.0.3X 版本的 WebSocketServlet 實現,而不是 JSR356 的 @ServerEndpoint 註釋端點。

WebSocket 客戶端 API

對於 WebSocket 客戶端,主流的瀏覽器(包括 PC 和移動終端)現已都支援標準的 HTML5 的 WebSocket API,這意味著客戶端的 WebSocket JavaScirpt 指令碼具備良好的一致性和跨平臺特性,以下列舉了常見的瀏覽器廠商對 WebSocket 的支援情況:

表 2.WebSocket 客戶端支援
瀏覽器 支援情況
Chrome Chrome version 4 支援
Firefox Firefox version 5 支援
IE IE version 10 支援
Safari IOS 5 支援
Android Brower Android 4.5 支援

客戶端 WebSocket API 基本上已經在各個主流瀏覽器廠商中實現了統一,因此使用標準 HTML5 定義的 WebSocket 客戶端的 JavaScript API 即可,當然也可以使用業界滿足 WebSocket 標準規範的開源框架,如 Socket.io。

以下以一段程式碼示例說明 WebSocket 的客戶端實現:

清單 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!”);};

第一行程式碼是在申請一個 WebSocket 物件,引數是需要連線的伺服器端的地址,同 HTTP 協議開頭一樣,WebSocket 協議的 URL 使用 ws://開頭,另外安全的 WebSocket 協議使用 wss://開頭。

第二行到第五行為 WebSocket 物件註冊訊息的處理函式,WebSocket 物件一共支援四個訊息 onopen, onmessage, onclose 和 onerror,有了這 4 個事件,我們就可以很容易很輕鬆的駕馭 WebSocket。

當 Browser 和 WebSocketServer 連線成功後,會觸發 onopen 訊息;如果連線失敗,傳送、接收資料失敗或者處理資料出現錯誤,browser 會觸發 onerror 訊息;當 Browser 接收到 WebSocketServer 傳送過來的資料時,就會觸發 onmessage 訊息,引數 evt 中包含 Server 傳輸過來的資料;當 Browser 接收到 WebSocketServer 端傳送的關閉連線請求時,就會觸發 onclose 訊息。我們可以看出所有的操作都是採用非同步回撥的方式觸發,這樣不會阻塞
UI,可以獲得更快的響應時間,更好的使用者體驗。