1. 程式人生 > >WebSocket介紹,與Socket的區別

WebSocket介紹,與Socket的區別

UNC -c 中間 iis 7 抽象層 public display blank targe

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 請求響應客戶端服務器交互圖
技術分享圖片

使用 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 客戶端連接報文
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 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,可以獲得更快的響應時間,更好的用戶體驗。

WebSocket介紹,與Socket的區別