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 }
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 }
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>
四、測試效果
1、啟動裝配有WebSocket 的SpringBoot 工程。
2、啟動VUE 工程,點擊登錄,連接WebSocket。
后端工程控制台打印
[2021-07-07 08:45:47] [INFO ] -- 有連接加入,當前連接數為:1
前端瀏覽器的WS 下分別顯示WebSocket 連接狀態:

3、模擬訂單生成,在后台調用訂單生成接口API 。
前端瀏覽器Console 打印,成功監聽並節接收到后端發送的WebSocket 訊息。

4、音頻試聽
接收到后端發送的WebSocket 訊息,即刻播放音效。
四、音頻資源
下載鏈接:https://pan.baidu.com/s/1rqa6Uift3RpWShPytgBLgQ
密碼: 8arh
長安如夢里
何日是歸期

