­

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

 

 

 

 

    長安如夢裡

何日是歸期 

 

 

 

Tags: