《菜農升職記》之 Websocket

蔡不菜丶發表於2022-02-21

大家好呀,我是小菜~

本文主要介紹 websocket 的使用

微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!

作為準應屆生的小菜農早早的便找到了一份實習工作,初到公司一切都沒那麼適應,作為導師的程立這天給小菜農安排了一個需求,想要實現一個簡單的《人工客服》需求,也就是即時通訊。小菜農儘管沒啥經驗,但為了給導師留下良好的印象便一口爽快的接下需求。

接下需求後小菜農便開始構思如何實現即時通訊,他開始在各大平臺檢視關於線上客服的案例~

image-20220204143551034

他總結了下需求:線上客服,需求理解起來很簡單,就相當於一個 web 的聊天頁面,也就是客戶端能夠即時拉取到服務端的響應,儘管平時微信重度使用,但是到了自己實現的時候卻滿頭霧水,眼看一上午的時間就要過去了,自己卻沒有任何進展,唯一的進展便是找到了以上那張圖,卻沒有絲毫卵用~

中午吃完飯,其他人都已經息屏休息了,而小菜農還在電腦前為這個需求而煩惱,不由有些煩躁起來了,開始想念在學校的日子了。通過介面獲取響應!前端整個定時任務去撈訊息 這個奇妙的想法直接衝擊菜農的大腦

"妙啊",小菜農為自己的好主意開始津津樂道起來,煩惱來得快去的也快~不由多想便開始吭哧吭哧的寫起程式碼了,那一套可謂是行雲流水

虛擬碼如下

服務端:

客戶端:

setInterval(function () {
    $.ajax({
        async: false,
        url: "localhost:8080/roll",
        type: "get",
       
        success(data){
         console.log("success");
        }
    })
},1000)

寫完後,小菜農簡單驗證了下,發現都功能已經滿足了便開始嚮導師程立showcase了,程立簡單地過了遍頁面效果,感覺效果在預期內便讓小菜農提交程式碼準備合併釋出了

小菜農提交完程式碼後心中不由歡喜起來,自我感覺十分良好,能在規定時間內完成這個不是那麼簡單的需求,想必離自己的轉正又進一步了吧!但是沒等小菜農高興太久,電腦上便閃起了導師的叮叮提示,"小菜農,現在有空嗎,過來下"。不好的念頭浮上小菜農的心頭,"這該不會出 bug 了吧"。小菜農顫顫巍巍的來到導師的工位,"我剛剛 review 了下你的程式碼",原來還沒釋出,那就不是bug的事情了,幸好幸好~ 小菜農心中暗想。"我看了下你這個功能的實現方式,這種方式儘管能夠實現需求,但是並不是很好的解決方案",導師接著說。“通過輪詢的方法,儘管可以從服務端撈到聊天資料,但是介面的頻繁請求缺陷也會很明顯,十分浪費頻寬流量,伺服器的壓力就會比較大,所以這種方式並不是很好的解決方法,你可以回去再想想看有沒有什麼其他比較好的解決方法!”

"嗯嗯,是我沒考慮好,那我回去再改改!"小菜農涉世未深,導師都這樣說了,那這個方案肯定得 pass,連忙接道。

小菜農回到工位後,難免有些沮喪,本來想好好表現表現,沒想到自己想出的方案弊端這麼多。一陣頭大,現在也沒時間想這件事,如何實現才是要緊之事!小菜農又陷入了沉思,這可該如何是好~

小菜農隨後便開啟了某度,看到了一個關鍵詞 SSE

SSE 全稱 Server-Sent Events,指的是網頁自動獲取來自伺服器的更新,也就是自動化獲取服務端推送至網頁的資料,這是一個 H5 的屬性,除了 IE,其他標準瀏覽器基本都相容

小菜農認真研究了下,發現這種方式和自己之前的實現方式有些相似,但是就不需要客戶端定時去獲取,而是服務端向客戶端宣告要傳送流資訊,然後連續不斷地傳送過來。這時客戶端是不會關閉連線的,會一直等這伺服器發過來的新的資料流。"妙啊,這樣子不就不會頻繁建立連線,浪費頻寬了",小菜農又興奮了起來,這回肯定能夠滿足導師的需求了!小菜農又花費一個下午的時間將程式碼實現方式重構了一遍,便提交了~

虛擬碼

服務端:

客戶端:

這回可別再出意外了!小菜農心中默唸,但是好景不長,叮叮又開始閃爍了,這這這。。。小菜農的心態有些崩了,完了,這回試用期可能要提前結束了。

沉重都不足以形容小菜農現在的狀態了,"我剛剛看了下你這種實現方式比之前改進了不少,但是我們應該還有更好的實現方式,不妨可以考慮下使用 websocket 來實現,沒事不要急,我們們可以回去再好好看看"。小菜農並沒有聽到想象中的責怪,不由心中一暖,Websocket!這回我可要了解清楚再動手實現了,可不能想之前那樣為了速度草草的實現了事 下定決心後,小菜農回到工位開始研究起了 Websocket

這次小菜農決定不再為了縮短工時而草草上線了,他開啟了搜尋引擎開始查詢關於《Websocket》的有關資料。

什麼是 websocket?

WebSocket 是一種基於 TCP 的網路協議,同時他也是一種 全雙工通訊的協議,既允許客戶端向服務端傳送訊息,也允許伺服器主動向客戶端傳送訊息。在 WebSocket 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就可以建立永續性的連線,進行雙向資料傳輸

在 WebSocket API 中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。

"好傢伙,這簡介直接概括了我的需求!秒啊~",小菜農喜出望外,天是那麼的藍~ 他迫不及待的往下看

WebSocket 有哪些特點?

1、支援雙向通訊,實時性更強

2、協議識別符號是 ws ,如果採用類似 Https 方式的加密就需要用 wss

3、輕量級,效能開銷小,通訊十分高效

4、建立在 TCP 協議智商,服務端的實現比較容易

原來是這麼一回事,小菜農開始分析自己前兩種實現方式的弊端

1、定時輪詢的方式

優點就是實現簡單,想到這個小菜農老臉一紅。缺點也是導師所說的,有一定延遲性,而且伺服器的壓力較大,浪費頻寬流量,因為絕大部分的請求是無效的

2、SSE 方式

這種方式和 websocket 有些類似,但是它只能單工通訊,建立連線後,只能由服務端發往客戶端,且佔用一個連線,如果需要客戶端向服務端通訊,需要額外再開啟一個連線

通過java編寫的服務端自帶websocket包,編寫如下:

客戶端實現 websocket 也十分簡單,只需要以下API

var Socket = new WebSocket(url, [protocol] );
第一個引數 url, 指定連線的 URL。第二個引數 protocol 是可選的,指定了可接受的子協議

在websocket 存在 4 種事件如下:

事件事件處理程式描述
openSocket.onopen連線建立時觸發
messageSocket.onmessage客戶端接收服務端資料時觸發
errorSocket.onerror通訊發生錯誤時觸發
closeSocket.onclose連線關閉時觸發

程式碼如下:

到這裡,websocket 通訊便已經實現了,當小菜農剛要準備提交的時候,一個念頭興起,websocket 是導師給我的建議,雖然我已經完成了,但是會不會有更好的方式,能讓導師眼前一亮? 想到這裡,小菜農不由磨手擦拳了。一番查詢,沒想到還真的讓他找到了,STOMP 協議~ 這應該就是我想要的了~

什麼是 STOMP 協議?

STOMP (Simple Text-Orientated Messaging Protocaol) ,它是一種簡單的面向文字的訊息傳遞協議,提供了一個可互操作的連線格式,允許 STOMP 客戶端向任意 STOMP 訊息代理 Broker 進行互動,設計簡單,易於開發

STOMP 的特點?

1、STOMP 是基於幀的協議,其 幀 是以 HTTP 為模型

2、STOMP 框架由命令,一組可選的標頭和可選的主體構成

3、STOMP 基於文字,但也允許傳輸二進位制訊息

這有點牛啊,走心的感嘆~

STOMP 幀是啥?

STOMP 的結構如下:

COMMAND
header1:value1
header2:value2

Body^@

傳送和接收分別使用命令 SENDSUBSCRIBE,並且還可以使用destination來描述訊息的內容和接受者

STOMP 的常用幀有哪些?

  • 連線相關

1、CONNECT (連線)

2、CONNECTED (成功連線)

  • 客戶端相關

1、SEND(傳送)

2、SUBSRIBE(訂閱)

3、UNSUBSCRIBE(取消訂閱)

4、BEGIN(開始)

5、COMMIT(提交)

6、ABORT(中斷)

7、ACK(確認)

8、NACK(否認)

9、DISCONNECT(斷開連線)

  • 服務端相關

1、MESSAGE(訊息)

2、RECEIPT(接收)

3、ERROR(錯誤)

小菜農吭哧吭哧地整理了關於 STOMP 的筆記,那麼為什麼有 websocket,還需要有 stomp,stomp的出現帶來了什麼好處,或是解決了什麼問題?。小菜農逐漸開始學會思考了,他又開始檢視 stomp 的相關資料,經過一番折騰,終於找到了些答案:WebSocket 的建立,就很類似使用 TCP 套接字傳輸,傳輸的報文是無定義的,也就是自由度很高,沒有明確的約定,那麼這個時候可能就需要一種高層面的應用協議來定義這些報文的語義格式,也就是說 STOMP 也是一種協議,一種作為 WebSocket 的子協議,能夠保證連線的兩端都遵循這些語義。

那麼使用 STOMP 的好處是什麼呢

1、STOMP 已經定義好了語義格式,我們就可以無需自定義

2、現成的 stomp.js 客戶端,開箱即用

3、可以使用配套的訊息代理進行廣播,適用於多叢集的情況(RabbitMQ、ActiveMQ)

瞭解到這裡再不動手寫程式碼就真的是在划水了,小菜農開啟專案開始擼程式碼了~

要使用 stomp,需要先定義 stomp 的配置類

image-20220219224218722

上面的ws就是前端的url,後端宣告端點,前端進行連線。

stomp 攔截器:

接收客戶端訊息的地方:

傳送訊息:

到這裡服務端部分的程式碼便已經實現了~客戶端部分也很簡單隻需要引入兩個 js 便可實現

這裡為了在客戶端接收到訊息,必須要先訂閱一個目的地 destination,也就是使用 subscribe()去訂閱,這個方法有兩個必需的引數:目的地回撥函式。還有一個可選的引數 headers

當客戶端與服務端連線成功後,可以呼叫 send()來傳送STOMP訊息。這個方法必須有一個引數,用來描述對應的STOMP的目的地。另外可以有兩個可選的引數:headersobject型別包含額外的資訊頭部

到這裡就已經實現了 stomp 的功能,小菜農連忙開啟頁面驗證下成果:

image-20220219221718490

​ 到這裡,小菜農便已經實現了線上客服的功能~ 雖然小菜農實現了聊天室的功能,但實現的過程中也遇到不小的困難,得趕緊記錄一下!

可以看到上面涉及到了一些關鍵詞:

  • Message:訊息,攜帶 header 和 payload
  • MessageHandler:處理 client 訊息的實體
  • MessageChannel:解耦訊息傳送者與訊息接收者的實體

    • clientInboudChannel:用於從 WebSocket 客戶端接收訊息
    • clientOutboundChannel:用於將伺服器訊息傳送給 WebSocket 客戶端
    • brokerChannel:用於從伺服器,應用程式中向訊息代理髮送訊息
  • Broker:存放訊息的中介軟體,client 可以訂閱 broker 中的訊息

可以看出stomp是一種類似訂閱釋出模式,我們可以動態靈活的宣告主題,前端可以訂閱不同的主題,接收到不同主題下的訊息,接觸過訊息佇列的小夥伴肯定不會陌生~

小菜農到此便完成了《人工客服》 的需求,想到自己之前因為沒有思緒而各種煩躁的行為不由尷尬一笑,所以說在遇到自己不會的時候切勿急躁,有問題還是要及時理清思路,可以多問,但不能不學~

不要空談,不要貪懶,和小菜一起做個吹著牛X做架構的程式猿吧~點個關注做個伴,讓小菜不再孤單。我們們下文見!

今天的你多努力一點,明天的你就能少說一句求人的話!

我是小菜,一個和你一起變強的男人。 ?

微信公眾號已開啟,小菜良記,沒關注的同學們記得關注哦!

相關文章