最近一个后台管理项目中需要使用websocket跟服务器建立长连接,以便实时通信,一番百度查阅后,决定使用Stomp+websocket形式。网上简单的链接demo倒是很多,能真正发布到生产环境使用的却很少,尤其断线重连机制,一直没找到理想参考,于是决定自己写了,特此记录,以备查阅。本示例是全局使用的websocket,初始化成功后,可在任意页面或组件中调用,由于使用了stomp,可以基于主题订阅模式跟服务器端交互,使用起来非常方便。
在store文件夹下新建:websocket.js
import Vue from 'vue'
import Vuex from 'vuex'
import SockJS from "sockjs-client";
import Stomp from "stompjs";
Vue.use(Vuex)
export default new Vuex.Store({
state: {
url:'',
checkInterval:null,//断线重连时 检测连接是否建立成功
websocket:null,
stompClient:null,
listenerList:[],//监听器列表,断线重连时 用于重新注册监听
},
getters: {
stompClient(state) {
return function () {
return state.stompClient;
}
}
},
mutations: {
WEBSOCKET_INIT(state,url) {
if(state.stompClient==null||!state.stompClient.connected) {
state.url = url
if(state.stompClient!=null&&state.websocket.readyState=== SockJS.OPEN){
state.stompClient.disconnect(()=>{
this.commit('WEBSOCKET_CONNECT')
})
}else if(state.stompClient!=null&&state.websocket.readyState=== SockJS.CONNECTING){
console.log("连接正在建立")
return;
}else{
this.commit('WEBSOCKET_CONNECT')
}
if(!state.checkInterval){
const interval=setInterval(()=>{
console.log("检测连接:"+state.websocket.readyState)
if(state.stompClient!=null&&state.stompClient.connected){
clearInterval(state.checkInterval)
state.checkInterval=null
console.log('重连成功')
}else if(state.stompClient!=null&&state.websocket.readyState!= SockJS.CONNECTING){
//经常会遇到websocket的状态为open 但是stompClient的状态却是未连接状态,故此处需要把连接断掉 然后重连
state.stompClient.disconnect(()=>{
this.commit('WEBSOCKET_CONNECT')
})
}
},2000)
state.checkInterval=interval
}
}else{
console.log("连接已建立成功,不再执行")
}
},
WEBSOCKET_CONNECT(state){
const _this = this
const websock = new SockJS(state.url);
state.websocket=websock
// 获取STOMP子协议的客户端对象
const stompClient = Stomp.over(websock);
stompClient.debug = null //关闭控制台打印
stompClient.heartbeat.outgoing = 20000;
stompClient.heartbeat.incoming = 0;//客户端不从服务端接收心跳包
// 向服务器发起websocket连接
stompClient.connect(
{name: 'test'}, //此处注意更换自己的用户名,最好以参数形式带入
frame => {
console.log('链接成功!')
state.listenerList.forEach(item=>{
state.stompClient.subscribe(item.topic,item.callback)
})
//unsubscribe()可以用来取消客户端对这个目的地destination的订阅
// stompClient.subscribe("/user/queue/message", msg => {
// // this.getData(msg);
// console.log(msg)
// });
},
err => {//第一次连接失败和连接后断开连接都会调用这个函数 此处调用重连
setTimeout(() => {
_this.commit('WEBSOCKET_INIT', state.url)
}, 1000)
}
);
state.stompClient = stompClient
},
WEBSOCKET_SEND(state, p) {
state.stompClient.send(p.topic,{},p.data);
},
WEBSOCKET_UNSUBSRCIBE(state,p){
state.stompClient.unsubscribe(p)
for(let i=0;i<state.listenerList.length;i++){
if(state.listenerList[i].topic==p){
state.listenerList.splice(i,1)
console.log("解除订阅:"+p+" size:"+state.listenerList.length)
break;
}
}
}
},
actions: {
WEBSOCKET_INIT({commit},url) {
commit('WEBSOCKET_INIT',url)
},
WEBSOCKET_SEND({commit}, p) {
commit('WEBSOCKET_SEND', p)
},
WEBSOCKET_UNSUBSRCIBE({commit}, p){
commit('WEBSOCKET_UNSUBSRCIBE', p)
}
}
})
main.js添加引用
//socket
import websocket from './store/websocketStore'
Vue.prototype.$websocket = websocket;
接下来就是在任意需要的地方初始化链接,我是在用户登录成功,进入主界面后
created(){
this.$websocket.dispatch('WEBSOCKET_INIT',"http://192.168.0.103:8084/websocket")
},
链接初始化完成以后,就可以在任意界面或组件中注册监听主题或发送消息了,为了防止用户刷新的当前页面就需要注册监听,所以我用了个方法轮询,确认链接可用后再注册
const _this=this
const stompClient=this.$websocket.getters.stompClient()
const interval=setInterval(()=>{
if(stompClient!=null&&stompClient.connected){
clearInterval(interval)
_this.subscriptions.push(stompClient.subscribe("/user/queue/cmdListener",msg=>{
const result=JSON.parse(msg.body)
if(result.status) {
_this.$notify({
title: '成功',
message: '端口开放成功,请给设备上电',
type: 'success',
duration: 2000
})
}
}))
_this.subscriptions.push(stompClient.subscribe("/user/queue/registerDevice",msg=>{
_this.messageList.push(JSON.parse(msg.body))
console.log(msg.body)
}))
stompClient.send("/topic/cmd/registerDevice",{},"{cmd:\"openConnection\"}");
}else{
console.log("等待连接成功")
}
},500)