2008年6月誕生了一個影響計算機世界的通訊協議,原先需要二十臺計算機資源才能支撐的業務場景,現在只需要一臺,這得幫"摳門"老闆們省下多少錢,它就是大名鼎鼎的WebSocket協議。很快在下一年也就是2009年的12月,Google瀏覽器就宣佈成為第一個支援WebSocket標準的瀏覽器。
WebSocket的推動者和設計者就是下面的Michael Carter,他設計的WebSocket協議技術現在每天在全地球有超過20億的裝置在使用。
1. WebSocket概念
1.1 為什麼會出現WebSocket
面試官:有了解過WebSocket嗎?
一般的Http請求我們只有主動去請求介面,才能獲取到伺服器的資料。例如前後端分離的開發場景,自嘲為切圖仔實際扮豬吃老虎的前端大佬找你要一個配置資訊
的介面,我們後端開發三下兩下開發出一個RESTful
架構風格的API介面,只有當前端主動請求,後端介面纔會響應。
但上文這種基於HTTP的請求-響應模式並不能滿足實時數據通信的場景,例如遊戲、聊天室等實時業務場景。現在救世主來了,WebSocket作為一款主動推送技術,可以實現服務端主動推送資料給客戶端。大家有沒聽說過全雙工、半雙工的概念。
全雙工通訊允許資料同時雙向流動,而半雙工通訊則是資料交替在兩個方向上傳輸,但在任一時刻只能一個方向上有數據流動
HTTP通訊協議就是半雙工,而資料實時傳輸需要的是全雙工通訊機制,WebSocket採用的便是全雙工通訊。舉個微信聊天的例子,企業微信炸鍋了,有成百條訊息轟炸你手機,要實現這個場景,大家要怎麼設計?用iframe、Ajax非同步互動技術配合以客戶端長輪詢不斷請求伺服器資料也可以實現,但造成的問題是伺服器資源的無端消耗,運維大佬直接找到你工位來。顯然服務端主動推送資料的WebSocket技術更適合聊天業務場景。
1.2 WebSocket優點
面試官:為什麼WebSocket可以減少資源消耗?
大家先看看傳統的Ajax長輪詢和WebSocket效能上掰手腕誰厲害。在websocket.org網站提供的Use Case C
的測試裡,客戶端輪詢頻率為10w/s,使用Poling長輪詢每秒需要消耗高達665Mbps,而我們的新寵兒WebSocet僅僅只需要花費1.526Mbps,435倍的差距!!
為什麼差距會這麼大?南哥告訴你,WebSocket技術設計的目的就是要取代輪詢技術和Comet技術。Http訊息十分冗長和繁瑣,一個Http訊息就要包含了起始行、訊息頭、訊息體、空行、換行符,其中請求頭Header非常冗長,在大量Http請求的場景會佔用過多的頻寬和伺服器資源。
大家看下百度翻譯介面的Http請求,複製成curl命令是非常冗長的,可用的訊息肉眼看過去沒多少。
curl ^"https://fanyi.baidu.com/mtpe-individual/multimodal?query=^%^E6^%^B5^%^8B^%^E8^%^AF^%^95&lang=zh2en^" ^ -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" ^ -H "Accept-Language: zh-CN,zh;q=0.9" ^ -H "Cache-Control: max-age=0" ^ -H "Connection: keep-alive" ^ -H ^"Cookie: BAIDUID=C8FA8569F446CB3F684CCD2C2B32721E:FG=1; BAIDUID_BFESS=C8FA8569F446CB3F684CCD2C2B32721E:FG=1; ab_sr=1.0.1_NDhjYWQyZmRjOWIwYjI3NTNjMGFiODExZWFiMWU4NTY4MjA2Y2UzNGQwZjJjZjI1OTdlY2JmOThlNzk1ZDAxMDljMTA2NTMxYmNlM1OTQ1MTE0ZTI3Y2M0NTIzMzdkMmU2MGMzMjc1OTRiM2EwNTJQ==; RT=^\^"z=1&dm=baidu.com&si=b9941642-0feb-4402-ac2b-a913a3eef1&ss=ly866fx&sl=4&tt=38d&bcn=https^%^3A^%^2F^%^2Ffclog.baidu.com^%^2Flog^%^2Fweirwood^%^3Ftype^%^3Dp&ld=ccy&ul=jes^\^"^" ^ -H "Sec-Fetch-Dest: document" ^ -H "Sec-Fetch-Mode: navigate" ^ -H "Sec-Fetch-Site: same-origin" ^ -H "Sec-Fetch-User: ?1" ^ -H "Upgrade-Insecure-Requests: 1" ^ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" ^ -H ^"sec-ch-ua: ^\^"Not/A)Brand^\^";v=^\^"8^\^", ^\^"Chromium^\^";v=^\^"126^\^", ^\^"Google Chrome^\^";v=^\^"126^\^"^" ^ -H "sec-ch-ua-mobile: ?0" ^ -H ^"sec-ch-ua-platform: ^\^"Windows^\^"^" &
而WebSocket是基於幀傳輸的,只需要做一次握手動作就可以讓客戶端和服務端形成一條通訊通道,這僅僅只需要2個位元組。我搭建了一個SpringBoot整合的WebSocket專案,瀏覽器複製WebSocket的Curl命令十分簡潔明瞭,大家對比下。
curl "ws://localhost:8080/channel/echo" ^ -H "Pragma: no-cache" ^ -H "Origin: http://localhost:8080" ^ -H "Accept-Language: zh-CN,zh;q=0.9" ^ -H "Sec-WebSocket-Key: VoUk/1sA1lGGgMElV/5RPQ==" ^ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" ^ -H "Upgrade: websocket" ^ -H "Cache-Control: no-cache" ^ -H "Connection: Upgrade" ^ -H "Sec-WebSocket-Version: 13" ^ -H "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits"
如果你要區分Http請求或是WebSocket請求很簡單,WebSocket請求的請求行字首都是固定是ws://
。
2. WebSocket實踐
2.1 整合WebSocket伺服器
面試官:有沒動手實踐過WebSocket?
大家要在SpringBoot使用WebSocket的話,可以整合spring-boot-starter-websocket
,引入南哥下面給的pom依賴。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> </dependencies>
感興趣點開spring-boot-starter-websocket
依賴的話,你會發現依賴所引用包名為package jakarta.websocket
。這代表SpringBoot其實是整合了Java EE開源的websocket專案。這裏有個小故事,Oracle當年決定將Java EE移交給Eclipse基金會後,Java EE就進行了改名,現在Java EE更名為Jakarta EE。Jakarta是雅加達的意思,有誰知道有什麼寓意嗎,評論區告訴我下?
我們的程式匯入websocket依賴後,應用程式就可以看成是一臺小型的WebSocket伺服器。我們透過@ServerEndpoint可以定義WebSocket伺服器對客戶端暴露的介面。
@ServerEndpoint(value = "/channel/echo")
而WebSocket伺服器要推送訊息給到客戶端,則使用package jakarta.websocket
下的Session物件,呼叫sendText
傳送服務端訊息。
private Session session; @OnMessage public void onMessage(String message) throws IOException{ LOGGER.info("[websocket] 服務端收到客戶端{}訊息:message={}", this.session.getId(), message); this.session.getAsyncRemote().sendText("halo, 客戶端" + this.session.getId()); }
看下getAsyncRemote
方法返回的物件,裡面是一個遠端端點例項。
RemoteEndpoint.Async getAsyncRemote();
2.2 客戶端傳送訊息
面試官:那客戶端怎麼傳送訊息給伺服器?
客戶端傳送訊息要怎麼操作?這點還和Http請求很不一樣。後端開發出介面後,我們在Swagger填充引數,點選Try it out
,Http請求就發過去了。
但WebSocket需要我們在瀏覽器的控制檯上操作,例如現在南哥要給我們的WebSocket伺服器傳送Halo,JavaGetOffer
,可以在瀏覽器的控制檯手動執行以下命令。
websocket.send("Halo,JavaGetOffer");
實踐的操作介面如下。