vue websockt 实现站内消息的发送和接收
1. 什么是 WebSocket
websocket是HTML5开始提供的一种网络通信协议,它的目的是在浏览器之间建立一个不受限的双方通信的通道,比如说,服务器可以在任意时刻发送信息给浏览器。在websocket的API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
2. WebSocket 的方法
- ws.send() - 向服务器发送数据
- ws.close() - 关闭连接
3. WebSocket 的事件
- ws.onopen - 建立连接时触发
- ws.onmessage - 客户端接受服务端数据时触发
- ws.onerror - 通信错误时触发
- ws.onclose - 连接关闭时触发
4. WebSocket.readyState
- readyState 属性返回实例对象的当前状态,共有四种状态
- 0 - 表示正在连接
- 1 - 表示连接成功,可以进行通信
- 2 - 表示连接正在关闭
- 3 - 表示连接已经关闭,或者打开连接失败
5. WebSocket 的基本流程
- 检查浏览器是否支持websocket,即判断'WebSocket' in window和'MozWebSocket' in window值是否为true,true就是浏览器支持websocket,这一步骤可以发生在mounted当中。
- 客户端与服务器建立连接。new WebSocket(url)即创建新的websocket实例,url为服务端提供,用来和服务器建立websocket连接。注意这个连接的过程中使用的协议不是http,https,而是ws协议。new WebSocket(url).onopen = () => {}是用来打开连接。这一步骤也可以发生在mounted当中。
- 判断连接状态,确保连接成功。检查new WebSocket(url).readyState的值,0-CONNECTING-正在连接中,暂时不可以通信;1-OPEN-连接成功,可以发送和接收消息;2-CLOSING-正在关闭连接,不可以发送接收消息了;3-CLOSED-打开连接失败或者连接已关闭。这一步骤也可以发生在mounted当中。
- 向服务器发送消息。new WebSocket(url).send(yourMsg),yourMsg为客户端自定义的消息,可以发生在事件监听中。
- 接收服务器返回的消息。new WebSocket(url).onmessage = (data) => {},data为服务器返回内容对象,其中data(data.data)为返回的文本内容。这一步骤可以在mounted中定义,不需要手动执行。
- 断连检查,启动重连。再执行一次new WebSocket(url).onopen = () => {},发生在readyState为2或3时或者其他需要重连的时候。
- 关闭websocket连接。new WebSocket(url).onclose = () => {},监听连接关闭。离开页面、组件注销等情况下需要关闭websocket的链接。new WebSocket(url).close()执行关闭的动作。
6.WebSocket和心跳机制
增加心跳检测(防止断开):
怎么使用:每隔一段时间向服务器发送消息,在message事件里面收到消息就重置心跳。
使用心跳检测原因:由于网络以及websocket自身的一些不稳定性,页面长时间打开的情况下有时会发生websocket链接的断开,为了防止这种情况,我们增加心跳检测机制
实现心跳检测的思路是:每隔一段固定的时间,向服务器端发送一个ping数据,如果在正常的情况下,服务器会返回一个pong给客户端,如果客户端通过
onmessage事件能监听到的话,说明请求正常,这里我们使用了一个定时器,每隔3秒的情况下,如果是网络断开的情况下,在指定的时间内服务器端并没有返回心跳响应消息,因此服务器端断开了,因此这个时候我们使用ws.close关闭连接,在一段时间后(在不同的浏览器下,时间是不一样的,firefox响应更快),
可以通过 onclose事件监听到。因此在onclose事件内,我们可以调用 reconnect事件进行重连操作。
7.代码实现
1)utils里新建websoket.js
1 import Vue from 'vue' 2 import { Message } from 'element-ui' 3 let v = new Vue() 4 v.$message = Message; 5 var webSocket = null; 6 var isConnect = false; //连接状态 7 var globalCallback = function(e){ console.log(e) };//定义外部接收数据的回调函数 8 var reConnectNum = 0;//重连次数 9 let userId = sessionStorage.getItem("userId") 10 var websocketUrl = `ws://192.168.220.111:9080/websocket/${userId}`; 11 12 //心跳设置 13 var heartCheck = { 14 heart:"heart",//心跳包 15 timeout: 45 * 1000, //每段时间发送一次心跳包 这里设置为60s 16 heartbeat: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) 17 start: function () { 18 this.heartbeat = setInterval(()=>{ 19 if (isConnect){ 20 webSocketSend(this.heart); 21 }else{ 22 this.clear(); 23 } 24 }, this.timeout); 25 }, 26 reset: function () { 27 clearInterval(this.heartbeat); 28 this.start(); 29 }, 30 clear:function(){ 31 clearInterval(this.heartbeat); 32 } 33 } 34 35 //初始化websocket 36 function initWebSocket(callback) { 37 //此callback为在其他地方调用时定义的接收socket数据的函数 38 if(callback){ 39 if(typeof callback == 'function'){ 40 globalCallback = callback 41 }else{ 42 throw new Error("callback is not a function") 43 } 44 } 45 if ("WebSocket" in window) { 46 webSocket = new WebSocket(websocketUrl);//创建socket对象 47 } else { 48 Message({ 49 message: '该浏览器不支持websocket!', 50 type: 'warning' 51 }); 52 return 53 } 54 //打开 55 webSocket.onopen = function() { 56 webSocketOpen(); 57 }; 58 //收信 59 webSocket.onmessage = function(e) { 60 webSocketOnMessage(e); 61 }; 62 //关闭 63 webSocket.onclose = function(e) { 64 webSocketOnClose(e); 65 }; 66 //连接发生错误的回调方法 67 webSocket.onerror = function(e) { 68 webSocketonError(e); 69 }; 70 } 71 //连接socket建立时触发 72 function webSocketOpen() { 73 console.log("WebSocket连接成功"); 74 //首次握手 75 webSocketSend(heartCheck.heart); 76 isConnect = true; 77 heartCheck.start(); 78 reConnectNum = 0; 79 } 80 81 //客户端接收服务端数据时触发,e为接受的数据对象 82 function webSocketOnMessage(e) { 83 console.log("websocket信息:"); 84 console.log(e.data) 85 if(e.data == "stopUser") { 86 Message({ 87 message: '你已被上级管理员停用即将跳转登录页', 88 type: 'warning' 89 }); 90 setTimeout(() => { 91 window.location.href = vueConfig.jqUrl + "vue/web/login" 92 }, 3000); 93 } 94 const data = JSON.parse(e.data);//根据自己的需要对接收到的数据进行格式化 95 globalCallback(data);//将data传给在外定义的接收数据的函数,至关重要。 96 } 97 98 //socket关闭时触发 99 function webSocketOnClose(e){ 100 heartCheck.clear(); 101 isConnect = false; //断开后修改标识 102 console.log(e) 103 console.log('webSocket已经关闭 (code:' + e.code + ')') 104 //被动断开,重新连接 105 if(e.code == 1006){ 106 if(reConnectNum < 3){ 107 initWebSocket(); 108 ++reConnectNum; 109 }else{ 110 v.$message({ 111 message: 'websocket连接不上,请刷新页面或联系开发人员!', 112 type: 'warning' 113 }); 114 } 115 } 116 } 117 118 //连接发生错误的回调方法 119 function webSocketonError(e){ 120 heartCheck.clear(); 121 isConnect = false; //断开后修改标识 122 console.log("WebSocket连接发生错误:"); 123 console.log(e); 124 } 125 126 127 //发送数据 128 function webSocketSend(data) { 129 webSocket.send(JSON.stringify(data));//在这里根据自己的需要转换数据格式 130 } 131 //在其他需要socket地方主动关闭socket 132 function closeWebSocket(e) { 133 webSocket.close(); 134 heartCheck.clear(); 135 isConnect = false; 136 reConnectNum = 0; 137 } 138 //在其他需要socket地方接受数据 139 function getSock(callback) { 140 globalCallback = callback 141 } 142 //在其他需要socket地方调用的函数,用来发送数据及接受数据 143 function sendSock(agentData) { 144 //下面的判断主要是考虑到socket连接可能中断或者其他的因素,可以重新发送此条消息。 145 switch (webSocket.readyState) { 146 //CONNECTING:值为0,表示正在连接。 147 case webSocket.CONNECTING: 148 setTimeout(function() { 149 sendSock(agentData, callback); 150 }, 1000); 151 break; 152 //OPEN:值为1,表示连接成功,可以通信了。 153 case webSocket.OPEN: 154 webSocketSend(agentData); 155 break; 156 //CLOSING:值为2,表示连接正在关闭。 157 case webSocket.CLOSING: 158 setTimeout(function() { 159 sendSock(agentData, callback); 160 }, 1000); 161 break; 162 //CLOSED:值为3,表示连接已经关闭,或者打开连接失败。 163 case webSocket.CLOSED: 164 // do something 165 break; 166 default: 167 // this never happens 168 break; 169 } 170 } 171 172 export default { 173 initWebSocket, 174 closeWebSocket, 175 sendSock, 176 getSock 177 };
2)main.js引入挂载原型
1 import Vue from "vue"; 2 import App from "./App.vue"; 3 import router from "./router"; 4 import store from "./store"; 5 import axios from "./utils/api/index"; 6 import utils from "./utils"; 7 import socketApi from "./utils/websocket"; 8 Vue.prototype.$socketApi = socketApi;//websocket挂在原型上
3)使用
1 beforeDestroy() {//销毁的生命周期 2 this.$socketApi.closeWebSocket(); 3 }, 4 mounted() {//挂载的生命周期 5 this.$socketApi.initWebSocket(this.getsocketResult); 6 }, 7 methods: { 8 // socket信息返回接受函数 9 getsocketResult(data) { 10 console.log(data); 11 }, 12 //发送socket信息 13 websocketSend(data) { 14 this.$socketApi.sendSock(data); 15 }, 16 },
4)例子:项目首页有一个消息铃铛,展示对应的消息数量。项目通过新增系统通知实时接收消息数量。
5)上代码
1 export default { 2 data() { 3 return { 4 badgeValue: 0, // 消息总条数 5 // websocket消息推送 6 ws: null, // websocket实例 7 wsUrl: "", // websocket连结url 8 tt: null, 9 heartCheck: null, 10 timeoutObj: null, 11 serverTimeoutObj: null, 12 timer: null, // 定时器 13 lockReconnect: false, 14 }; 15 }, 16 methods: { 17 // 页面获取用户信息 18 initPage() { 19 const loginInfo = JSON.parse(localStorage.getItem("loginInfo")); 20 this.wsUrl = `wss://xxxxxxxxx/api/management/wsmsg/${loginInfo.userId}`; 21 window.clearTimeout(this.timeoutObj); 22 window.clearTimeout(this.serverTimeoutObj); 23 window.clearTimeout(this.tt); 24 this.createWebSocket(); 25 }, 26 // 创建websocket 27 createWebSocket() { 28 try { 29 this.ws = new WebSocket(this.wsUrl); 30 this.initWebScoketFun(); 31 } catch (e) { 32 this.reconnect(this.wsUrl); 33 } 34 }, 35 // websocket消息提醒 36 initWebScoketFun() { 37 const timeout = 30000; 38 this.timeoutObj = null; 39 this.serverTimeoutObj = null; 40 this.heartCheck = { 41 start: () => { 42 this.timeoutObj && window.clearTimeout(this.timeoutObj); 43 this.serverTimeoutObj && 44 window.clearTimeout(this.serverTimeoutObj); 45 this.timeoutObj = setTimeout(() => { 46 // 这里发送一个心跳,后端收到后,返回一个心跳消息, 47 this.ws.send("1"); 48 this.serverTimeoutObj = setTimeout(() => { 49 this.ws.close(); 50 }, timeout); 51 }, timeout); 52 }, 53 }; 54 this.ws.onclose = () => { 55 console.log("链接关闭", this.wsUrl); 56 this.reconnect(this.wsUrl); 57 }; 58 this.ws.onerror = () => { 59 console.log("链接失败", this.wsUrl); 60 this.reconnect(this.wsUrl); 61 }; 62 this.ws.onopen = () => { 63 // 心跳检测重置 64 this.heartCheck.start(); 65 }; 66 this.ws.onmessage = (event) => { 67 console.log(event, "webScoket心跳链接"); 68 if ( 69 event.data != "1" && 70 event.data != "链接成功" && 71 event.data.indexOf("newAutoOpenOrder") == -1 72 ) { 73 //获取后台的小铃铛的数量、及重新调取列表接口 74 const data = JSON.parse(event.data); 75 this.badgeValue = data.msgnum; 76 this.getMessageList(); 77 } 78 // 拿到任何消息都说明当前连接是正常的 79 this.heartCheck.start(); 80 }; 81 }, 82 // 重新链接websocket 83 reconnect(url) { 84 if (this.lockReconnect) { 85 return; 86 } 87 this.lockReconnect = true; 88 // 没连接上会一直重连,设置延迟避免请求过多 89 this.tt && window.clearTimeout(this.tt); 90 this.tt = setTimeout(() => { 91 this.createWebSocket(url); 92 this.lockReconnect = false; 93 }, 60000); 94 }, 95 // 获取未读消息列表 96 async getMarkallread() { 97 const res = await markallread(this.loginInfo.userId); 98 if (res && res.code == 200) { 99 this.getMessageList(); 100 } 101 }, 102 // 退出登录 清空WebSocket 103 logout() { 104 this.ws.onclose = () => { }; 105 this.ws.onerror = () => { }; 106 this.ws.close(); 107 window.clearTimeout(this.timeoutObj); 108 window.clearTimeout(this.serverTimeoutObj); 109 this.$router.push("/login"); 110 }, 111 },