SpringBoot2.x系列教程 | WebSockets實現通訊

SpringBoot2。x系列教程|SpringBoot中整合WebSockets

作者:一一哥

一。 WebSockets簡介

1。 什麼是Websockets

WebSocket是HTML5的一種新的網路通訊協議,它實現了服務端與客戶端的全雙工通訊,建立在傳輸層TCP協議之上,即瀏覽器與服務端需要先建立TCP協議,再發送WebSocket連線建立請求。

2。 為什麼要有WebSockets

網路通訊已經有了http協議,為什麼還需要WebSocket協議?

因為http協議有一個缺陷,通訊只能由客戶端發起請求,伺服器端返回查詢結果。

比如說我們想要獲取一個實時的新聞資訊,在每次更新新聞資訊後,我們都需要重新整理頁面才能獲取到最新的資訊,只有再次發起客戶端請求,伺服器端才會返回結果。但是伺服器端不能做到推送訊息給客戶端,當然我們可以使用輪詢,檢視伺服器有沒有新的訊息,比如 “聊天室” 這樣的,但是輪詢效率是非常低的,因此WebSocket就這樣產生了。

SpringBoot2.x系列教程 | WebSockets實現通訊

3。 WebSocket建立連線過程

客戶端傳送請求資訊,服務端接收到這個請求並返回響應資訊。當連線建立後,客戶端傳送http請求時,透過Upgrade:webSocket Connection:Upgrade 告知伺服器需要建立的是WebSocket連線,並且還會傳遞WebSocket版本號、協議的字版本號、原始地址、主機地址, WebSocket相互通訊的Header很小,大概只有2Bytes。

SpringBoot2.x系列教程 | WebSockets實現通訊

4。 WebSocket的優點

WebSocket的最大優點就是

伺服器可以主動向客戶端推送訊息,客戶端也可以主動向伺服器端傳送訊息

使用WebSockets可以在伺服器與客戶端之間建立一個非http的雙向連線。這個連線是實時的,也是永久的(除非被關閉)。

當伺服器想向客戶端傳送資料時,可以立即將資料推送到客戶端的瀏覽器中,無需重新建立連結,只要客戶端有一個被開啟的socket(套接字)並且與伺服器建立連結,伺服器就可以把資料推送到這個socket上。

5。 WebSocket的前端API

5。1 建立連線

WebSocket需要接收一個url引數,然後呼叫WebSocket物件的構造器來建立與伺服器之間的通訊連結。

如下程式碼初始化:

var websocket = new WebSocket(‘wss://echo。websocket。org’);

注:

URL字串必須以 “ws” 或 “wss”(加密通訊)開頭。

利用上面的程式碼,我們的通訊連線建立之後,就可以進行客戶端與伺服器端的雙向通訊了。可以使用WebSocket物件的send方法對伺服器傳送資料,但是隻能傳送文字資料(我們可以使用JSON物件把任何js物件轉換成文字資料後再進行傳送)。

5。2 傳送訊息的方法

websocket。send(“data”);

5。3 接收伺服器傳過來的資料

透過onmessage事件來接收伺服器傳過來的資料,如下程式碼:

websocket。onmessage = function(event) { var data = event。data;}

5。4 監聽socket的開啟事件

透過獲取onopen事件來監聽socket的開啟事件。如下程式碼:

websocket。onopen = function(event) {// 開始通訊時的處理}

5。5 監聽socket的關閉事件

透過獲取onclose事件來監聽socket的關閉事件。如下程式碼:

websocket。onclose = function(event) {// 通訊結束時的處理}

5。6 關閉socket

透過close方法來關閉socket, 如下程式碼:

websocket。close();

5。6 WebSocket的狀態

可以透過讀取readyState的屬性值來獲取WebSocket物件的狀態,readyState屬性存在以下幾種屬性值。

CONNECTING(數字值為0),表示正在連線;

OPEN(數字值為1),表示已建立連線;

CLOSING(數字值為2),表示正在關閉連線;

CLOSED(數字值為3),表示已關閉連結。

二。 SpringBoot2。x整合WebSockets

1。 建立web專案

我們按照之前的經驗,建立一個web程式,並將之改造成Spring Boot專案,具體過程略。

SpringBoot2.x系列教程 | WebSockets實現通訊

2。 新增依賴包

org。springframework。boot spring-boot-starter-websocket org。springframework。boot spring-boot-starter-thymeleaf com。alibaba fastjson 1。2。46

3。建立WebSocket配置檔案

透過該配置類,開啟對WebSockets的支援。

package com。yyg。boot。config;import org。springframework。context。annotation。Bean;import org。springframework。context。annotation。Configuration;import org。springframework。web。socket。server。standard。ServerEndpointExporter;/** * @Author 一一哥Sun * @Date Created in 2020/5/13 * @Description Description */@Configurationpublic class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }}

4。 建立WebSockets的Server端

package com。yyg。boot。websockets;import com。alibaba。fastjson。JSON;import com。alibaba。fastjson。JSONObject;import lombok。extern。slf4j。Slf4j;import org。springframework。stereotype。Component;import org。springframework。util。StringUtils;import javax。websocket。*;import javax。websocket。server。PathParam;import javax。websocket。server。ServerEndpoint;import java。io。IOException;import java。util。concurrent。ConcurrentHashMap;/** * @Author 一一哥Sun * @Date Created in 2020/5/13 * @Description Description */@Component@ServerEndpoint(“/server/{uid}”)@Slf4jpublic class WebSocketServer { /** * 用來記錄當前線上連線數量,應該把它設計成執行緒安全的。 */ private static int onlineCount = 0; /** * concurrent包是執行緒安全的Set,用來存放每個客戶端對應的WebSocket物件。 */ private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>(); /** * 與某個客戶端的連線會話,需要透過它來給客戶端傳送資料。 */ private Session session; /** * 接收客戶端訊息的uid */ private String uid = “”; /** * 連線建立成功呼叫的方法 */ @OnOpen public void onOpen(Session session, @PathParam(“uid”) String uid) { this。session = session; this。uid = uid; if (webSocketMap。containsKey(uid)) { webSocketMap。remove(uid); //加入到set中 webSocketMap。put(uid, this); } else { //加入set中 webSocketMap。put(uid, this); //線上數加1 addOnlineCount(); } log。info(“使用者連線:” + uid + “,當前線上人數為:” + getOnlineCount()); try { sendMsg(“連線成功”); } catch (IOException e) { log。error(“使用者:” + uid + “,網路異常!!!!!!”); } } /** * 連線關閉呼叫的方法 */ @OnClose public void onClose() { if (webSocketMap。containsKey(uid)) { webSocketMap。remove(uid); //從set中刪除 subOnlineCount(); } log。info(“使用者退出:” + uid + “,當前線上人數為:” + getOnlineCount()); } /** * 收到客戶端訊息後呼叫的方法 * @param message 客戶端傳送過來的訊息 */ @OnMessage public void onMessage(String message, Session session) { log。info(“使用者id:” + uid + “,接收到的報文:” + message); //可以群發訊息 //訊息儲存到資料庫、redis if (!StringUtils。isEmpty(message)) { try { //解析傳送的報文 JSONObject jsonObject = JSON。parseObject(message); //追加發送人(防止串改) jsonObject。put(“fromUID”, this。uid); String toUID = jsonObject。getString(“toUID”); //傳送給對應的toUserId使用者的WebSocket if (!StringUtils。isEmpty(toUID) && webSocketMap。containsKey(toUID)) { webSocketMap。get(toUID)。sendMsg(jsonObject。toJSONString()); } else { //若果不在這個伺服器上,可以考慮傳送到mysql或者redis log。error(“請求的UserId:” + toUID + “不在該伺服器上”); } } catch (Exception e) { e。printStackTrace(); } } } /** * 處理錯誤 */ @OnError public void onError(Session session, Throwable error) { log。error(“使用者錯誤:” + this。uid + “,原因:” + error。getMessage()); error。printStackTrace(); } /** * 實現伺服器主動推送 */ private void sendMsg(String msg) throws IOException { this。session。getBasicRemote()。sendText(msg); } /** * 傳送自定義訊息 */ public static void sendInfo(String message, @PathParam(“uid”) String uid) throws IOException { log。info(“傳送訊息到:” + uid + “,傳送的報文:” + message); if (!StringUtils。isEmpty(uid) && webSocketMap。containsKey(uid)) { webSocketMap。get(uid)。sendMsg(message); } else { log。error(“使用者” + uid + “,不線上!”); } } private static synchronized int getOnlineCount() { return onlineCount; } private static synchronized void addOnlineCount() { WebSocketServer。onlineCount++; } private static synchronized void subOnlineCount() { WebSocketServer。onlineCount——; }}

5。 建立Controller介面

package com。yyg。boot。web;import com。yyg。boot。websockets。WebSocketServer;import org。springframework。http。ResponseEntity;import org。springframework。web。bind。annotation。GetMapping;import org。springframework。web。bind。annotation。PathVariable;import org。springframework。web。bind。annotation。RequestMapping;import org。springframework。web。bind。annotation。RestController;import org。springframework。web。servlet。ModelAndView;import java。io。IOException;/** * @Author 一一哥Sun * @Date Created in 2020/5/13 * @Description Description */@RestControllerpublic class WebSocketController { @GetMapping(“/page”) public ModelAndView page() { return new ModelAndView(“webSocket”); } @RequestMapping(“/push/{toUID}”) public ResponseEntity pushToClient(String message, @PathVariable String toUID) throws IOException { WebSocketServer。sendInfo(message, toUID); return ResponseEntity。ok(“Send Success!”); }}

6。 建立前端頁面傳送訊息

<!DOCTYPE html> WebSocket通訊

【uid】:

【toUID】:

【Msg】:

【第一步操作:】:

【第二步操作:】:

7。 配置application。yml

server: port: 8080 servlet: context-path: /socketspring: http: encoding: force: true charset: UTF-8 application: name: websocket-demo freemarker: request-context-attribute: request prefix: /templates/ suffix: 。html content-type: text/html enabled: true cache: false charset: UTF-8 allow-request-override: false expose-request-attributes: true expose-session-attributes: true expose-spring-macro-helpers: true

8。 建立入口類

package com。yyg。boot;import org。springframework。boot。SpringApplication;import org。springframework。boot。autoconfigure。SpringBootApplication;/** * @Author 一一哥Sun * @Date Created in 2020/5/13 * @Description Description */@SpringBootApplicationpublic class SocketApplication { public static void main(String[] args){ SpringApplication。run(SocketApplication。class,args); }}

9。 完整專案結構

SpringBoot2.x系列教程 | WebSockets實現通訊

10。 啟動專案進行測試

我們需要在瀏覽器中開啟另一個頁面,網址都是:http://localhost:8080/socket/page

SpringBoot2.x系列教程 | WebSockets實現通訊

首先我們在第一個頁面中,輸入如下引數資訊,點擊發送訊息按鈕:

SpringBoot2.x系列教程 | WebSockets實現通訊

可以看到Console控制檯顯示的日誌資訊。

然後我們去Intellij idea中看看後臺列印的日誌資訊:

SpringBoot2.x系列教程 | WebSockets實現通訊

然後我們在第二個頁面中,輸入如下引數資訊,點擊發送訊息按鈕:

SpringBoot2.x系列教程 | WebSockets實現通訊

然後我們去Intellij idea中看看後臺列印的日誌資訊:

SpringBoot2.x系列教程 | WebSockets實現通訊

再然後我們回到第一個頁面,可以看到log中展示瞭如下資訊:

SpringBoot2.x系列教程 | WebSockets實現通訊

說明接收到了從另一個客戶端發來的資訊。

如果在第一個頁面中再次點擊發送資訊的按鈕,同樣的在第二個頁面中可以收到日誌資訊:

SpringBoot2.x系列教程 | WebSockets實現通訊

至此,我們就實現了WebSocket通訊。