自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

別背八股文了,WebSocket 是什么,我勸你花幾分鐘讓面試官驚艷!

開發(fā) 前端
WebSocket 前端的使用非常簡單,我自然會聯(lián)想到:如果我做全棧開發(fā),用 Nodejs 實現(xiàn) WebSocket 服務(wù)端,有原生的模塊可以支持嗎?

前言

作為前端工程師,我們幾乎每天都在使用 ajax / fetch 請求與后端進行數(shù)據(jù)交互,這種基于請求-響應(yīng)的通訊模式,我們再熟練不過了,無論是C端產(chǎn)品或者是B端產(chǎn)品,都離不開這種通訊模式。但是像即時通訊IM類場景,通常不會選擇這種“你來我回”的通信模式,而是會選擇 WebSocket 這類的全雙工通信模式。

本文會帶您全方位去了解一下 WebSocket 的本質(zhì),方便您搞清楚“Connection: Upgrade 是什么意思,為什么是它?”、“Upgrade: WebSocket 又是什么意思?這就可以雙向通信了?”、“WebSocket 和 HTTP/TCP 到底有什么關(guān)聯(lián)?八股文背了還是不理解”之類的問題,幫助您無論面試或工作時被問到 WebSocket 都能有更多細節(jié)可以聊,妥妥的一個加分項!

最后通過一個在線聊天室實戰(zhàn)案例帶大家熟悉下 WebSocket 的全棧使用,可點擊在線聊天室[3]進行體驗。

圖片圖片

認識 WebSocket

WebSocket 是一種網(wǎng)絡(luò)通信協(xié)議,它建立在 HTTP 之上,提供了在單個 TCP 連接上進行全雙工通信的能力。這意味著服務(wù)器和客戶端可以互相發(fā)送和接收消息,而不需要每次都重新建立連接。WebSocket 最初由 HTML5 規(guī)范定義,具體可以參考WebSockets Living Standard[4]。而現(xiàn)在,WebSocket 已被廣泛支持并應(yīng)用于各種應(yīng)用,包括實時聊天、多人在線游戲、股票交易系統(tǒng)等需要實時數(shù)據(jù)更新的場景。

WebSocket 的兼容性如何?

作為前端開發(fā),對 API 的兼容性還是非常敏感的,我們先來看看 WebSocket 的兼容性怎么樣。

圖片圖片

可以看到,主流瀏覽器都支持了 WebSocket,IE10 及以上版本也對 WebSocket 提供了完備的支持,所以我們可以大膽地使用起來!

WebSocket 的前端用法

瀏覽器 JS 運行時提供了 WebSocket[5] 這個 API,可以用來創(chuàng)建和管理 WebSocket 連接。

使用也非常簡單,構(gòu)造實例后只有幾個簡單的方法調(diào)用,一看就會。

// 創(chuàng)建一個新的 WebSocket 連接
const socket = new WebSocket('ws://your-websocket-server-url')

// 監(jiān)聽連接打開
socket.onopen = (e) => {
  console.log('WebSocket is connected.')
  // 連接打開后,你可以發(fā)送消息
  socket.send('Hello Server!')
}

// 監(jiān)聽消息
socket.onmessage = (e) => { console.log('Message from server: ', e.data) }

// 監(jiān)聽關(guān)閉
socket.onclose = (e) => { console.log('Connection closed.') }

// 監(jiān)聽錯誤
socket.onerror = (err) => { console.error('WebSocket Error: ', err) }

// 如果你想主動關(guān)閉連接,可以調(diào)用 close 方法
// socket.close()

wss:// 是 ws:// 的 TLS 加持版,可以類比于 https:// 和 http://

Nodejs 原生支持 WebSocket 嗎?

WebSocket 前端的使用非常簡單,我自然會聯(lián)想到:如果我做全棧開發(fā),用 Nodejs 實現(xiàn) WebSocket 服務(wù)端,有原生的模塊可以支持嗎?

經(jīng)過查詢了解到,Node 原生模塊中并未直接支持 WebSocket 服務(wù)端的開箱使用,一個比較流行的庫是 ws[6]。

那么 ws 這個庫是怎么實現(xiàn) WebSocket 服務(wù)端的呢?怎么才能和瀏覽器的 WebSocket 實現(xiàn)對接上?

直接讀源碼肯定是看不懂的,即便看懂了一些過程調(diào)用,也是懵逼的,我們往下看。

WebSocket 協(xié)議概覽

我們知道,通訊是基于協(xié)議的,WebSocket 也有它的專屬協(xié)議。ws 的實現(xiàn)它也是要遵循這個協(xié)議,才能和客戶端實現(xiàn)匹配上,完成通訊。

這個協(xié)議我們?nèi)ツ睦锟茨??根?jù) wikipedia 的介紹,我們知道,WebSocket 的標準化是基于IETF 的 RFC 6455 WebSocket Protocol[7]。大致瀏覽后,我圈出了協(xié)議里一些值得關(guān)注的內(nèi)容。閱讀這類協(xié)議時,我們可以先挑重點看,對協(xié)議有一個基本的認識即可。

圖片圖片

圖片圖片

我們了解到一些關(guān)鍵詞:

  1. 連接握手
  2. 怎么建立連接
  3. 數(shù)據(jù)發(fā)送和接收,數(shù)據(jù)幀
  4. 關(guān)閉握手
  5. 插件,相關(guān) HTTP 頭部字段

雖然有了一些關(guān)鍵詞在腦海中,但我們對整個通訊過程肯定還有一連串疑問。帶著疑問,我們繼續(xù)往下看協(xié)議的具體內(nèi)容。

  1. 我們會了解到 WebSocket 出現(xiàn)的背景,它是為了解決什么,很顯然,普通的 HTTP 請求不適合一些雙向通信場景,比如聊天、股票、游戲等。
  2. 即便普通 HTTP 請求能通過一些業(yè)務(wù)設(shè)計滿足雙向通信需求,性能問題也很大,TCP 連接的開銷等問題都要考慮在內(nèi)。
  3. WebSocket 就是希望在一個 TCP 連接上,開辟雙工通道,實現(xiàn)全雙工實時通信。
  4. 之所以選擇在 HTTP 協(xié)議的基礎(chǔ)上去實現(xiàn) WebSocket,也是一種權(quán)衡和取舍,可能會犧牲一些性能,但是也極大地復(fù)用了已有的網(wǎng)絡(luò)基礎(chǔ)設(shè)施,包括協(xié)議、安全、代理、認證等。

圖片圖片

WebSocket 協(xié)議 - 連接握手

再往下翻一翻協(xié)議,我們能翻到最關(guān)鍵的部分,這也是面試里能和面試官吹的內(nèi)容,請仔細看!

很多人面試被問到 WebSocket,就說 WebSocket 可以雙向通信,這是和 HTTP 最大的不同。講道理,這種回復(fù)面試官已經(jīng)聽膩了。

如果你能告訴面試官,WebSocket 的協(xié)議涉及到以下幾個 HTTP 頭部字段,并簡述一下各個字段的簡單含義,我相信你的面試絕對加分!

圖片圖片

我們先看請求頭:

  • WebSocket 請求一定是 GET 類型的。
  • Origin: 瀏覽器客戶端會帶上,包含 Origin 是為了安全考慮,充分利用瀏覽器的同源策略。
  • Connection: Upgrade 和 Upgrade: websocket。這是客戶端告知服務(wù)端需要升級協(xié)議,并且升級的協(xié)議為 websocket。
  • Sec-WebSocket-Key:由客戶端(比如瀏覽器)隨機生成,16位隨機數(shù)經(jīng)過 base64 編碼后得到。響應(yīng)頭 Sec-WebSocket-Accept 是與它搭配使用的,用來確保請求的有效性和安全性。
  • Sec-WebSocket-Protocol:不是必選的。用來約定應(yīng)用層面的子協(xié)議,使得客戶端和服務(wù)器能夠靈活地協(xié)商并選擇一個雙方都能理解的協(xié)議來進行通信。如果服務(wù)端選擇使用某個子協(xié)議通信,則會在響應(yīng)頭中返回。

再看響應(yīng)頭:

  • 服務(wù)端返回 101 Switching Protocols,代表握手成功,協(xié)議切換到 WebSocket。
  • Connection: Upgrade 和 Upgrade: websocket,用于告知客戶端可以升級為 websocket 協(xié)議。
  • Sec-WebSocket-Accept:基于 Sec-WebSocket-Key 處理得到,處理公式如下,其中"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"是一個固定的字符串:
base64-encode(sha1(Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

引用 wikipedia 的一張圖,可以看得更清楚!

圖片圖片

WebSocket 協(xié)議 - 數(shù)據(jù)幀

建立了連接握手后,WebSocket 就可以發(fā)送和接受消息數(shù)據(jù)了,消息是由一個或多個幀組成的。我們先看看這兩張圖了解一下數(shù)據(jù)幀的結(jié)構(gòu)。

圖片圖片

圖片圖片

其中 Opcode 是操作碼,具體見下表。

圖片圖片

由于存在數(shù)據(jù)比較大的可能,這時需要切片傳輸,WebSocket 消息數(shù)據(jù)支持分片傳輸。

  • 當 FIN = 1 且 OpCode ≠ 0 時,代表這一幀數(shù)據(jù)不是分片處理的。
  • 當 FIN = 0 時,如果 OpCode = 0,代表這一幀數(shù)據(jù)是某個分片的中間數(shù)據(jù)幀;如果 OpCode ≠ 0,代表這一幀數(shù)據(jù)是一個分片數(shù)據(jù)中的起始幀。

WebSocket 能發(fā)送文本數(shù)據(jù)或二進制數(shù)據(jù),這個是體現(xiàn)在 OpCode 上。如果起始幀的 OpCode 是 1,則代表是文本數(shù)據(jù);如果起始幀的 OpCode 是 2,則代表是二進制數(shù)據(jù)。

WebSocket 協(xié)議 - 關(guān)閉連接

我們看看規(guī)范中,關(guān)閉握手這部分是怎么說的。

圖片圖片

WebSocket 任何一端都可以發(fā)起關(guān)閉連接。

當一方準備關(guān)閉連接時,應(yīng)該發(fā)送 Close Frame 開始關(guān)閉握手,之后不應(yīng)該再發(fā)送任何數(shù)據(jù);

另一方收到 Close Frame 時,需要回復(fù) Close Frame,并且準備釋放資源,同時也應(yīng)該丟棄后續(xù)從這個連接上可能接收到的數(shù)據(jù)。

發(fā)起方收到 Close Frame 控制幀后,關(guān)閉連接釋放資源,不再接收數(shù)據(jù)。

這種 WebSocket 關(guān)閉握手機制也是在 TCP 握手機制上的一種補充,更好地保證端到端通信的可靠性!

有的朋友可能會考慮到這個問題:當客戶端發(fā)送 Close Frame 后,服務(wù)端正常接收到,并且回復(fù) Close Frame,但是由于網(wǎng)絡(luò)問題客戶端沒有服務(wù)端響應(yīng)的 Close Frame,這種情況是怎么關(guān)閉 WebSocket 連接的?

實際上,TCP 連接也有它的超時和重試機制,當一段時間內(nèi)沒有數(shù)據(jù)傳輸時,也會斷開連接。所以我們無需擔心這一點。

當這種沒有成功關(guān)閉握手但是關(guān)閉了 TCP 連接的情況發(fā)生時,onclose事件回調(diào)中收到的錯誤碼應(yīng)該是 1006,這一點可以在上面的表格中找到。

正常關(guān)閉是 1000。

在實際業(yè)務(wù)實現(xiàn)上,還會通過 ping-pong 之類的心跳檢測機制來保證可靠性。

回顧 ws 關(guān)鍵源碼

有了這些知識儲備后,再來看 ws 的實現(xiàn)源碼,可能就會有頭緒一點。

當你看到這部分,你會知道它在校驗頭部字段是否符合協(xié)議要求,準備升級協(xié)議...

圖片圖片

當你看到這個 101 狀態(tài)碼,你會恍然大悟:“哦,原來是在這里完成了協(xié)議的升級!”雖然還有些細節(jié)看不懂,但是無傷大雅!

圖片圖片

當你看到這里時,你會知道,如果客戶端嘗試通過普通的 HTTP 請求來連接 WebSocket 服務(wù),服務(wù)端應(yīng)該返回 426 Upgrade Required 告訴客戶端,“你該升級協(xié)議再跟我對話!”

圖片圖片

本文不是源碼解讀,點到為止,我們往下看。

為什么選擇 Socket.IO

實際將 WebSocket 運用到生產(chǎn)環(huán)境時,我們一般不會直接使用 ws 這種協(xié)議實現(xiàn)庫,而是會選擇在應(yīng)用層面進行了一些封裝的庫,比如 Socket.IO[8]。

圖片圖片

這是因為在 WebSocket 實際使用過程中,還有很多問題要考慮,比如心跳檢測、優(yōu)雅降級、房間隔離、命名空間隔離、API 的易用性等。而這些,Socket.IO 已經(jīng)開箱支持。

準確說,Socket.IO 并非是一個 WebSocket 實現(xiàn),而是一個事件驅(qū)動的低延遲雙向通訊方案。

它的底層通訊不一定是基于 WebSocket 的,可能會根據(jù)情況選擇 HTTP 長輪詢、WebTransport[9]。

WebTransport 是一個基于 HTTP/3 的通訊技術(shù),可實現(xiàn)可靠通信和不可靠通信。HTTP/3 底層基于 Google 的 QUIC 協(xié)議,而 QUIC 協(xié)議是基于 UDP 的。

圖片圖片

Socket.IO 有它的約定和規(guī)則,或者叫協(xié)議,只要遵循這個協(xié)議,就能完成客戶端和服務(wù)端的實現(xiàn),所以你會看到,它也有多語言的實現(xiàn),甚至在客戶端還有小程序的實現(xiàn)。

圖片圖片

這個協(xié)議其實也就對應(yīng)著Socket.IO 的底層引擎 Engine.IO[10]。

圖片圖片

雖然現(xiàn)在大部分瀏覽器都支持了 WebSocket,但是也不排除某些遠古項目的存在,它必須運行在“古董”瀏覽器版本之上。Socket.IO 考慮到了這一點,它的自動優(yōu)雅降級完美解決了這一問題。

圖片圖片

Socket.IO 的心跳檢測機制和自動重連也是實際業(yè)務(wù)中必不可少的!

更多的特性還有:

  • 對話回調(diào)
  • 廣播
  • 房間
  • 命名空間多路復(fù)用
  • ...

Socket.IO 的通訊過程

當我們打開一個 Socket.IO 的客戶端頁面時,會發(fā)現(xiàn) Network 里發(fā)出了多個請求,在 101 websocket 連接建立之前,有 4 個 xhr 請求,其中還有一個是 POST 請求。

圖片圖片

Socket.IO 在升級機制中解釋了這一點,直接建立可靠可用的 WebSocket 連接并非一件很輕松的事情,通常從 HTTP 開始平滑升級到 WebSocket,對連接的可靠性和用戶體驗來說是更好的。

升級協(xié)議會經(jīng)歷這么一些步驟,對應(yīng)著我們在上面看到的幾個 Network 請求。

圖片圖片

我這個項目開始得很早,所以EIO=3,代表協(xié)議版本號是3,目前 Engine.IO 已經(jīng)升級到版本4了。

在 Socket.IO 的 HTTP 長輪詢模式中,使用長時間運行的 GET 請求接收數(shù)據(jù),使用短期運行的 POST 請求發(fā)送數(shù)據(jù)。

了解了這些機制,并且查看 API 用法后,就可以開始運用了,一些高級用法可以在使用過程中再去探索!

聊天室的全棧實現(xiàn)

基于以上理解,我們開始搭建博客項目中的聊天室功能,我們會實現(xiàn)這些主要能力:

  • 成功創(chuàng)建 Socket.IO 連接
  • 展示聊天室的系統(tǒng)通知信息(涉及到單播和廣播)
  • 聊天對話功能(廣播)

我們首先把依賴安裝好,客戶端使用socket.io-client[11],服務(wù)端使用socket.io[12]即可。

服務(wù)端開啟 WebSocket 服務(wù)

第一步是把 WebSocket 服務(wù)啟動。由于本項目開始較早,socket.io 版本是 2.5,大家對照文檔的時候按 2.x 文檔看就好。

圖片圖片

socket.io 可以利用已經(jīng)存在的 HTTP 服務(wù),由于項目是用 Express 搭建的,我們直接與 Express 共享一個 HTTP 服務(wù)即可。

io 實例化后,監(jiān)聽到 connection 事件就代表有客戶端過來了,可以開始干活了。我這里是把聊天室相關(guān)的邏輯都放在了 chatroom 這個命名空間下。

圖片圖片

of 的作用就是初始化并使用命名空間。

客戶端服務(wù)端建立連接

第二步是建立連接。首先引入依賴。

import io from "socket.io-client";

再進行實例化,得到一個 socket 實例。

this.socket = io(process.env.VUE_APP_SOCKET_SERVER + "/chatroom");

有了這個 socket,我們就能監(jiān)聽各種事件了。

圖片圖片

聊天室的系統(tǒng)通知信息

當一個客戶端連接上服務(wù)器時,服務(wù)器會發(fā)送一條消息,“hello,歡迎您加入在線聊天室!”這是通過單播實現(xiàn)的,只要拿著socket對象,調(diào)用其emit方法就行。

圖片圖片

除了對這個客戶端打招呼外,還需要告訴其他用戶,有新人加入了。這是通過socket.broadcast廣播實現(xiàn)的。

socket.broadcast.emit('broadcast', param);

當有人退出聊天室,會觸發(fā) disconnect 事件,此時我們可以廣播通知其他人。

圖片圖片

這就是我們在前端頁面看到的效果:

圖片圖片

聊天對話功能

由于我們做的是聊天室,相當于一個群聊,就不涉及到單播聊天了,直接用廣播就行。

用戶在客戶端發(fā)聊天消息時,是用到socket對象的emit進行發(fā)送。

圖片圖片

這個chat事件是在客戶端連接上服務(wù)端時開始監(jiān)聽的,在這個回調(diào)里,我們需要把內(nèi)容廣播給除發(fā)送者之外的其他用戶,子事件名是new_chat_content。

圖片圖片

而其他用戶則會通過客戶端監(jiān)聽廣播事件中的new_chat_content子事件拿到聊天數(shù)據(jù),最終呈現(xiàn)到界面上。

圖片圖片

圖片圖片

以上都是通訊上的設(shè)計,了解了這個機制,UI 的展示就非常簡單了,畢竟 UI = Render(data),不做更多介紹!

小結(jié)

本文中,我首先分享了我對 WebSocket 協(xié)議的一些理解,希望對還不太理解這塊的朋友起到一點幫助作用。面試里,WebSocket 是一個常問的考點,如果你回答的僅僅是“全雙工通信”,可能并不能起到一個很好的效果,把文中小知識甩面試官臉上吧,哈哈哈!

最終通過一個實際案例,帶大家理解一個聊天室功能的設(shè)計思路,在實際落地的過程中夯實對 WebSocket 協(xié)議的理解。

責任編輯:武曉燕 來源: 程序員白彬
相關(guān)推薦

2021-11-04 14:32:17

Spring 面試作用域

2021-10-26 14:40:03

MySQL SQL 語句數(shù)據(jù)庫

2021-10-21 14:43:23

Java 語言 Java 基礎(chǔ)

2021-09-07 14:46:42

面試網(wǎng)絡(luò)HTTP 協(xié)議

2021-07-26 14:59:23

面試Redis內(nèi)存數(shù)據(jù)庫

2021-11-24 07:56:56

For i++ ++i

2024-02-23 19:17:12

構(gòu)造函數(shù)C++開發(fā)

2023-01-13 18:04:03

面試題消息中間件

2023-11-28 18:09:49

Java多態(tài)

2021-10-26 17:05:55

Redis字符串復(fù)雜度

2022-09-03 11:36:11

Python文件網(wǎng)絡(luò)

2021-05-06 07:27:57

面試任務(wù)調(diào)度器

2021-08-01 22:59:43

Object八股文quals

2023-09-22 08:27:39

2021-04-14 10:02:59

網(wǎng)絡(luò)八股文協(xié)議

2021-08-13 07:23:15

架構(gòu)秒殺系統(tǒng)

2021-08-12 09:28:24

Java多線程變量

2021-05-20 11:43:57

操作系統(tǒng)硬件軟件

2023-11-29 17:28:07

2024-10-12 09:26:32

線程池系統(tǒng)核心線程
點贊
收藏

51CTO技術(shù)棧公眾號