Vue中前后端使用WebSocket詳細代碼實例
什么是websocket
WebSocket 是一種網(wǎng)絡通信協(xié)議。RFC6455定義了它的通信標準。
WebSocket是HTML5下一種新的協(xié)議(websocket協(xié)議本質上是一個基于tcp的協(xié)議)
它實現(xiàn)了瀏覽器與服務器全雙工通信,能更好的節(jié)省服務器資源和帶寬并達到實時通訊的目的
http是一種無狀態(tài),無連接,單向的應用層協(xié)議,它采用了請求/響應模型,通信請求只能由客戶端發(fā)起,服務端對請求做出應答處理。這樣的弊端顯然是很大的,只要服務端狀態(tài)連續(xù)變化,客戶端就必須實時響應,都是通過javascript與ajax進行輪詢,這樣顯然是非常麻煩的,同時輪詢的效率低,非常的浪費資源(http一直打開,一直重復的連接)。
于是就有了websocket,Websocket是一個持久化的協(xié)議,它是一種全面雙工通訊的網(wǎng)絡技術,任意一方都可以建立連接將數(shù)據(jù)推向另一方,websocket只需要建立一次連接,就可以一直保持
websocket 原理
websocket約定了一個通信的規(guī)范,通過一個握手的機制,客戶端和服務器之間能建立一個類似tcp的連接,從而方便它們之間的通信
在websocket出現(xiàn)之前,web交互一般是基于http協(xié)議的短連接或者長連接
websocket是一種全新的協(xié)議,不屬于http無狀態(tài)協(xié)議,協(xié)議名為"ws"
說它是TCP傳輸,主要體現(xiàn)在建立長連接后,瀏覽器是可以給服務器發(fā)送數(shù)據(jù),服務器也可以給瀏覽器發(fā)送請求的。當然它的數(shù)據(jù)格式并不是自己定義的,是在要傳輸?shù)臄?shù)據(jù)外層有ws協(xié)議規(guī)定的外層包的。
websocket與http的關系
相同點:
都是基于tcp的,都是可靠性傳輸協(xié)議,都是應用層協(xié)議
不同點:
WebSocket是雙向通信協(xié)議,模擬Socket協(xié)議,可以雙向發(fā)送或接受信息
HTTP是單向的
WebSocket是需要瀏覽器和服務器握手進行建立連接的
而http是瀏覽器發(fā)起向服務器的連接,服務器預先并不知道這個連接
聯(lián)系
WebSocket在建立握手時,數(shù)據(jù)是通過HTTP傳輸?shù)?。但是建立之后,在真正傳輸時候是不需要HTTP協(xié)議的
關系圖
實際開發(fā)
我們有一個需求就是報警數(shù)據(jù)來了之后在前端報警處理,不使用websocket就只能通過輪詢每3秒調用一次接口,在吧查回來的數(shù)據(jù)進行判斷,如果多了就開始調接口繼續(xù)操作,這樣很浪費資源。
使用websocket之后,建立連接之后。后端察覺數(shù)據(jù)變化之后,通過他的send方法通知前端,前端通過onmessage提示調用,可以在里面直接調用查詢數(shù)據(jù)接口繼續(xù)操作。
我們這里是建立連接通過他數(shù)據(jù)變化后端通知前端,前端有個方法會執(zhí)行,我們在這個方法里面調用我們查詢接口,也可以是后端把這條數(shù)據(jù)發(fā)回來我們處理,根據(jù)實際情況而定。
后端代碼
我們后端是做了容器化的分布式的,主要代碼如下
public class WebSocketConfig { /** * 如果使用Springboot默認內置的tomcat容器,則必須注入ServerEndpoint的bean; * 如果使用外置的web容器,則不需要提供ServerEndpointExporter,下面的注入可以注解掉 */ @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } } public class WebSocketServer { ? //與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù) private Session session; ? private static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>(); //用來存放每個客戶端對應的WebSocket對象。 private static Map<String,Session> sessionPool = new HashMap<>(); ? /** * 連接成功后調用的方法 * @param session * @param key */ @OnOpen public void onOpen(Session session, @PathParam("key") String key) throws IOException { //key為前端傳給后端的token this.session = session; //從token中獲取到userid當做區(qū)分websocket客戶端的key Long userId = JwtHelper.getUserId(key); sessionPool.put(String.valueOf(userId),session); if (sessionPool.get(String.valueOf(userId))==null){ webSockets.add(this); } webSockets.add(this); System.out.println("webSocket連接成功"); System.out.println("WebSocket有新的連接,連接總數(shù)為:"+webSockets.size()); } ? /** * 連接關閉調用的方法 */ @OnClose public void onClose() { webSockets.remove(this); System.out.println("webSocket連接關閉"); } ? /** * 收到客戶端消息后調用的方法,根據(jù)業(yè)務要求進行處理,這里就簡單地將收到的消息直接群發(fā)推送出去 * @param message 客戶端發(fā)送過來的消息 */ @OnMessage public void onMessage(String message) { //心跳檢測 int beginIndex = 10; if ("HeartBeat".equals(message.substring(0,9))){ String token = message.substring(beginIndex); System.out.println("token1"+token); if (!"".equals(token)){ System.out.println("token2"+token); Long userId = JwtHelper.getUserId(token); sendTextMessage(String.valueOf(userId),"ok"); } } System.out.println("WebSocket收到客戶端消息:"+message); } /** * 發(fā)生錯誤時的回調函數(shù) * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發(fā)生錯誤"); error.printStackTrace(); } ? /** * 實現(xiàn)服務器主動推送消息 */ //單點消息發(fā)送 public void sendTextMessage(String key,String message){ Session session = sessionPool.get(key); if (session!=null){ try { session.getBasicRemote().sendText(message); }catch (Exception e){ e.printStackTrace(); } } } ? }
?前端代碼
1.在src/utils 建立websocket.js 引入文件,這個websocket是全局的,通過登錄,和退出控制,在哪兒頁面都可以使用。
// 提示信息 import { Message } from 'element-ui' // 引入token 解析用戶id在后端處理 import { getToken } from '@/utils/auth' ? var url = 'ws://后端地址/equipment/websocket/' ? var ws var tt var lockReconnect = false //避免重復連接 var clientId = getToken() //cookies中獲取token值 ? var websocket = { // 建立連接 Init: function (clientId) { if ('WebSocket' in window) { ws = new WebSocket(url + clientId) } else if ('MozWebSocket' in window) { ws = new MozWebSocket(url + clientId) } else { // console.log('您的瀏覽器不支持 WebSocket!') return } // websocket 生命周期根據(jù)websocket狀態(tài)自己會執(zhí)行 // websocket 成功 失敗 錯誤 斷開 這里會自動執(zhí)行 // 這個方法后端通過send調用 這個方法會執(zhí)行和接收參數(shù) ws.onmessage = function (e) { // console.log('接收消息:' + e.data) heartCheck.start() if (e.data == 'ok') { //心跳消息不做處理 return } Message({ message: e.data, type: 'success' }) //messageHandle(e.data) } ws.onclose = function () { console.log('連接已關閉') Message({ message: '報警功能連接已關閉', type: 'error' }) reconnect(clientId) } ws.onopen = function () { // console.log('連接成功') Message({ message: '報警功能連接成功', type: 'success' }) heartCheck.start() } ? ws.onerror = function (e) { // console.log('數(shù)據(jù)傳輸發(fā)生錯誤') Message({ message: '數(shù)據(jù)傳輸發(fā)生錯誤', type: 'error' }) reconnect(clientId) } }, // 我們單獨寫了一個方法 調用ws的關閉方法,這樣就可以在退出登錄的時候主動關閉連接 //關閉連接 onClose: function () { console.log('主動關閉連接!') //關閉websocket連接和關閉斷開重連機制 lockReconnect = true // 調用 上面的websocket關閉方法 ws.close() }, // 前端的send給后端發(fā)信息 Send: function (sender, reception, body, flag) { let data = { sender: sender, reception: reception, body: body, flag: flag } let msg = JSON.stringify(data) // console.log('發(fā)送消息:' + msg) ws.send(msg) }, // 返回ws對象 getWebSocket () { return ws }, // websocket 自帶的狀態(tài)碼意思提示 getStatus () { if (ws.readyState == 0) { return '未連接' } else if (ws.readyState == 1) { return '已連接' } else if (ws.readyState == 2) { return '連接正在關閉' } else if (ws.readyState == 3) { return '連接已關閉' } } } ? // 刷新頁面后需要重連 if (window.performance.navigation.type == 1 && getToken() != null) { //刷新后重連 // reconnect(clientId); websocket.Init(clientId) //如果websocket沒連接成功,則開始延遲連接 if (ws == null) { reconnect(clientId) } } ? export default websocket ? //根據(jù)消息標識做不同的處理 function messageHandle (message) { let msg = JSON.parse(message) switch (msg.flag) { case 'command': // console.log('指令消息類型') break case 'inform': // console.log('通知') break default: // console.log('未知消息類型') } } // 重連方法 刷新頁面 連接錯誤 連接關閉時調用 function reconnect (sname) { if (lockReconnect) { return } lockReconnect = true //沒連接上會一直重連,設置延遲避免請求過多 tt && clearTimeout(tt) tt = setTimeout(function () { // console.log('執(zhí)行斷線重連...') websocket.Init(sname) lockReconnect = false }, 4000) } ? //心跳檢測 跟后端是對應的 會進行處理 // 連接成功 和后端推消息時調用 var heartCheck = { timeout: 1000 * 60 * 3, timeoutObj: null, serverTimeoutObj: null, start: function () { // console.log('開始心跳檢測') var self = this this.timeoutObj && clearTimeout(this.timeoutObj) this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj) this.timeoutObj = setTimeout(function () { //這里發(fā)送一個心跳,后端收到后,返回一個心跳消息, //onmessage拿到返回的心跳就說明連接正常 // console.log('心跳檢測...') ws.send('HeartBeat:' + clientId) self.serverTimeoutObj = setTimeout(function () { if (ws.readyState != 1) { ws.close() } // createWebSocket(); }, self.timeout) }, this.timeout) } } ?
2.在登錄和退出的時候進行websocket進行建立連接和關閉連接,就是在vuex(src/store/modules/user.js)調用這里方法(詳細的Vuex做登錄主頁文章會有)。
// 引入外部文件 import websocket from '@/utils/websocket' // 請求 const actions = { // 登錄 async login (ctx, data) { // 調用mutations里的方法存到state // 登錄成功后創(chuàng)建websocket連接 // res.data 是token websocket.Init(res.data) ctx.commit('SET_TOKEN', res.data) }, // 退出登錄 logout (ctx) { // 主動關閉連接 websocket.onClose() Message.success('退出成功,請重新登錄') } }
3.在需要用到websocket使用,我們這里是在首頁使用。
// 引入websocket文件 import websocket from '@/utils/websocket' ? // 登錄成功一進到頁面的時候調用 created() { this.getWebSocket() }, ? // getWebSocket()方法 method: { // websocket 接受消息 getWebSocket() { // websocket.getWebSocket()這個是websocket里面方法返回ws對象(websocket.js) let ws = websocket.getWebSocket() // 通過ws這個對象獲取這個websocket里面后端推消息前端執(zhí)行的方法onmessage。 // 給他賦給我們自己方法 this.websocketonmessage websocketonmessage(e)就會執(zhí)行 ws.onmessage = this.websocketonmessage }, //接收到消息的回調函數(shù) websocketonmessage(e) { // e后端傳回來參數(shù) // console.log(e.data); // 防止心跳監(jiān)測,返回來的ok 對方法執(zhí)行的影響(心跳監(jiān)測方法也會執(zhí)行一次) if (e.data == 'ok') { //心跳消息不做處理 return } // 需要監(jiān)測的接口 我們查詢數(shù)據(jù)的接口 在進行處理 this.alarmerlist() }, } ?
細節(jié):這樣登錄創(chuàng)建連接之后,后端察覺到數(shù)據(jù)變化,就通過他的send方法給我們推消息我們前端websocket.js文件的onmessage這個方法會自己調用執(zhí)行并會接受參數(shù)。我們在需要的頁面引入websocket使用。把它賦值我們自己寫的方法,這樣數(shù)據(jù)一變化我們就會調用一次查詢接口,進行處理,大大節(jié)約性能(http要用輪詢一直調用查詢接口)。
總結:
經(jīng)過這一趟流程下來相信你也對 Vue 中前后端使用WebSocket 有了初步的深刻印象,但在實際開發(fā)中我 們遇到的情況肯定是不一樣的,所以我們要理解它的原理,萬變不離其宗。加油,打工人!
到此這篇關于Vue中前后端使用WebSocket的文章就介紹到這了,更多相關Vue前后端使用WebSocket內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
vue?element修改el-select控件長度style=“width:XXpx“不生效的解決
這篇文章主要介紹了vue?element修改el-select控件長度style=“width:XXpx“不生效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07vue實現(xiàn)修改標簽中的內容:id class style
這篇文章主要介紹了vue實現(xiàn)修改標簽中的內容:id class style,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07Vue3使用element-plus實現(xiàn)彈窗效果
本文主要介紹了Vue3使用element-plus實現(xiàn)彈窗效果,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07VUE微信H5生成二維碼海報保存在本地相冊的實現(xiàn)
本文主要介紹了VUE微信H5生成二維碼海報保存在本地相冊的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06