WebSocket簡介與最佳實踐

你是哪塊小餅乾發表於2019-05-05

一、背景:源於一個需求

需求:“客戶端掃二維碼,將客戶端引數資訊展示在web端”

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

WebSocket簡介與最佳實踐

二、傳統解決方案?

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

Client 定期向 Server 傳送請求,以此保持與 Server 端資料的同步。典型應用場景:

  • Ajax技術,區域性重新整理Web頁面

缺點:

  • 頻寬和 CPU 資源:由於 Client 定期向 Server 傳送請求,當 Server 端沒有資料更新時,Client仍舊傳送請求,這造成頻寬的浪費以及Server端CPU的耗費

  • 實時性:輪詢間隔內,會有資料延遲

WebSocket簡介與最佳實踐

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

目標:節省頻寬,降低無效的網路傳輸。

基本原理:

  • 保持連線:HTTP 層,保持連線,Server 接收到 Client 的請求之後,如果沒有資料更新,則連線保持一段時間;

  • 直到有資料更新或者連線超時,這樣可以減少無效的 Client 與 Server 之間的互動;

  • 通過保持連線,減少 Request 和 Response 的數量,節省頻寬;

缺陷:

  • 節省頻寬,效果有限:HTTP的資料包HEAD部分資料量很大(400+Byte),但真正有效的資料很少(10Byte),這樣的資料包在網路中週期傳輸,浪費頻寬。

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

  • 網路擁塞場景下,等待時間更久,因為需要重新建立連線;

WebSocket簡介與最佳實踐

本質原因:

  • 連線保持:需要重新建立 HTTP 連線(HTTP 1.1 只能緩解,無法從原理上,徹底解決)

  • 資料格式:仍然為應用層的資料格式, HTTP HEADER 資料佔用比較大

事件流方式(SSE)

  • 通過 SSE ,客戶端可以自動獲取資料更新,而不用重複傳送HTTP請求。一旦連線建立,“事件”便會自動被推送到客戶端。伺服器端SSE通過 事件流(Event Stream) 的格式產生並推送事件。

  • 可以實現伺服器到客戶端的單向資料通訊。

  • SSE相較於輪詢具有較好的實時性,使用方法也非常簡便。

缺點:

  • 大併發情況下,伺服器可能會當機。

  • SSE只支援伺服器到客戶端單向的事件推送,而且所有版本的IE(包括到目前為止的Microsoft Edge)都不支援SSE。如果需要強行支援IE和部分移動端瀏覽器,可以嘗試 EventSource Polyfill(本質上仍然是輪詢)

三、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應用的效率:

WebSocket簡介與最佳實踐

四、協議

首先,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 標準保留以便未來使用.

五、優勢

  • 相較於HTTP協議,WebSocket支援持久連線;

  • 伺服器與客戶端之間交換的標頭資訊很小,大概只有2位元組;

  • 客戶端與伺服器都可以主動傳送資料給對方,真正的全雙工;

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

六、總結

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

七、實踐

1、流程圖

WebSocket簡介與最佳實踐

2、Attention Point

  • spring websocket需要tomcat為7.x及以上版本;

  • websocket的功能支援需要nginx和本機都增加支援websocket協議的配置;

3、Details

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

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

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

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

  5. web端元件展示服務端響應的資訊;

4、前端外掛推薦

作者使用的是SpringMVC+React的前後端分離框架,這裡推薦幾個React不錯的外掛方便使用:

二維碼生成外掛:github.com/zpao/qrcode…

Cookie外掛:https://www.npmjs.com/package/react-cookies

八、參考

ningg.top/websocket-i…

segmentfault.com/a/119000001…

segmentfault.com/a/119000001…

附:輪詢、長輪詢、短連線、長連線區別對比


相關文章