SpringBoot+Vue整合WebSocket实现前后端消息推送
場景
WebSocket
HTTP 協(xié)議是一種無狀態(tài)的、無連接的、單向的應(yīng)用層協(xié)議。它采用了請求/響應(yīng)模型。通信請求只能由客戶端發(fā)起,服務(wù)端對請求做出應(yīng)答處理。
這種通信模型有一個弊端:HTTP 協(xié)議無法實現(xiàn)服務(wù)器主動向客戶端發(fā)起消息。
這種單向請求的特點,注定了如果服務(wù)器有連續(xù)的狀態(tài)變化,客戶端要獲知就非常麻煩。大多數(shù) Web 應(yīng)用程序?qū)⑼ㄟ^頻繁的異步 JavaScript 和 XML(AJAX)請求實現(xiàn)長輪詢。輪詢的效率低,非常浪費(fèi)資源(因為必須不停連接,或者 HTTP 連接始終打開)。
WebSocket 就是這樣發(fā)明的。WebSocket 連接允許客戶端和服務(wù)器之間進(jìn)行全雙工通信,以便任一方都可以通過建立的連接將數(shù)據(jù)推送到另一端。WebSocket 只需要建立一次連接,就可以一直保持連接狀態(tài)。這相比于輪詢方式的不停建立連接顯然效率要大大提高。
若依前后端分離版手把手教你本地搭建環(huán)境并運(yùn)行項目:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662
在上面將前后端的項目搭建起來之后。進(jìn)行后臺SpringBoot和Vue的WebSocket集成。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
關(guān)注公眾號
霸道的程序猿
獲取編程相關(guān)電子書、教程推送與免費(fèi)下載。
實現(xiàn)
SpringBoot集成
首先在pom文件中引入依賴
??????? <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>然后新建一個WebSocket的配置類,用來開啟WebSocket的支持
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){return? new ServerEndpointExporter();} }然后新建一個客戶端實體類WebSocketClient用來存儲連接的Session和Uri
import javax.websocket.Session;public class WebSocketClient {// 與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)private Session session;//連接的uriprivate String uri;public Session getSession() {return session;}public void setSession(Session session) {this.session = session;}public String getUri() {return uri;}public void setUri(String uri) {this.uri = uri;} }然后新建WebSocketService,用來創(chuàng)建和處理連接
import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap;@ServerEndpoint(value = "/websocket/{userName}") @Component public class WebSocketService {private static final Logger log = LoggerFactory.getLogger(WebSocketService.class);//靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計成線程安全的。private static int onlineCount = 0;//concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的WebSocketServer對象。private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>();/**與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)*/private Session session;/**接收userName*/private String userName="";/*** 連接建立成功調(diào)用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userName") String userName) {if(!webSocketMap.containsKey(userName)){addOnlineCount(); // 在線數(shù) +1}this.session = session;this.userName= userName;WebSocketClient client = new WebSocketClient();client.setSession(session);client.setUri(session.getRequestURI().toString());webSocketMap.put(userName, client);log.info("----------------------------------------------------------------------------");log.info("用戶連接:"+userName+",當(dāng)前在線人數(shù)為:" + getOnlineCount());try {sendMessage("來自后臺的反饋:連接成功");} catch (IOException e) {log.error("用戶:"+userName+",網(wǎng)絡(luò)異常!!!!!!");}}/*** 連接關(guān)閉調(diào)用的方法*/@OnClosepublic void onClose() {if(webSocketMap.containsKey(userName)){webSocketMap.remove(userName);if(webSocketMap.size()>0){//從set中刪除subOnlineCount();}}log.info("----------------------------------------------------------------------------");log.info(userName+"用戶退出,當(dāng)前在線人數(shù)為:" + getOnlineCount());}/*** 收到客戶端消息后調(diào)用的方法** @param message 客戶端發(fā)送過來的消息*/@OnMessagepublic void onMessage(String message, Session session) {log.info("收到用戶消息:"+userName+",報文:"+message);//可以群發(fā)消息//消息保存到數(shù)據(jù)庫、redisif(StringUtils.isNotBlank(message)){}}/**** @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("用戶錯誤:"+this.userName+",原因:"+error.getMessage());error.printStackTrace();}/*** 連接服務(wù)器成功后主動推送*/public void sendMessage(String message) throws IOException {synchronized (session){this.session.getBasicRemote().sendText(message);}}/*** 向指定客戶端發(fā)送消息* @param userName* @param message*/public static void sendMessage(String userName,String message){try {WebSocketClient webSocketClient = webSocketMap.get(userName);if(webSocketClient!=null){webSocketClient.getSession().getBasicRemote().sendText(message);}} catch (IOException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketService.onlineCount++;}public static synchronized void subOnlineCount() {WebSocketService.onlineCount--;}public static void setOnlineCount(int onlineCount) {WebSocketService.onlineCount = onlineCount;}public static ConcurrentHashMap<String, WebSocketClient> getWebSocketMap() {return webSocketMap;}public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketClient> webSocketMap) {WebSocketService.webSocketMap = webSocketMap;}public Session getSession() {return session;}public void setSession(Session session) {this.session = session;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}}注意這里的引用的WebSocketClient是上面自己新建的用來存儲連接相關(guān)信息的實體類
然后@ServerEndpoint(value = "/websocket/{userName}")
這里的就是WebSocket連接的地址,后面的{userName}是用來接收前端傳遞的參數(shù),用作不同標(biāo)識,進(jìn)而做不同的處理
然后下面的OnOpen注解的方法是連接建立成功后調(diào)用的方法,這里做了用戶在線數(shù)的計數(shù)處理,可以根據(jù)自己
的業(yè)務(wù)需要去處理。
然后OnClose注解的是連接關(guān)系調(diào)用的方法。
然后OnMessage注解的是客戶端發(fā)來消息時的回調(diào)方法,里面根據(jù)自己需要去處理數(shù)據(jù)
sendMessage方法在客戶端連接到服務(wù)器時使用當(dāng)前會話來給客戶端推送一個反饋消息
然后向指定客戶端發(fā)送消息使用的是sendMessage方法,傳遞的是用戶的唯一標(biāo)識和消息內(nèi)容
新建一個Controller接口用來測試消息的推送
import com.ruoyi.web.websocket.WebSocketService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/websocket") public class WebSocketController {@GetMapping("/pushone")public void pushone(){WebSocketService.sendMessage("badao","公眾號:霸道的程序猿");} }如果是普通的SpringBoot項目則后臺到此就可以了,然后因為是使用上面若依搭建的框架,所以需要將ws的url和接口的url放開權(quán)限認(rèn)證
?
Vue項目集成
新建一個WebSocket組件,這里放在componets目錄下
?
然后聲明一些變量
??????? data() {return {// ws是否啟動wsIsRun: false,// 定義ws對象webSocket: null,// ws請求鏈接(類似于ws后臺地址)ws: '',// ws定時器wsTimer: null,}},在mounted函數(shù)中執(zhí)行初始化websocket連接的方法
??????? async mounted() {this.wsIsRun = truethis.wsInit()},在執(zhí)行初始化的方法實現(xiàn)中
?????????? const wsuri = 'ws://你的后臺ip:7777/websocket/badao'this.ws = wsuriif (!this.wsIsRun) return// 銷毀wsthis.wsDestroy()// 初始化wsthis.webSocket = new WebSocket(this.ws)// ws連接建立時觸發(fā)this.webSocket.addEventListener('open', this.wsOpenHanler)// ws服務(wù)端給客戶端推送消息this.webSocket.addEventListener('message', this.wsMessageHanler)// ws通信發(fā)生錯誤時觸發(fā)this.webSocket.addEventListener('error', this.wsErrorHanler)// ws關(guān)閉時觸發(fā)this.webSocket.addEventListener('close', this.wsCloseHanler)// 檢查ws連接狀態(tài),readyState值為0表示尚未連接,1表示建立連接,2正在關(guān)閉連接,3已經(jīng)關(guān)閉或無法打開clearInterval(this.wsTimer)this.wsTimer = setInterval(() => {if (this.webSocket.readyState === 1) {clearInterval(this.wsTimer)} else {console.log('ws建立連接失敗')this.wsInit()}}, 3000)},進(jìn)行websocket連接并傳遞badao參數(shù),并且設(shè)置相應(yīng)的回調(diào)處理方法。
最后設(shè)置了一個3秒的定時器去定時檢查websocket的連接狀態(tài)。
此組件還添加了一個按鈕并設(shè)置其點擊事件用來給服務(wù)端推送消息
??????????? sendDataToServer() {if (this.webSocket.readyState === 1) {this.webSocket.send('來自前端的數(shù)據(jù)')} else {throw Error('服務(wù)未連接')}},完整的WebSocket組件代碼
<template><el-button @click="sendDataToServer" >給后臺發(fā)送消息</el-button> </template><script>export default {name: "WebSocket",data() {return {// ws是否啟動wsIsRun: false,// 定義ws對象webSocket: null,// ws請求鏈接(類似于ws后臺地址)ws: '',// ws定時器wsTimer: null,}},async mounted() {this.wsIsRun = truethis.wsInit()},methods: {sendDataToServer() {if (this.webSocket.readyState === 1) {this.webSocket.send('來自前端的數(shù)據(jù)')} else {throw Error('服務(wù)未連接')}},/*** 初始化ws*/wsInit() {const wsuri = 'ws://10.229.36.158:7777/websocket/badao'this.ws = wsuriif (!this.wsIsRun) return// 銷毀wsthis.wsDestroy()// 初始化wsthis.webSocket = new WebSocket(this.ws)// ws連接建立時觸發(fā)this.webSocket.addEventListener('open', this.wsOpenHanler)// ws服務(wù)端給客戶端推送消息this.webSocket.addEventListener('message', this.wsMessageHanler)// ws通信發(fā)生錯誤時觸發(fā)this.webSocket.addEventListener('error', this.wsErrorHanler)// ws關(guān)閉時觸發(fā)this.webSocket.addEventListener('close', this.wsCloseHanler)// 檢查ws連接狀態(tài),readyState值為0表示尚未連接,1表示建立連接,2正在關(guān)閉連接,3已經(jīng)關(guān)閉或無法打開clearInterval(this.wsTimer)this.wsTimer = setInterval(() => {if (this.webSocket.readyState === 1) {clearInterval(this.wsTimer)} else {console.log('ws建立連接失敗')this.wsInit()}}, 3000)},wsOpenHanler(event) {console.log('ws建立連接成功')},wsMessageHanler(e) {console.log('wsMessageHanler')console.log(e)//const redata = JSON.parse(e.data)//console.log(redata)},/*** ws通信發(fā)生錯誤*/wsErrorHanler(event) {console.log(event, '通信發(fā)生錯誤')this.wsInit()},/*** ws關(guān)閉*/wsCloseHanler(event) {console.log(event, 'ws關(guān)閉')this.wsInit()},/*** 銷毀ws*/wsDestroy() {if (this.webSocket !== null) {this.webSocket.removeEventListener('open', this.wsOpenHanler)this.webSocket.removeEventListener('message', this.wsMessageHanler)this.webSocket.removeEventListener('error', this.wsErrorHanler)this.webSocket.removeEventListener('close', this.wsCloseHanler)this.webSocket.close()this.webSocket = nullclearInterval(this.wsTimer)}},}} </script><style scoped></style>然后在首頁中引用該組件
<template><div class="app-container home"><el-row :gutter="20">websocket推送<WebSocket></WebSocket></el-row><el-divider /></div> </template> import WebSocket from '@/components/WebSocket/WebSocket' export default {name: "index",components: {WebSocket},效果
首先運(yùn)行后臺SpringBoot服務(wù),然后運(yùn)行前端項目并登錄到首頁,打開控制臺輸出
?
可以看到websocket建立連接成功,并且在后臺也有輸出
?
然后后臺給前端推送消息,調(diào)用后臺發(fā)送消息的接口
然后就可以在前端收到后臺的推送
?
然后點擊前端的給后臺發(fā)送消息按鈕
則在后臺的回調(diào)方法中收到發(fā)送的消息
?
總結(jié)
以上是生活随笔為你收集整理的SpringBoot+Vue整合WebSocket实现前后端消息推送的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 若依微服务版怎样在common-core
- 下一篇: 若依前后端分离版(vue)中配置页面跳转