WebSocket協議及其在實時通信中的重要性

本文深入介紹了WebSocket協議及其在實時通信中的重要性。從HTTP的限制到WebSocket的優勢,再到連接建立和實際應用,全面闡述了WebSocket的工作原理及其在實際業務中的應用場景。

一、引言

在生產中,有時需要服務端向客戶端發送消息,但是在傳統的 HTTP 協議中,是請求-響應模式,也就是說每個請求都是獨立的,是由客戶端向服務器發送請求,服務器處理請求并返回響應,然后連接就會關閉。

這種請求-響應模式并不能支持后端發起請求,為了解決傳統 HTTP 協議在實時通信中的限制,WebSocket協議被引入。

二、WebSocket 介紹

WebSocket 是一種網絡傳輸協議。它允許客戶端和服務器之間進行雙向通信,而不需要每次請求都重新建立連接。可在單個 TCP 連接上進行全雙工通信,位于 OSI 模型的應用層。

WebSocket 通信始于 HTTP 握手,之后升級到WebSocket協議。具有以下優勢:

  1. 雙向通信:WebSocket支持全雙工通信,允許服務器主動向客戶端推送數據,而不需要客戶端發送請求。
  2. 較低的延遲:WebSocket 通過在建立連接后保持持久連接的方式,避免了重復建立和關閉連接的開銷。可以減少延遲,實現更快的數據傳輸和實時更新。
  3. 減少網絡流量:與輪詢方式相比,WebSocket 采用事件驅動的方式,只在有新數據時才發送更新,避免了不必要的網絡流量和服務器負載。
  4. 兼容性:WebSocket 協議已經得到了廣泛的支持。

三、其他主動推送

短輪詢長輪詢iframe流SSEWebSocketSocket.IO
實時性較差較差較好較好較好較好
網絡開銷較大較大較小較小較小較小
協議支持HTTP 協議HTTP 協議HTTP 協議HTTP 協議WebSocket 協議自適應
跨域支持較差較差較差支持支持支持
兼容性較好較好較好較好較好較好
雙向通信單向通信單向通信單向通信單向通信雙向通信雙向通信
實現復雜度相對簡單相對簡單相對簡單相對復雜相對復雜相對復雜
延遲較高相對較低相對較低較低最低較低

1.短輪詢

優勢:

  • 實現簡單。簡單場景的快速的解決方案。
    劣勢:
  • 請求頻繁。頻繁的請求和響應會導致較高的網絡開銷和延遲。
  • 資源占用高。對服務器資源的占用較高,每次請求需要建立連接和斷開連接。

2.長輪詢

優勢:

  • 相比短輪詢,能夠減少一部分請求的頻繁性。客戶端在收到響應后會立即發起新的請求。
  • 相比短輪詢速度相較快。服務器在有數據時才會響應,能夠更快地將數據推送給客戶端。
    劣勢:
  • 延遲高。存在一定程度的延遲,客戶端需要等待服務器有數據時才能收到響應。
  • 資源占用高。服務器需要維護大量的長連接,可能會影響服務器的性能。

3.iframe流

優勢:

  • 通過在 iframe 中加載長連接資源來實現數據推送,相對于傳統的輪詢方式,可以降低一定程度的延遲。
    劣勢:
  • 受到瀏覽器同源策略的限制。
  • 復雜應用場景難以管理和維護。

4.SSE(Server-Sent Events)

優勢:

  • 基于 HTTP,在不需要額外的握手過程的情況下實現服務器向客戶端的數據推送,降低了通信的開銷。
  • 相對于傳統的輪詢方式,能夠實現較低的延遲。
    劣勢:
  • 僅支持單向通信,無法實現客戶端到服務器的雙向通信。

5.Socket.IO

優勢:

  • 提供了跨瀏覽器的雙向通信能力,可以自動選擇最佳的通信方式,包括 WebSocket、輪詢等,從而實現較低的延遲。
  • 支持實時雙向通信。
    劣勢:
  • 需要額外的庫支持,增加代碼的復雜性。

6.WebSocket

優勢:

  • 提供了實時的雙向通信能力,實現最低的延遲。
  • 與 HTTP 不同,WebSocket 在建立連接后能夠直接實現服務器和客戶端之間的雙向通信,而不需要頻繁地發起新的連接。
    劣勢:
  • 部署和維護復雜。

7.如何選擇

雙向通訊:WebSocket、Socket.IO
實時性:WebSocket >= Socket.IO > SSE ≈ iframe流 ≈ 長輪詢 > 短輪詢
僅單向通訊:SSE
場景簡單且不復雜:長輪詢、短輪詢

四、WebSocket 應用

1.傳統 HTTP 的限制

HTTP 是請求-響應模式,也就是說每個請求都是獨立的。
WebSocket 是全雙工通信。全雙工(Full Duplex)是指在發送數據的同時也能夠接收數據,兩者同步進行。

用一個例子來解釋一下兩個的區別:

WebSocket(ws)就像你在餐廳里用餐,旁邊有一位專門跟著你的服務員。你點菜,服務員會立刻把你的要求傳達給廚房,菜做好后立刻送到你桌上,甚至如果廚房需要你的建議,服務員也能立即傳達給你。

而 HTTP 就像整個餐廳共用一批服務員。可能 A 服務員會給你送第一道菜,但送第二道菜的時候會換成 B 服務員。而且服務員之間不共享信息。也就是說,如果你向 A 服務員點了菜,當你問 B 服務員的時候,B 會回答你“很快就上了”,但實際上 B 并不知道這個事情,只是在安慰你的情緒。

從資源使用上講,相比于 HTTP,WebSocket 更具優勢。相比于每次通訊需要建立連接,WebSocket只需要維持 TCP 即可。
![[Pasted image 20231209204843.png]]

2.WebSocket 連接的建立

WebSocket 連接流程
  1. 建立 TCP 連接:WebSocket 連接首先需要建立一個基礎的 TCP 連接,這是因為WebSocket 是基于 TCP 的。
  2. 發送特殊的 HTTP 請求(WebSocket 握手):客戶端會發送一個特殊的 HTTP 請求,這個請求被稱為 WebSocket 握手請求。這個請求包含一些特殊的頭部信息,其中包括 Upgrade 和 Connection 字段,告訴服務器希望升級到 WebSocket 協議。服務器收到這個請求后,如果支持 WebSocket,就會進行協議升級。
  3. 服務器確認協議升級:如果服務器支持 WebSocket,它會發送一個類似的響應,這個響應也包含特殊的頭部信息,告訴客戶端協議已經升級到 WebSocket。這樣,從此之后,客戶端和服務器之間的通信就不再遵循傳統的 HTTP 協議,而是遵循 WebSocket 協議。
  4. 使用 WebSocket 協議進行通訊:一旦協議升級完成,客戶端和服務器就可以使用 WebSocket 協議進行實時的雙向通信,發送和接收數據幀而無需頻繁地建立和斷開連接。
    ![[Pasted image 20231209204917.png]]
WebSocket 握手

創建HTTP請求,對連接進行升級參數如下:

# 請求頭
Request Headers
# websocket版本
Sec-WebSocket-Version: 13
# 唯一口令,base64編碼
Sec-WebSocket-Key: 2icxlyJOYxKXJpCXa8T14Q==
# 連接升級為websocket協議
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: 127.0.0.1:9095# 返回值
Response Headers
# 升級為websocket協議
Upgrade: websocket
Connection: upgrade
# 口令
Sec-WebSocket-Accept: stCq5oQ38vohTpeBYUTMb0IU8Fo=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15

![[Pasted image 20231209202336.png]]

![[Pasted image 20231209202607.png]]

3.分布式環境中的 Session 共享

分布式問題

在生產中,程序往往是以分布式進行部署的,即啟動多份程序,通過代理提高并發能力。

如下圖,用戶A 通過負載在節點1上建立了 WebSocket 連接,連接的 Session 也存儲在節點1。當 用戶A 的數據通過其他方式到達系統時,通過負載代理,最終到達節點4,當節點4對數據進行發送時,發現此節點沒有用戶A的Session,這樣就導致了分布式情況下出現發送不了的情況。

WebSocket 的 Session 是繼承自 Closeable,不能像 HttpSession 一樣,把內容序列化到Redis中,每個客戶端都會與服務器之間建立獨立的 WebSocket 連接,Session 存儲在建立連接的服務中,那么就引申出 WebSocket 的分布式問題。

![[Pasted image 20231129215436.png]]

解決方法

解決方法大概有三種,分別為中間件廣播處理、服務間建立WebSocket連接、請求代理哈希轉發。

  1. 中間件廣播處理:利用中間件廣播的能力,當出現需要發送的消息時,廣播所有節點,至少有一個節點能夠處理此消息,發送給客戶端。
  2. 服務間建立WebSocket連接:所有服務間建立WebSocket連接,分內外兩部分連接,外部為用戶發起的連接,內部為節點間的連接。通過記錄用戶連接關系,即可知消息需發送的客戶端節點。
  3. 請求代理哈希轉發:通過重寫代理的方式,獲取請求頭中用戶信息,哈希分配到指定節點處理操作。

下面是不同方法間的對比:

中間件廣播處理服務間建立WebSocket連接請求代理哈希轉發
優勢簡單易實現點對點通訊,靈活性較高定向請求,保證數據一致性
劣勢依賴于中間件,廣播效率低系統復雜,需維護大量連接可能負載不均衡,需自定義負載策略
擴展性擴展性好隨節點增加復雜節點增加減少需重新計算哈希值
分析

第二種、第三種方法是可以解決分布式的問題,但在實現難易程度、擴展性方面相比第一種不具有優勢。

  • 第二種,需要維護眾多WebSocket連接,需要節點間長連接,自動重連等功能,維護調試成本高。
  • 第三種,需要對集群節點可用數量精確把控,若節點已經down掉,需要重新計算哈希值,以免代理到此節點上,維護成本高。

綜上所述,選擇第一種方案相比來講,在開發、維護方面更加具有優勢。

![[Pasted image 20231129215455.png]]

五、WebSocket案例

1.業務場景和需求

消息數據通過三方接口接入到系統中,服務端主動發送消息到小程序,小程序進行頁面跳轉,展示消息(由于小程序不支持SSE,選擇WebSocket解決主動推送問題)。

要求有較高實時性。由于消息不知什么時候發送到系統中,所以連接在跳轉前一直維持。

2.分析

由于消息會不定時接入到系統中,請求通過負載到不同節點,需要保持連接,且解決分布式問題。分析結果如下:

  1. 分布式問題。消息接收節點 與 用戶建立 WebSocket 連接節點,兩節點不一定一致。
  2. 連接空擋。連接到達最大時間后自動斷開,與下次連接間存在時間差。所以需要重復連接,這樣會導致多個連接存在,消息多次發送問題。
  3. 惡意連接。惡意使用不存在的 key 進行惡意連接。

針對上述問題方案如下:

  1. 使用MQ廣播解決分布式問題。接收消息廣播的方式分發。
  2. 在 WebSocket 連接成功后也進行廣播,目的是斷開此人其他的連接,保證連接有且僅有一個。
  3. 在連接建立前重寫鉤子函數,對用戶進行校驗。

![[Pasted image 20231129220451.png]]

3.關鍵代碼

對Session進行封裝,擴展Session數據,添加Session管理類,封裝相應的方法

public class SessionExt {  private WebSocketSession session;  private String uniqueId;... 省略 get set 方法
}
@Component  
public class SessionContainer {  private static final ConcurrentHashMap<String, SessionExt> SESSION_MAP =  new ConcurrentHashMap<>(WebSocketConstants.INT_16);  /**  * 獲取 session 數據  *  * @param key key  * @return session  */    public SessionExt getSessionExt(String key) {  return SESSION_MAP.getOrDefault(key, null);  }  /**  * 添加 session 數據  *  * @param key        key  * @param sessionExt sessionExt  */    public void addSessionExt(String key, SessionExt sessionExt) {  SESSION_MAP.put(key, sessionExt);  }  /**  * 添加 session 數據  *  * @param key        key  * @param sessionExt sessionExt  */    public void addSessionExtAndClose(String key, SessionExt sessionExt) {  SessionExt sessionExtOld = SESSION_MAP.get(key);  if (sessionExtOld != null) {  sessionExtOld.closeSession();  }  SESSION_MAP.put(key, sessionExt);  }  /**  * 刪除 session 數據  *  * @param key key  */    public void delSessionExt(String key) {  SESSION_MAP.remove(key);  }  
}

連接創建、關閉后的廣播回調,關閉多余連接

 
public void handleOpenProcessing(String key, String uniqueId) {  SessionExt sessionExt = SESSION_CONTAINER.getSessionExt(key);  if (sessionExt != null) {  String uniqueIdOld = sessionExt.getUniqueId();  if (!uniqueIdOld.equals(uniqueId)) {  log.debug("[websocket]廣播,連接后置處理,關閉用戶之前連接");  sessionExt.closeSession();  SESSION_CONTAINER.delSessionExt(key);  }  }  log.info("[websocket]廣播,連接后置處理,完成");  
}  public void handleCloseProcessing(String key, String uniqueId) {  log.debug("[websocket]廣播處理 關閉連接,key:" + key);  SessionExt sessionExt = SESSION_CONTAINER.getSessionExt(key);  if (sessionExt != null) {  String uniqueIdOld = sessionExt.getUniqueId();  if (uniqueIdOld.equals(uniqueId)) {  sessionExt.closeSession();  SESSION_CONTAINER.delSessionExt(key);  }  }  log.info("[websocket]廣播,關閉后置處理,完成");  
}

六、總結

本文介紹了 WebSocket 協議及其在實時通信中的重要性。因為傳統 HTTP 協議的限制,引出了 WebSocket 協議,展現了 WebSocket 相對于傳統HTTP的優越性。

對WebSocket連接的建立過程進行了詳細解析,包括TCP連接的建立、特殊HTTP請求的發送以及協議升級的過程。

最后通過具體案例 WebSocket 在分布式環境中的 Session 共享問題,并提出了解決方案。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/716476.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/716476.shtml
英文地址,請注明出處:http://en.pswp.cn/news/716476.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

三元組的最小距離

題目鏈接&#xff1a; 三元組最小距離 定義三元組 $(a, b, c)$&#xff08;$a,b,c$ 均為整數&#xff09;的距離 $D|a-b||b-c||c-a|$。 給定 $3$ 個非空整數集合 $S_1, S_2, S_3$&#xff0c;按升序分別存儲在 $3$ 個數組中。 請設計一個盡可能高效的算法&#xff0c;計算并…

AI學習集合-前瞻

AI學習前瞻 工作崗位 算法工程師機器學習工程師圖像算法工程師ai工程師NLP高級算法工程師 學習路線 應用場景 計算機視覺技術應用場景 自然語言應用 AI流程 AI擬人流程 機器人歷史數據經驗模型規律依據模型預測未來依據規律做出判斷 AI基本流程 術語所用到的技術手段數據數…

javascript中對包含關系判斷介紹

本文將為您詳細講解 JavaScript 中對包含關系的判斷&#xff0c;包括數組、字符串等&#xff0c;并提供相應的代碼例子。 1. 數組包含關系判斷 在 JavaScript 中&#xff0c;數組包含關系判斷通常使用 Array.prototype.includes() 方法。這個方法返回一個布爾值&#xff0c;表示…

牛客網C++專項題目整理(2)

1.參加位運算的數據可以是任何類型的數據。請問這句話的說法是正確的嗎&#xff1f; 答案&#xff1a;錯誤 位運算符主要用于整型數據&#xff08;如int、unsigned int、long、unsigned long等&#xff09;和字符型數據&#xff08;如char和unsigned char&#xff09;&#x…

mac 本地使用dockerfile啟動 springboot項目

1.創建Dockerfile放在項目的根目錄下 2.編寫Dockerfile FROM openjdk:11 MAINTAINER ChengLinADD target/JiaLi-0.0.1-SNAPSHOT.jar /app.jar# 暴露 Spring Boot 應用的端口號 EXPOSE 8088 # 啟動 Spring Boot 應用 CMD ["java", "-jar", "app.jar&q…

前端學習第四天-css提升

達標要求 掌握css復合選擇器 塊級元素和行內元素及行內塊的區別? 哪些元素是塊元素,行內元素及行內塊元素? 熟練掌握display的用法 能夠說出css三大特性 熟練運用背景樣式 1. CSS復合選擇器 復合選擇器是由兩個或多個基礎選擇器&#xff0c;通過不同的方式組合而成的…

vue2結合electron開發跨平臺應用(桌面端應用)

1.確定nodejs和electron的版本號 確定nodejs和electron的版本號及其重要&#xff0c;因為electron的開發版本需要指定的nodejs版本支持。 本文安裝測試使用的是: 1.node18.19.0 2.npm10.2.3 3.vue-cli5.0.8 4.electron29.0.0 2.創建vue2項目 vue create elctron29.0.0_no…

zotero | 多平臺同步 | 堅果云

zotero注冊登陸 打開zotero軟件&#xff0c;mac電腦打開首選項&#xff0c;如下圖所示&#xff1a; 然后點擊同步選項&#xff0c;如下圖所示&#xff0c;如果已經有賬號&#xff0c;請登陸賬號&#xff0c;無則注冊賬號之后再登陸&#xff1b; 注冊堅果云賬號 注冊完堅果…

求最短路徑之BF算法

介紹 全稱Bellman-Ford算法&#xff0c;目的是求解有負權邊的最短路徑問題。 考慮環&#xff0c;根據環中邊的邊權之和的正負&#xff0c;將環分為零環、正環、負環。其中零環、正環不會影響最短路徑的求解&#xff0c;而負環會影響最短路徑的求解。 可用BF算法返回一個bool值…

暗黑大氣MT蘋果CMS MT主題源碼-PC版適用于蘋果CMS V10

蘋果CMS MT主題是一款多功能的主題&#xff0c;適用于蘋果CMS V10的暗黑大氣風格。 地 址 &#xff1a; runruncode.com/houtai/19704.html 初次使用說明&#xff1a; 在后臺設置中&#xff0c;選擇MT主題&#xff0c;并在模板目錄中填寫HTML。 后臺地址為&#xff1a;MT主題…

*JAVAWEB--maven*

一:介紹: maven是一種專門管理以及構建JAVA項目的一個工具,maven屹立這么久也是因為其有三個非常好用的功能: 1.提供標準化的項目結構 比方說平時我們編寫JAVA項目的時候,如果想把原本在eclipse當中編寫的項目導入到IDEA當中進行使用,就會導致報錯,因為這兩個的項目結構并不一樣…

圖神經網絡實戰——基于DeepWalk創建節點表示

圖神經網絡實戰——基于DeepWalk創建節點表示 0. 前言1. Word2Vec1.1 CBOW 與 skip-gram1.2 構建 skip-gram 模型1.3 skip-gram 模型1.4 實現 Word2Vec 模型 2. DeepWalk 和隨機行走3. 實現 DeepWalk小結系列鏈接 0. 前言 DeepWalk 是機器學習 (machine learning, ML) 技術在圖…

[Angular 基礎] - routing 路由(上)

[Angular 基礎] - routing 路由(上) 之前部分 Angular 筆記&#xff1a; [Angular 基礎] - 生命周期函數 [Angular 基礎] - 自定義指令&#xff0c;深入學習 directive [Angular 基礎] - service 服務 終于到 routing 了……這部分的內容比我想象的要復雜很多&#xff0c;果…

LC打怪錄 選擇排序 215.Kth Largest Element in an Array

題目鏈接&#xff1a;力扣 選擇排序知識 設第一個元素為比較元素&#xff0c;依次和后面的元素比較&#xff0c;比較完所有元素并找到最小元素&#xff0c;記錄最小元素下標&#xff0c;和第0個下表元素進行交換。在未排序區域中&#xff0c;重復上述操作&#xff0c;以此類推…

力扣每日一題 用隊列實現棧 模擬

Problem: 225. 用隊列實現棧 文章目錄 思路復雜度Code 思路 &#x1f468;?&#x1f3eb; 力扣官解 輔助隊列存棧頂元素主隊列存逆序序列 復雜度 時間復雜度: 添加時間復雜度, 示例&#xff1a; O ( n ) O(n) O(n) 空間復雜度: 添加空間復雜度, 示例&#xff1a; O ( …

js監聽網頁iframe里面元素變化其實就是監聽iframe變化

想要監聽網頁里面iframe標簽內容變化&#xff0c;需要通過監聽網頁dom元素變化&#xff0c;然后通過查詢得到iframe標簽&#xff0c;再通過iframe.contentWindow.document得到ifram內的document&#xff0c;然后再使用選擇器得到body元素&#xff0c;有了body元素&#xff0c;就…

2024年華為OD機試真題-貪吃的猴子-Python-OD統一考試(C卷)

題目描述: 一只貪吃的猴子,來到一個果園,發現許多串香蕉排成一行,每串香蕉上有若干根香蕉。每串香蕉的根數由數組numbers給出。猴子獲取香蕉,每次都只能從行的開頭或者末尾獲取,并且只能獲取N次,求猴子最多能獲取多少根香蕉。 輸入描述: 第一行為數組numbers的長度 第二…

Java和JavaScript之間的主要區別與聯系

目錄 概況 主要區別 聯系 總結 概況 Java和JavaScript&#xff0c;盡管名字相似&#xff0c;但它們在編程世界中卻扮演著截然不同的角色。Java&#xff0c;一種強類型、面向對象的編程語言&#xff0c;廣泛應用于企業級應用和安卓應用開發。它的設計理念是一次編寫&#x…

使用協程庫httpx并發請求

httpx和aiohttp都是比較常用的異步請求庫&#xff0c;當然requests多線程或requestsgevent也是不錯的選擇。 一個使用httpx進行并發請求的腳本如下&#xff1a; import functools import sys import timeimport anyio import httpxasync def fetch(client, results, index) -…

詳解 JavaScript 中的數組

詳解 JavaScript 中的數組 創建數組 注&#xff1a;在JS中的數組不要求元素的類型&#xff0c;元素類型可以一樣&#xff0c;也可以不一樣 1.使用 new 關鍵字創建 let array new Array()2.使用字面量方式創建(常用) let array1 [1,2,3,"4"]獲取數組元素 使用下…