WebSocket實現前後端通訊
WebSocket實現前後端通訊
長安如夢裡,何日是歸期。
簡介:我們上線了一個商城項目,移交運營團隊使用之後,他們要求商城有新訂單來的時候同時加上聲音提示,讓她們可以及時知道有單來了。我這邊想了想,這個需求是在後端完成還是前端完成,但是仔細一想,無論是在前端還是後端完成都一樣,需求註定甩不出去了,因為這個商城的後台管理沒有前端工程師,前後端的工作都是一個後端工程師來完成的。這也導致前端介面很難看,包括前端程式碼風格~因為這是我第一次寫這麼多前端程式碼的項目~哈哈~敢讓我寫~我就敢寫。
一、思路:
1、平台實現推送,之在前的項目有用過Ajax輪詢的技術,這種方式瀏覽器需要不斷的向伺服器發出請求,會浪費很多的頻寬等資源,技術可行但不太好。
2、完完全全在前端實現此需求,在前端監聽訂單列表中元素的變化,循環遍歷訂單列表監聽或使用Vue的Watch監聽,當訂單列表有新增元素即可調用播放音效API,感覺不怎麼靠譜,沒去試過。
3、最終使用了Websocket實現的 ,WebSocket是HTML5開始提供的一種在單個TCP連接上進行全雙工通訊的協議,能更好的節省伺服器資源和頻寬,並且能夠更實時地進行通訊。WebSocket 使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據,在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以創建持久性的連接-長連接,並進行雙向數據傳輸。我猜使用這個方案最重要的原因是後端開發是我更擅長的領域吧。
二、SpringBoot/SpringCloud整合WebSocket
1、引入WebSocket Jar包
在需要用到WebSocket 通訊的SpringBoot 工程的pom.xml 文件中引入WebSocket Jar包。
1 <!-- test websocket -->
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-websocket</artifactId>
5 </dependency>
2、創建WebSocket 配置類
該配置類用於檢測帶註解@ServerEndpoint 的bean 並將它們註冊到容器中。


1 package com.tjt.mall.config;
2
3 import lombok.extern.slf4j.Slf4j;
4 import org.springframework.context.annotation.Bean;
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
7
8
9 @Configuration
10 @Slf4j
11 public class WebSocketConfig {
12 /**
13 * 給spring容器注入這個ServerEndpointExporter對象
14 * 相當於xml:
15 * <beans>
16 * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/>
17 * </beans>
18 * <p>
19 * 檢測所有帶有@serverEndpoint註解的bean並註冊他們。
20 *
21 * @return
22 */
23 @Bean
24 public ServerEndpointExporter serverEndpointExporter() {
25 log.info("serverEndpointExporter被注入了");
26 return new ServerEndpointExporter();
27 }
28 }
View Code
3、創建WebSocket 處理類
該WebSocket 處理類需要使用@ServerEndpoint 註解,在這個類里監聽連接的建立、關閉和消息的接收等。


1 package com..mall.config;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.stereotype.Component;
6
7 import javax.annotation.PostConstruct;
8 import javax.websocket.*;
9 import javax.websocket.server.ServerEndpoint;
10 import java.io.IOException;
11 import java.util.concurrent.CopyOnWriteArraySet;
12 import java.util.concurrent.atomic.AtomicInteger;
13
14 @ServerEndpoint(value = "/ws/path/asset") // WebSocket 路徑
15 @Component
16 public class WebSocketServer {
17
18 private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
19 private static final AtomicInteger OnlineCount = new AtomicInteger(0);
20 // concurrent包的執行緒安全Set,用來存放每個客戶端對應的Session對象。
21 private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
22
23 @PostConstruct
24 public void init() {
25 log.info("websocket 載入");
26 }
27
28
29 /**
30 * 連接建立成功調用的方法
31 */
32 @OnOpen
33 public void onOpen(Session session) throws IOException{
34 SessionSet.add(session);
35 int cnt = OnlineCount.incrementAndGet(); // 在線數加1
36 log.info("有連接加入,當前連接數為:{}", cnt);
37 // SendMessage(session, "連接成功");
38 }
39
40 /**
41 * 連接關閉調用的方法
42 */
43 @OnClose
44 public void onClose(Session session) {
45 SessionSet.remove(session);
46 int cnt = OnlineCount.decrementAndGet();
47 log.info("有連接關閉,當前連接數為:{}", cnt);
48 }
49
50 /**
51 * 收到客戶端消息後調用的方法
52 *
53 * @param message
54 * 客戶端發送過來的消息
55 */
56 @OnMessage
57 public void onMessage(String message, Session session) throws IOException {
58 log.info("來自客戶端的消息:{}",message);
59 SendMessage(session, "收到消息,消息內容:"+message);
60
61 }
62
63 /**
64 * 出現錯誤
65 * @param session
66 * @param error
67 */
68 @OnError
69 public void onError(Session session, Throwable error) {
70 log.error("發生錯誤:{},Session ID: {}",error.getMessage(),session.getId());
71 error.printStackTrace();
72 }
73
74 /**
75 * 發送消息,實踐表明,每次瀏覽器刷新,session會發生變化。
76 * @param session
77 * @param message
78 */
79 public static void SendMessage(Session session, String message) throws IOException {
80 try {
81 // session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
82 session.getBasicRemote().sendText(message);
83 } catch (IOException e) {
84 log.error("發送消息出錯:{}", e.getMessage());
85 e.printStackTrace();
86 }
87 }
88
89 /**
90 * 群發消息
91 * @param message
92 * @throws IOException
93 */
94 public static void BroadCastInfo(String message) throws IOException {
95 for (Session session : SessionSet) {
96 if(session.isOpen()){
97 SendMessage(session, message);
98 }
99 }
100 }
101
102 /**
103 * 指定Session發送消息
104 * @param sessionId
105 * @param message
106 * @throws IOException
107 */
108 public static void SendMessage(String message,String sessionId) throws IOException {
109 Session session = null;
110 for (Session s : SessionSet) {
111 if(s.getId().equals(sessionId)){
112 session = s;
113 break;
114 }
115 }
116 if(session!=null){
117 SendMessage(session, message);
118 }
119 else{
120 log.warn("沒有找到你指定ID的會話:{}",sessionId);
121 }
122 }
123 }
View Code
4、調用WebSocket 發送消息
在訂單生成的程式碼後,根據需求調用WebSocket 處理類中的群發或者單獨發送消息的方法。
1 log.info("購物車生成訂單OK: {}", result);
2 // send webSocket message
3 WebSocketServer.BroadCastInfo("after service order");
三、VUE整合WebSocket
在前端使用WebSocket 時還需要判斷下,因為目前雖然大部分瀏覽器都支援WebSocket,比如Chrome、Mozilla、Opera 和Safari,但還有少部分是不支援的。
1、HTML中操作WebSocket
在html頁面中建立websocket 連接,至於什麼時候連接WebSocket,看個人需求吧。我是在登錄成功後,也就是在登錄頁面的Html中連接WebSocket 並監聽消息。
我把登錄HTML頁面中的部分程式碼刪除了,只留下WebSocket 相關操作的和播放音效相關的程式碼。


1 <template>
2 <div class="dashboard-container">
3 <!-- 在div 中引入 音頻資源-->
4 <audio ref="audio" src="@/assets/voice/tjtVoice.mp3"></audio>
5 </div>
6 </template>
7
8 <script>
9
10 export default {
11
12 created() {
13 this.playVoice()
14 },
15
16 methods: {
17 playVoice() {
18 var socket;
19 // 當找不到句柄對象即音頻資源的時候使用 that
20 let that = this
21 if (typeof (WebSocket) == "undefined") {
22 console.log("遺憾:您的瀏覽器不支援WebSocket");
23 } else {
24 console.log("恭喜:您的瀏覽器支援WebSocket");
25 //實現化WebSocket對象
26 //指定要連接的伺服器地址與埠建立連接
27 //注意ws、wss使用不同的埠。我使用自簽名的證書測試,
28 //無法使用wss,瀏覽器打開WebSocket時報錯
29 //ws對應http、wss對應https。
30 socket = new WebSocket("ws://localhost:11200/ws/path/asset");
31 //連接打開事件
32 socket.onopen = function() {
33 console.log("Socket 已打開");
34 // socket.send("消息發送測試(From Client)");
35
36 };
37 //收到消息事件
38 socket.onmessage = msg => {
39 // 當收到消息調用播放音頻資源API
40 console.log(msg.data);
41 this.$refs.audio.play()
42 };
43 //連接關閉事件
44 socket.onclose = function() {
45 console.log("Socket已關閉");
46 };
47 //發生了錯誤事件
48 socket.onerror = function() {
49 alert("Socket發生了錯誤");
50 }
51 //窗口關閉時,關閉連接
52 window.unload=function() {
53 // socket.close();
54 };
55 }
56 }
57 }
58
59 }
60 </script>
View Code
四、測試效果
1、啟動裝配有WebSocket 的SpringBoot 工程。
2、啟動VUE 工程,點擊登錄,連接WebSocket。
後端工程式控制制台列印
[2021-07-07 08:45:47] [INFO ] -- 有連接加入,當前連接數為:1
前端瀏覽器的WS 下分別顯示WebSocket 連接狀態:
3、模擬訂單生成,在後台調用訂單生成介面API 。
前端瀏覽器Console 列印,成功監聽並節接收到後端發送的WebSocket 訊息。
4、音頻試聽
接收到後端發送的WebSocket 訊息,即刻播放音效。
四、音頻資源
下載鏈接://pan.baidu.com/s/1rqa6Uift3RpWShPytgBLgQ
密碼: 8arh
長安如夢裡
何日是歸期