WebSocket簡介與最佳實踐

1、背景:源於一個需求

需求:「客戶端掃二維碼,將客戶端參數信息展現在web端」前端

問題焦點:實時的web應用,Client 跟 Server 之間,實時的雙向通訊。react


2、傳統解決方案?

輪詢(Polling):又稱按期輪詢

Client 按期向 Server 發送請求,以此保持與 Server 端數據的同步。典型應用場景:nginx

  • Ajax技術,局部刷新Web頁面git

缺點:github

  • 帶寬和 CPU 資源:因爲 Client 按期向 Server 發送請求,當 Server 端沒有數據更新時,Client仍舊發送請求,這形成帶寬的浪費以及Server端CPU的耗費web

  • 實時性:輪詢間隔內,會有數據延遲spring


長輪詢(Long Polling):對普通輪詢的改進和提升

目標:節省帶寬,下降無效的網絡傳輸。npm

基本原理:segmentfault

  • 保持鏈接:HTTP 層,保持鏈接,Server 接收到 Client 的請求以後,若是沒有數據更新,則鏈接保持一段時間;後端

  • 直到有數據更新或者鏈接超時,這樣能夠減小無效的 Client 與 Server 之間的交互;

  • 經過保持鏈接,減小 Request 和 Response 的數量,節省帶寬;

缺陷:

  • 節省帶寬,效果有限:HTTP的數據包HEAD部分數據量很大(400+Byte),但真正有效的數據不多(10Byte),這樣的數據包在網絡中週期傳輸,浪費帶寬。

  • 數據更新頻繁場景下,數據實時性:當Server端數據頻繁更新時,Server端必須等待下一個請求到來,才能發送更新的數據,這中間的延遲最高爲 1.5 x RTT(往返時間)

  • 網路擁塞場景下,等待時間更久,由於須要從新創建鏈接;


本質緣由:

  • 鏈接保持:須要從新創建 HTTP 鏈接(HTTP 1.1 只能緩解,沒法從原理上,完全解決)

  • 數據格式:仍然爲應用層的數據格式, HTTP HEADER 數據佔用比較大

事件流方式(SSE)

  • 經過 SSE ,客戶端能夠自動獲取數據更新,而不用重複發送HTTP請求。一旦鏈接創建,「事件」便會自動被推送到客戶端。服務器端SSE經過 事件流(Event Stream) 的格式產生並推送事件。

  • 能夠實現服務器到客戶端的單向數據通訊。

  • SSE相較於輪詢具備較好的實時性,使用方法也很是簡便。

缺點:

  • 大併發狀況下,服務器可能會宕機。

  • SSE只支持服務器到客戶端單向的事件推送,並且全部版本的IE(包括到目前爲止的Microsoft Edge)都不支持SSE。若是須要強行支持IE和部分移動端瀏覽器,能夠嘗試 EventSource Polyfill(本質上仍然是輪詢)

3、WebSocket是什麼?

B/S的請求-響應模式

  • 傳統Web中,是由 Browser 主動向 Server 端發送請求,以此得到 Server 端數據;

  • 若是要實現實時通訊,實時獲取 Server 端的數據,一般是 Client 端按期發送HTTP請求,Server端進行響應並返回數據;

  • HTTP協議:基於請求/響應模式的、單向的、無狀態的、應用層協議;

HTTP協議爲何不容許Server主動向Client推送數據?

若是容許 Server 向 Client 主動推送數據,則 Client 很容易受到攻擊;特別是廣告商會將廣告信息,強行推送給 Client,所以 HTTP 的單向特性是必要的。

WebSocket簡介

WebSocket協議借用HTTP協議的101( switch protocol) 狀態碼來達到協議轉換,切換爲WebSocket協議,它自己是基於Tcp協議的。

特色:

  • Client 跟 Server 之間,雙向通訊技術,Client 和 Server,均可以主動發起通訊

  • 是一種網絡通訊協議

  • 創建在傳輸層 TCP 協議之上

優勢:

  • 節省帶寬;(HTTP 協議的 HEAD 比較大)

  • 節省服務器CPU資源;(HTTP 協議的 Polling 方式,即便 Server 沒有數據也要接收 Request)

下圖展現了Polling和WebSocket兩種模式下,Web應用的效率:


4、協議

首先,WebSocket是一個持久化的協議,相對於HTTP這種非持久的協議來講。HTTP的生命週期經過Request來界定,也就是一個Request一個Response,那麼在HTTP1.0中,此次HTTP請求就結束了。HTTP1.1進行了改進,可使用keep-alive保持鏈接,可是仍舊是一個Request = 一個Response, Response顯得很是的被動,不能主動發起。

握手

來自客戶端的握手信息:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13複製代碼

重點請求首部含義:

  • Connection: Upgrade:表示要升級協議

  • Upgrade: websocket:表示要升級到 websocket 協議。

  • Sec-WebSocket-Version: 13:表示 websocket 的版本。若是服務端不支持該版本,須要返回一個 Sec-WebSocket-Versionheader ,裏面包含服務端支持的版本號。

  • Sec-WebSocket-Key:是一個Base64 encode的值,由瀏覽器隨機生成,與後面服務端響應首部的 Sec-WebSocket-Accept 是配套的,用於服務端校驗,提供基本的防禦,好比惡意的鏈接。

  • Sec_WebSocket-Protocol:是一個用戶定義的字符串,用來區分同 URL 下,不一樣的服務所須要的協議。

來自服務器的握手信息:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat複製代碼

重點響應首部含義:

  • Sec-WebSocket-Accept 根據客戶端請求首部的 Sec-WebSocket-Key 計算出來。

  • Connection和Upgrade依然是表示協議升級爲websocket協議。

切分數據幀

WebSocket 客戶端、服務端通訊的最小單位是 幀(frame),由 1 個或多個幀組成一條完整的消息(message)。

  • 發送端:將消息切割成多個幀,併發送給服務端;

  • 接收端:接收消息幀,並將關聯的幀從新組裝成完整的消息;

數據傳遞

一旦 WebSocket 客戶端、服務端創建鏈接後,後續的操做都是基於數據幀的傳遞。

WebSocket的每條消息可能被切分紅多個數據幀。當 WebSocket 的接收方收到一個數據幀時,會根據FIN(是數據幀當中的一個標識,用於判斷當前幀是否爲當前消息的最後一幀)的值來判斷,是否已經收到消息的最後一個數據幀。

當接收到消息的最後一幀,便可以對消息進行處理。

心跳檢測

不少緣由都會觸發鏈接關閉,通常狀況是都會觸發鏈接的onClose事件,可是當斷網狀況下是不會觸發的onclose,這時候就不知道鏈接是斷掉的,此時能夠採用心跳重連,客戶端每隔一段時間向服務端發送ping數據,服務端一旦甦醒,將進行pong響應,此時便可從新鏈接。心跳重連不是輪詢,輪詢會不斷創建鏈接(多個鏈接),而心跳仍是當前這個鏈接,只是一直髮探測消息而已。

關閉鏈接

一旦發送或接收到一個Close控制幀,websocket 關閉階段握手啓動。

關閉狀態碼錶(關閉緣由)

狀態碼

名稱

描述

0–999

保留段, 未使用.

1000

CLOSE_NORMAL

正常關閉; 不管爲什麼目的而建立, 該連接都已成功完成任務.

1001

CLOSE_GOING_AWAY

終端離開, 可能由於服務端錯誤, 也可能由於瀏覽器正從打開鏈接的頁面跳轉離開.

1002

CLOSE_PROTOCOL_ERROR

因爲協議錯誤而中斷鏈接.

1003

CLOSE_UNSUPPORTED

因爲接收到不容許的數據類型而斷開鏈接 (如僅接收文本數據的終端接收到了二進制數據).

1004

保留. 其意義可能會在將來定義.

1005

CLOSE_NO_STATUS

保留. 表示沒有收到預期的狀態碼.

1006

CLOSE_ABNORMAL

保留. 用於指望收到狀態碼時鏈接非正常關閉 (也就是說, 沒有發送關閉幀).

1007

Unsupported Data

因爲收到了格式不符的數據而斷開鏈接 (如文本消息中包含了非 UTF-8 數據).

1008

Policy Violation

因爲收到不符合約定的數據而斷開鏈接. 這是一個通用狀態碼, 用於不適合使用 1003 和 1009 狀態碼的場景.

1009

CLOSE_TOO_LARGE

因爲收到過大的數據幀而斷開鏈接.

1010

Missing Extension

客戶端指望服務器商定一個或多個拓展, 但服務器沒有處理, 所以客戶端斷開鏈接.

1011

Internal Error

客戶端因爲遇到沒有預料的狀況阻止其完成請求, 所以服務端斷開鏈接.

1012

Service Restart

服務器因爲重啓而斷開鏈接.

1013

Try Again Later

服務器因爲臨時緣由斷開鏈接, 如服務器過載所以斷開一部分客戶端鏈接.

1014

由 WebSocket 標準保留以便將來使用.

5、優點

  • 相較於HTTP協議,WebSocket支持持久鏈接;

  • 服務器與客戶端之間交換的標頭信息很小,大概只有2字節;

  • 客戶端與服務器均可以主動傳送數據給對方,真正的全雙工;

  • 不用頻率建立TCP請求及銷燬請求,減小網絡帶寬資源的佔用,同時也節省服務器資源;

6、總結

WebSocket在用於雙向傳輸、推送消息方面可以作到靈活、簡便、高效,但在普通的Request-Response過程當中並無太大用武之地,比起普通的HTTP請求來反倒麻煩了許多,甚至更爲低效。好比某些場景只須要簡單的Request-Response,若是換作WebSocket還須要增長一個請求標識RequestId,增長成本。每項技術都有自身的優缺點,在適合它的地方能發揮出最大長處,而看到它的幾個優勢就不分場合地全方位推廣的話,可能會拔苗助長。

7、實踐

一、流程圖


二、Attention Point

  • spring websocket須要tomcat爲7.x及以上版本;

  • websocket的功能支持須要nginx和本機都增長支持websocket協議的配置;

三、Details

  1. web端頁面初始化生成一個惟一標識,標識當前web端頁面;

  2. 將惟一標識放在二維碼的url地址中生成二維碼;

  3. app端掃一掃,請求二維碼對應的服務,同時這個請求攜帶了web端頁面的惟一標識和app的一些參數信息;

  4. 服務端收到請求,將參數進行處理返回給攜帶了惟一標識的web端頁面;

  5. web端組件展現服務端響應的信息;

四、前端插件推薦

做者使用的是SpringMVC+React的先後端分離框架,這裏推薦幾個React不錯的插件方便使用:

二維碼生成插件:github.com/zpao/qrcode…

Cookie插件:https://www.npmjs.com/package/react-cookies

8、參考

ningg.top/websocket-i…

segmentfault.com/a/119000001…

segmentfault.com/a/119000001…

附:輪詢、長輪詢、短鏈接、長鏈接區別對比

相關文章
相關標籤/搜索