一、背景:源於一個需求
需求:“客戶端掃二維碼,將客戶端引數資訊展示在web端”
問題焦點:實時的web應用,Client 跟 Server 之間,實時的雙向通訊。
二、傳統解決方案?
輪詢(Polling):又稱定期輪詢
Client 定期向 Server 傳送請求,以此保持與 Server 端資料的同步。典型應用場景:
Ajax技術,區域性重新整理Web頁面
缺點:
頻寬和 CPU 資源:由於 Client 定期向 Server 傳送請求,當 Server 端沒有資料更新時,Client仍舊傳送請求,這造成頻寬的浪費以及Server端CPU的耗費
實時性:輪詢間隔內,會有資料延遲
長輪詢(Long Polling):對普通輪詢的改進和提高
目標:節省頻寬,降低無效的網路傳輸。
基本原理:
保持連線: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(本質上仍然是輪詢)
三、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是一個持久化的協議,相對於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、流程圖
2、Attention Point
spring websocket需要tomcat為7.x及以上版本;
websocket的功能支援需要nginx和本機都增加支援websocket協議的配置;
3、Details
web端頁面初始化生成一個唯一標識,標識當前web端頁面;
將唯一標識放在二維碼的url地址中生成二維碼;
app端掃一掃,請求二維碼對應的服務,同時這個請求攜帶了web端頁面的唯一標識和app的一些引數資訊;
服務端收到請求,將引數進行處理返回給攜帶了唯一標識的web端頁面;
web端元件展示服務端響應的資訊;
4、前端外掛推薦
作者使用的是SpringMVC+React的前後端分離框架,這裡推薦幾個React不錯的外掛方便使用:
二維碼生成外掛:github.com/zpao/qrcode…
Cookie外掛:https://www.npmjs.com/package/react-cookies