Vue中前后端使用WebSocket詳細(xì)代碼實(shí)例
什么是websocket
WebSocket 是一種網(wǎng)絡(luò)通信協(xié)議。RFC6455定義了它的通信標(biāo)準(zhǔn)。
WebSocket是HTML5下一種新的協(xié)議(websocket協(xié)議本質(zhì)上是一個(gè)基于tcp的協(xié)議)
它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,能更好的節(jié)省服務(wù)器資源和帶寬并達(dá)到實(shí)時(shí)通訊的目的
http是一種無狀態(tài),無連接,單向的應(yīng)用層協(xié)議,它采用了請(qǐng)求/響應(yīng)模型,通信請(qǐng)求只能由客戶端發(fā)起,服務(wù)端對(duì)請(qǐng)求做出應(yīng)答處理。這樣的弊端顯然是很大的,只要服務(wù)端狀態(tài)連續(xù)變化,客戶端就必須實(shí)時(shí)響應(yīng),都是通過javascript與ajax進(jìn)行輪詢,這樣顯然是非常麻煩的,同時(shí)輪詢的效率低,非常的浪費(fèi)資源(http一直打開,一直重復(fù)的連接)。
于是就有了websocket,Websocket是一個(gè)持久化的協(xié)議,它是一種全面雙工通訊的網(wǎng)絡(luò)技術(shù),任意一方都可以建立連接將數(shù)據(jù)推向另一方,websocket只需要建立一次連接,就可以一直保持
websocket 原理
websocket約定了一個(gè)通信的規(guī)范,通過一個(gè)握手的機(jī)制,客戶端和服務(wù)器之間能建立一個(gè)類似tcp的連接,從而方便它們之間的通信
在websocket出現(xiàn)之前,web交互一般是基于http協(xié)議的短連接或者長(zhǎng)連接
websocket是一種全新的協(xié)議,不屬于http無狀態(tài)協(xié)議,協(xié)議名為"ws"
說它是TCP傳輸,主要體現(xiàn)在建立長(zhǎng)連接后,瀏覽器是可以給服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器也可以給瀏覽器發(fā)送請(qǐng)求的。當(dāng)然它的數(shù)據(jù)格式并不是自己定義的,是在要傳輸?shù)臄?shù)據(jù)外層有ws協(xié)議規(guī)定的外層包的。
websocket與http的關(guān)系
相同點(diǎn):
都是基于tcp的,都是可靠性傳輸協(xié)議,都是應(yīng)用層協(xié)議
不同點(diǎn):
WebSocket是雙向通信協(xié)議,模擬Socket協(xié)議,可以雙向發(fā)送或接受信息
HTTP是單向的
WebSocket是需要瀏覽器和服務(wù)器握手進(jìn)行建立連接的
而http是瀏覽器發(fā)起向服務(wù)器的連接,服務(wù)器預(yù)先并不知道這個(gè)連接
聯(lián)系
WebSocket在建立握手時(shí),數(shù)據(jù)是通過HTTP傳輸?shù)?。但是建立之后,在真正傳輸時(shí)候是不需要HTTP協(xié)議的
關(guān)系圖


實(shí)際開發(fā)
我們有一個(gè)需求就是報(bào)警數(shù)據(jù)來了之后在前端報(bào)警處理,不使用websocket就只能通過輪詢每3秒調(diào)用一次接口,在吧查回來的數(shù)據(jù)進(jìn)行判斷,如果多了就開始調(diào)接口繼續(xù)操作,這樣很浪費(fèi)資源。
使用websocket之后,建立連接之后。后端察覺數(shù)據(jù)變化之后,通過他的send方法通知前端,前端通過onmessage提示調(diào)用,可以在里面直接調(diào)用查詢數(shù)據(jù)接口繼續(xù)操作。
我們這里是建立連接通過他數(shù)據(jù)變化后端通知前端,前端有個(gè)方法會(huì)執(zhí)行,我們?cè)谶@個(gè)方法里面調(diào)用我們查詢接口,也可以是后端把這條數(shù)據(jù)發(fā)回來我們處理,根據(jù)實(shí)際情況而定。
后端代碼
我們后端是做了容器化的分布式的,主要代碼如下
public class WebSocketConfig {
/**
* 如果使用Springboot默認(rèn)內(nèi)置的tomcat容器,則必須注入ServerEndpoint的bean;
* 如果使用外置的web容器,則不需要提供ServerEndpointExporter,下面的注入可以注解掉
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
public class WebSocketServer {
?
//與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
private Session session;
?
private static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();
//用來存放每個(gè)客戶端對(duì)應(yīng)的WebSocket對(duì)象。
private static Map<String,Session> sessionPool = new HashMap<>();
?
/**
* 連接成功后調(diào)用的方法
* @param session
* @param key
*/
@OnOpen
public void onOpen(Session session, @PathParam("key") String key) throws IOException {
//key為前端傳給后端的token
this.session = session;
//從token中獲取到userid當(dāng)做區(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());
}
?
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
webSockets.remove(this);
System.out.println("webSocket連接關(guān)閉");
}
?
/**
* 收到客戶端消息后調(diào)用的方法,根據(jù)業(yè)務(wù)要求進(jìn)行處理,這里就簡(jiǎn)單地將收到的消息直接群發(fā)推送出去
* @param message 客戶端發(fā)送過來的消息
*/
@OnMessage
public void onMessage(String message) {
//心跳檢測(cè)
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ā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("發(fā)生錯(cuò)誤");
error.printStackTrace();
}
?
/**
* 實(shí)現(xiàn)服務(wù)器主動(dòng)推送消息
*/
//單點(diǎ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 引入文件,這個(gè)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 //避免重復(fù)連接
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)自己會(huì)執(zhí)行
// websocket 成功 失敗 錯(cuò)誤 斷開 這里會(huì)自動(dòng)執(zhí)行
// 這個(gè)方法后端通過send調(diào)用 這個(gè)方法會(huì)執(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('連接已關(guān)閉')
Message({
message: '報(bào)警功能連接已關(guān)閉',
type: 'error'
})
reconnect(clientId)
}
ws.onopen = function () {
// console.log('連接成功')
Message({
message: '報(bào)警功能連接成功',
type: 'success'
})
heartCheck.start()
}
?
ws.onerror = function (e) {
// console.log('數(shù)據(jù)傳輸發(fā)生錯(cuò)誤')
Message({
message: '數(shù)據(jù)傳輸發(fā)生錯(cuò)誤',
type: 'error'
})
reconnect(clientId)
}
},
// 我們單獨(dú)寫了一個(gè)方法 調(diào)用ws的關(guān)閉方法,這樣就可以在退出登錄的時(shí)候主動(dòng)關(guān)閉連接
//關(guān)閉連接
onClose: function () {
console.log('主動(dòng)關(guān)閉連接!')
//關(guān)閉websocket連接和關(guān)閉斷開重連機(jī)制
lockReconnect = true
// 調(diào)用 上面的websocket關(guān)閉方法
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對(duì)象
getWebSocket () {
return ws
},
// websocket 自帶的狀態(tài)碼意思提示
getStatus () {
if (ws.readyState == 0) {
return '未連接'
} else if (ws.readyState == 1) {
return '已連接'
} else if (ws.readyState == 2) {
return '連接正在關(guān)閉'
} else if (ws.readyState == 3) {
return '連接已關(guān)閉'
}
}
}
?
// 刷新頁面后需要重連
if (window.performance.navigation.type == 1 && getToken() != null) {
//刷新后重連
// reconnect(clientId);
websocket.Init(clientId)
//如果websocket沒連接成功,則開始延遲連接
if (ws == null) {
reconnect(clientId)
}
}
?
export default websocket
?
//根據(jù)消息標(biāo)識(shí)做不同的處理
function messageHandle (message) {
let msg = JSON.parse(message)
switch (msg.flag) {
case 'command':
// console.log('指令消息類型')
break
case 'inform':
// console.log('通知')
break
default:
// console.log('未知消息類型')
}
}
// 重連方法 刷新頁面 連接錯(cuò)誤 連接關(guān)閉時(shí)調(diào)用
function reconnect (sname) {
if (lockReconnect) {
return
}
lockReconnect = true
//沒連接上會(huì)一直重連,設(shè)置延遲避免請(qǐng)求過多
tt && clearTimeout(tt)
tt = setTimeout(function () {
// console.log('執(zhí)行斷線重連...')
websocket.Init(sname)
lockReconnect = false
}, 4000)
}
?
//心跳檢測(cè) 跟后端是對(duì)應(yīng)的 會(huì)進(jìn)行處理
// 連接成功 和后端推消息時(shí)調(diào)用
var heartCheck = {
timeout: 1000 * 60 * 3,
timeoutObj: null,
serverTimeoutObj: null,
start: function () {
// console.log('開始心跳檢測(cè)')
var self = this
this.timeoutObj && clearTimeout(this.timeoutObj)
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj)
this.timeoutObj = setTimeout(function () {
//這里發(fā)送一個(gè)心跳,后端收到后,返回一個(gè)心跳消息,
//onmessage拿到返回的心跳就說明連接正常
// console.log('心跳檢測(cè)...')
ws.send('HeartBeat:' + clientId)
self.serverTimeoutObj = setTimeout(function () {
if (ws.readyState != 1) {
ws.close()
}
// createWebSocket();
}, self.timeout)
}, this.timeout)
}
}
?2.在登錄和退出的時(shí)候進(jìn)行websocket進(jìn)行建立連接和關(guān)閉連接,就是在vuex(src/store/modules/user.js)調(diào)用這里方法(詳細(xì)的Vuex做登錄主頁文章會(huì)有)。
// 引入外部文件
import websocket from '@/utils/websocket'
// 請(qǐng)求
const actions = {
// 登錄
async login (ctx, data) {
// 調(diào)用mutations里的方法存到state
// 登錄成功后創(chuàng)建websocket連接
// res.data 是token
websocket.Init(res.data)
ctx.commit('SET_TOKEN', res.data)
},
// 退出登錄
logout (ctx) {
// 主動(dòng)關(guān)閉連接
websocket.onClose()
Message.success('退出成功,請(qǐng)重新登錄')
}
}3.在需要用到websocket使用,我們這里是在首頁使用。
// 引入websocket文件
import websocket from '@/utils/websocket'
?
// 登錄成功一進(jìn)到頁面的時(shí)候調(diào)用
created() {
this.getWebSocket()
},
?
// getWebSocket()方法
method: {
// websocket 接受消息
getWebSocket() {
// websocket.getWebSocket()這個(gè)是websocket里面方法返回ws對(duì)象(websocket.js)
let ws = websocket.getWebSocket()
// 通過ws這個(gè)對(duì)象獲取這個(gè)websocket里面后端推消息前端執(zhí)行的方法onmessage。
// 給他賦給我們自己方法 this.websocketonmessage
websocketonmessage(e)就會(huì)執(zhí)行
ws.onmessage = this.websocketonmessage
},
//接收到消息的回調(diào)函數(shù)
websocketonmessage(e) {
// e后端傳回來參數(shù)
// console.log(e.data);
// 防止心跳監(jiān)測(cè),返回來的ok 對(duì)方法執(zhí)行的影響(心跳監(jiān)測(cè)方法也會(huì)執(zhí)行一次)
if (e.data == 'ok') {
//心跳消息不做處理
return
}
// 需要監(jiān)測(cè)的接口 我們查詢數(shù)據(jù)的接口 在進(jìn)行處理
this.alarmerlist()
},
}
?細(xì)節(jié):這樣登錄創(chuàng)建連接之后,后端察覺到數(shù)據(jù)變化,就通過他的send方法給我們推消息我們前端websocket.js文件的onmessage這個(gè)方法會(huì)自己調(diào)用執(zhí)行并會(huì)接受參數(shù)。我們?cè)谛枰捻撁嬉雡ebsocket使用。把它賦值我們自己寫的方法,這樣數(shù)據(jù)一變化我們就會(huì)調(diào)用一次查詢接口,進(jìn)行處理,大大節(jié)約性能(http要用輪詢一直調(diào)用查詢接口)。
總結(jié):
經(jīng)過這一趟流程下來相信你也對(duì) Vue 中前后端使用WebSocket 有了初步的深刻印象,但在實(shí)際開發(fā)中我 們遇到的情況肯定是不一樣的,所以我們要理解它的原理,萬變不離其宗。加油,打工人!
到此這篇關(guān)于Vue中前后端使用WebSocket的文章就介紹到這了,更多相關(guān)Vue前后端使用WebSocket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue?element修改el-select控件長(zhǎng)度style=“width:XXpx“不生效的解決
這篇文章主要介紹了vue?element修改el-select控件長(zhǎng)度style=“width:XXpx“不生效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
一篇文章帶你吃透Vue生命周期(結(jié)合案例通俗易懂)
這篇文章主要給大家介紹了關(guān)于如何通過一篇文章帶你吃透Vue生命周期,文章通過結(jié)合案例更加的通俗易懂,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02
vue實(shí)現(xiàn)修改標(biāo)簽中的內(nèi)容:id class style
這篇文章主要介紹了vue實(shí)現(xiàn)修改標(biāo)簽中的內(nèi)容:id class style,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
Vue3使用element-plus實(shí)現(xiàn)彈窗效果
本文主要介紹了Vue3使用element-plus實(shí)現(xiàn)彈窗效果,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
VUE微信H5生成二維碼海報(bào)保存在本地相冊(cè)的實(shí)現(xiàn)
本文主要介紹了VUE微信H5生成二維碼海報(bào)保存在本地相冊(cè)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

