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   },

 

posted @ 2022-12-29 15:45  程序员肉包子  阅读(2884)  评论(0编辑  收藏  举报