《菜農(nóng)升職記》之 Websocket
大家好呀,我是小菜~
本文主要介紹
websocket 的使用微信公眾號(hào)已開啟,小菜良記,沒關(guān)注的同學(xué)們記得關(guān)注哦!
作為準(zhǔn)應(yīng)屆生的小菜農(nóng)早早的便找到了一份實(shí)習(xí)工作,初到公司一切都沒那么適應(yīng),作為導(dǎo)師的程立這天給小菜農(nóng)安排了一個(gè)需求,想要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的《人工客服》需求,也就是即時(shí)通訊。小菜農(nóng)盡管沒啥經(jīng)驗(yàn),但為了給導(dǎo)師留下良好的印象便一口爽快的接下需求。
接下需求后小菜農(nóng)便開始構(gòu)思如何實(shí)現(xiàn)即時(shí)通訊,他開始在各大平臺(tái)查看關(guān)于在線客服的案例~
i他總結(jié)了下需求:在線客服,需求理解起來很簡(jiǎn)單,就相當(dāng)于一個(gè) web 的聊天頁面,也就是客戶端能夠即時(shí)拉取到服務(wù)端的響應(yīng),盡管平時(shí)微信重度使用,但是到了自己實(shí)現(xiàn)的時(shí)候卻滿頭霧水,眼看一上午的時(shí)間就要過去了,自己卻沒有任何進(jìn)展,唯一的進(jìn)展便是找到了以上那張圖,卻沒有絲毫卵用~中午吃完飯,其他人都已經(jīng)息屏休息了,而小菜農(nóng)還在電腦前為這個(gè)需求而煩惱,不由有些煩躁起來了,開始想念在學(xué)校的日子了。通過接口獲取響應(yīng)!前端整個(gè)定時(shí)任務(wù)去撈消息這個(gè)奇妙的想法直接沖擊菜農(nóng)的大腦
"妙啊",小菜農(nóng)為自己的好主意開始津津樂道起來,煩惱來得快去的也快~不由多想便開始吭哧吭哧的寫起代碼了,那一套可謂是行云流水
偽代碼如下:
服務(wù)端:

客戶端:
setInterval(function?()?{
????$.ajax({
????????async:?false,
????????url:?"localhost:8080/roll",
????????type:?"get",
???????
????????success(data){
?????????console.log("success");
????????}
????})
},1000)
寫完后,小菜農(nóng)簡(jiǎn)單驗(yàn)證了下,發(fā)現(xiàn)都功能已經(jīng)滿足了便開始向?qū)煶塘howcase了,程立簡(jiǎn)單地過了遍頁面效果,感覺效果在預(yù)期內(nèi)便讓小菜農(nóng)提交代碼準(zhǔn)備合并發(fā)布了
小菜農(nóng)提交完代碼后心中不由歡喜起來,自我感覺十分良好,能在規(guī)定時(shí)間內(nèi)完成這個(gè)不是那么簡(jiǎn)單的需求,想必離自己的轉(zhuǎn)正又進(jìn)一步了吧!但是沒等小菜農(nóng)高興太久,電腦上便閃起了導(dǎo)師的叮叮提示,"小菜農(nóng),現(xiàn)在有空嗎,過來下"。不好的念頭浮上小菜農(nóng)的心頭,"這該不會(huì)出 bug 了吧"。小菜農(nóng)顫顫巍巍的來到導(dǎo)師的工位,"我剛剛 review 了下你的代碼",原來還沒發(fā)布,那就不是bug的事情了,幸好幸好~ 小菜農(nóng)心中暗想。"我看了下你這個(gè)功能的實(shí)現(xiàn)方式,這種方式盡管能夠?qū)崿F(xiàn)需求,但是并不是很好的解決方案",導(dǎo)師接著說。“通過輪詢的方法,盡管可以從服務(wù)端撈到聊天數(shù)據(jù),但是接口的頻繁請(qǐng)求缺陷也會(huì)很明顯,十分浪費(fèi)帶寬流量,服務(wù)器的壓力就會(huì)比較大,所以這種方式并不是很好的解決方法,你可以回去再想想看有沒有什么其他比較好的解決方法!”
"嗯嗯,是我沒考慮好,那我回去再改改!"小菜農(nóng)涉世未深,導(dǎo)師都這樣說了,那這個(gè)方案肯定得 pass,連忙接道。
小菜農(nóng)回到工位后,難免有些沮喪,本來想好好表現(xiàn)表現(xiàn),沒想到自己想出的方案弊端這么多。一陣頭大,現(xiàn)在也沒時(shí)間想這件事,如何實(shí)現(xiàn)才是要緊之事!小菜農(nóng)又陷入了沉思,這可該如何是好~
小菜農(nóng)隨后便打開了某度,看到了一個(gè)關(guān)鍵詞 SSE
SSE 全稱 Server-Sent Events,指的是網(wǎng)頁自動(dòng)獲取來自服務(wù)器的更新,也就是自動(dòng)化獲取服務(wù)端推送至網(wǎng)頁的數(shù)據(jù),這是一個(gè) H5 的屬性,除了 IE,其他標(biāo)準(zhǔn)瀏覽器基本都兼容
小菜農(nóng)認(rèn)真研究了下,發(fā)現(xiàn)這種方式和自己之前的實(shí)現(xiàn)方式有些相似,但是就不需要客戶端定時(shí)去獲取,而是服務(wù)端向客戶端聲明要發(fā)送流信息,然后連續(xù)不斷地發(fā)送過來。這時(shí)客戶端是不會(huì)關(guān)閉連接的,會(huì)一直等這服務(wù)器發(fā)過來的新的數(shù)據(jù)流。"妙啊,這樣子不就不會(huì)頻繁建立連接,浪費(fèi)帶寬了",小菜農(nóng)又興奮了起來,這回肯定能夠滿足導(dǎo)師的需求了!小菜農(nóng)又花費(fèi)一個(gè)下午的時(shí)間將代碼實(shí)現(xiàn)方式重構(gòu)了一遍,便提交了~
偽代碼
服務(wù)端:

客戶端:

這回可別再出意外了!小菜農(nóng)心中默念,但是好景不長(zhǎng),叮叮又開始閃爍了,這這這。。。小菜農(nóng)的心態(tài)有些崩了,完了,這回試用期可能要提前結(jié)束了。
沉重都不足以形容小菜農(nóng)現(xiàn)在的狀態(tài)了,"我剛剛看了下你這種實(shí)現(xiàn)方式比之前改進(jìn)了不少,但是我們應(yīng)該還有更好的實(shí)現(xiàn)方式,不妨可以考慮下使用 websocket 來實(shí)現(xiàn),沒事不要急,咱們可以回去再好好看看"。小菜農(nóng)并沒有聽到想象中的責(zé)怪,不由心中一暖,Websocket!這回我可要了解清楚再動(dòng)手實(shí)現(xiàn)了,可不能想之前那樣為了速度草草的實(shí)現(xiàn)了事 下定決心后,小菜農(nóng)回到工位開始研究起了 Websocket
這次小菜農(nóng)決定不再為了縮短工時(shí)而草草上線了,他打開了搜索引擎開始查找關(guān)于《Websocket》的有關(guān)資料。
什么是 websocket?
WebSocket 是一種基于 TCP 的網(wǎng)絡(luò)協(xié)議,同時(shí)他也是一種 全雙工通信的協(xié)議,既允許客戶端向服務(wù)端發(fā)送消息,也允許服務(wù)器主動(dòng)向客戶端發(fā)送消息。在 WebSocket 中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就可以建立持久性的連接,進(jìn)行雙向數(shù)據(jù)傳輸
在 WebSocket API 中,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作,然后,瀏覽器和服務(wù)器之間就形成了一條快速通道。兩者之間就直接可以數(shù)據(jù)互相傳送。
"好家伙,這簡(jiǎn)介直接概括了我的需求!秒啊~",小菜農(nóng)喜出望外,天是那么的藍(lán)~ 他迫不及待的往下看
WebSocket 有哪些特點(diǎn)?
1、支持雙向通信,實(shí)時(shí)性更強(qiáng)
2、協(xié)議標(biāo)識(shí)符是 ws ,如果采用類似 Https 方式的加密就需要用 wss
3、輕量級(jí),性能開銷小,通信十分高效
4、建立在 TCP 協(xié)議之上,服務(wù)端的實(shí)現(xiàn)比較容易
原來是這么一回事,小菜農(nóng)開始分析自己前兩種實(shí)現(xiàn)方式的弊端
1、定時(shí)輪詢的方式
優(yōu)點(diǎn)就是實(shí)現(xiàn)簡(jiǎn)單,想到這個(gè)小菜農(nóng)老臉一紅。缺點(diǎn)也是導(dǎo)師所說的,有一定延遲性,而且服務(wù)器的壓力較大,浪費(fèi)帶寬流量,因?yàn)榻^大部分的請(qǐng)求是無效的
2、SSE 方式
這種方式和 websocket 有些類似,但是它只能單工通信,建立連接后,只能由服務(wù)端發(fā)往客戶端,且占用一個(gè)連接,如果需要客戶端向服務(wù)端通信,需要額外再打開一個(gè)連接

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

客戶端實(shí)現(xiàn) websocket 也十分簡(jiǎn)單,只需要以下API
var?Socket?=?new?WebSocket(url,?[protocol]?);
第一個(gè)參數(shù) url, 指定連接的 URL。第二個(gè)參數(shù) protocol 是可選的,指定了可接受的子協(xié)議
在websocket 存在 4 種事件如下:
| 事件 | 事件處理程序 | 描述 |
|---|---|---|
| open | Socket.onopen | 連接建立時(shí)觸發(fā) |
| message | Socket.onmessage | 客戶端接收服務(wù)端數(shù)據(jù)時(shí)觸發(fā) |
| error | Socket.onerror | 通信發(fā)生錯(cuò)誤時(shí)觸發(fā) |
| close | Socket.onclose | 連接關(guān)閉時(shí)觸發(fā) |
代碼如下:

到這里,websocket 通信便已經(jīng)實(shí)現(xiàn)了,當(dāng)小菜農(nóng)剛要準(zhǔn)備提交的時(shí)候,一個(gè)念頭興起,websocket 是導(dǎo)師給我的建議,雖然我已經(jīng)完成了,但是會(huì)不會(huì)有更好的方式,能讓導(dǎo)師眼前一亮? 想到這里,小菜農(nóng)不由磨手擦拳了。一番查找,沒想到還真的讓他找到了,STOMP 協(xié)議~ 這應(yīng)該就是我想要的了~
什么是 STOMP 協(xié)議?
STOMP (Simple Text-Orientated Messaging Protocaol) ,它是一種簡(jiǎn)單的面向文本的消息傳遞協(xié)議,提供了一個(gè)可互操作的連接格式,允許 STOMP 客戶端向任意 STOMP 消息代理 Broker 進(jìn)行交互,設(shè)計(jì)簡(jiǎn)單,易于開發(fā)
STOMP 的特點(diǎn)?
1、STOMP 是基于幀的協(xié)議,其 幀 是以 HTTP 為模型
2、STOMP 框架由命令,一組可選的標(biāo)頭和可選的主體構(gòu)成
3、STOMP 基于文本,但也允許傳輸二進(jìn)制消息
這有點(diǎn)牛啊,走心的感嘆~
STOMP 幀是啥?
STOMP 的結(jié)構(gòu)如下:
COMMAND
header1:value1
header2:value2
Body^@發(fā)送和接收分別使用命令
SEND和SUBSCRIBE,并且還可以使用destination來描述消息的內(nèi)容和接受者
STOMP 的常用幀有哪些?
- 連接相關(guān)
1、CONNECT (連接)
2、CONNECTED (成功連接)
- 客戶端相關(guān)
1、SEND(發(fā)送)
2、SUBSRIBE(訂閱)
3、UNSUBSCRIBE(取消訂閱)
4、BEGIN(開始)
5、COMMIT(提交)
6、ABORT(中斷)
7、ACK(確認(rèn))
8、NACK(否認(rèn))
9、DISCONNECT(斷開連接)
- 服務(wù)端相關(guān)
1、MESSAGE(消息)
2、RECEIPT(接收)
3、ERROR(錯(cuò)誤)
小菜農(nóng)吭哧吭哧地整理了關(guān)于 STOMP 的筆記,那么為什么有 websocket,還需要有 stomp,stomp的出現(xiàn)帶來了什么好處,或是解決了什么問題?。小菜農(nóng)逐漸開始學(xué)會(huì)思考了,他又開始查看 stomp 的相關(guān)資料,經(jīng)過一番折騰,終于找到了些答案:WebSocket 的創(chuàng)建,就很類似使用 TCP 套接字傳輸,傳輸?shù)膱?bào)文是無定義的,也就是自由度很高,沒有明確的約定,那么這個(gè)時(shí)候可能就需要一種高層面的應(yīng)用協(xié)議來定義這些報(bào)文的語義格式,也就是說 STOMP 也是一種協(xié)議,一種作為 WebSocket 的子協(xié)議,能夠保證連接的兩端都遵循這些語義。
那么使用 STOMP 的好處是什么呢
1、STOMP 已經(jīng)定義好了語義格式,我們就可以無需自定義
2、現(xiàn)成的 stomp.js 客戶端,開箱即用
3、可以使用配套的消息代理進(jìn)行廣播,適用于多集群的情況(RabbitMQ、ActiveMQ)
了解到這里再不動(dòng)手寫代碼就真的是在劃水了,小菜農(nóng)打開項(xiàng)目開始擼代碼了~
要使用 stomp,需要先定義 stomp 的配置類
image-20220219224218722上面的ws就是前端的url,后端聲明端點(diǎn),前端進(jìn)行連接。
stomp 攔截器:

接收客戶端消息的地方:

發(fā)送消息:

到這里服務(wù)端部分的代碼便已經(jīng)實(shí)現(xiàn)了~客戶端部分也很簡(jiǎn)單只需要引入兩個(gè) js 便可實(shí)現(xiàn)

這里為了在客戶端接收到消息,必須要先訂閱一個(gè)目的地 destination,也就是使用 subscribe()去訂閱,這個(gè)方法有兩個(gè)必需的參數(shù):目的地,回調(diào)函數(shù)。還有一個(gè)可選的參數(shù) headers。
當(dāng)客戶端與服務(wù)端連接成功后,可以調(diào)用 send()來發(fā)送STOMP消息。這個(gè)方法必須有一個(gè)參數(shù),用來描述對(duì)應(yīng)的STOMP的目的地。另外可以有兩個(gè)可選的參數(shù):headers,object類型包含額外的信息頭部
到這里就已經(jīng)實(shí)現(xiàn)了 stomp 的功能,小菜農(nóng)連忙打開頁面驗(yàn)證下成果:

image-20220219221718490到這里,小菜農(nóng)便已經(jīng)實(shí)現(xiàn)了在線客服的功能~ 雖然小菜農(nóng)實(shí)現(xiàn)了聊天室的功能,但實(shí)現(xiàn)的過程中也遇到不小的困難,得趕緊記錄一下!
可以看到上面涉及到了一些關(guān)鍵詞:
- Message:消息,攜帶 header 和 payload
- MessageHandler:處理 client 消息的實(shí)體
- MessageChannel:解耦消息發(fā)送者與消息接收者的實(shí)體
- clientInboudChannel:用于從 WebSocket 客戶端接收消息
- clientOutboundChannel:用于將服務(wù)器消息發(fā)送給 WebSocket 客戶端
- brokerChannel:用于從服務(wù)器,應(yīng)用程序中向消息代理發(fā)送消息
- Broker:存放消息的中間件,client 可以訂閱 broker 中的消息
可以看出stomp是一種類似訂閱發(fā)布模式,我們可以動(dòng)態(tài)靈活的聲明主題,前端可以訂閱不同的主題,接收到不同主題下的消息,接觸過消息隊(duì)列的小伙伴肯定不會(huì)陌生~
小菜農(nóng)到此便完成了《人工客服》 的需求,想到自己之前因?yàn)闆]有思緒而各種煩躁的行為不由尷尬一笑,所以說在遇到自己不會(huì)的時(shí)候切勿急躁,有問題還是要及時(shí)理清思路,可以多問,但不能不學(xué)~
不要空談,不要貪懶,和小菜一起做個(gè)吹著牛X做架構(gòu)的程序猿吧~點(diǎn)個(gè)關(guān)注做個(gè)伴,讓小菜不再孤單。咱們下文見!
今天的你多努力一點(diǎn),明天的你就能少說一句求人的話!
我是小菜,一個(gè)和你一起變強(qiáng)的男人。
??微信公眾號(hào)已開啟,小菜良記,沒關(guān)注的同學(xué)們記得關(guān)注哦!
