前言
websoket出现的原因: 传统的http请求只能是由前端向后台发送一个请求,然后后台把结果返回给前端,前端再进行展示。这里就暴露了一个问题,就是通信只能由前端发起,而后台无法主动与前端通信。而websoket的出现就是为了解决这个问题,让前端可以主动联系后台,后台也可以主动联系前台。
应用场景: 相信大家都知道websoket的应用场景主要是用于即时通讯,比如QQ、微信即时通讯软件,同时在一些实时监控,需要即时暴露问题的地方也需要用到websoket,比如大屏可视化,需要及时的展现商品的成交数量。另外还有就是后台需要某个条件才能触发任务,在触发了任务时需要主动的告诉前端。
主流的技术
1.原生websocket
2.socket.io(推荐)
目前在websocket中应用比较多的就是这两个技术,一个是原生的websoket,另外一个socket.io是在原生websoket的基础上进行了封装了一层后的技术,让程序员更加的好用。
如何进行技术的选择:个人推荐最好还是使用ocket.io这个库, 原因就是更好用。但是很大的原因还是需要取决于后台的选择,如果后台选择了原生websocket,那么前端也需要使用原生websocket。如果后台对socket.io熟悉,那么前端也需要选择 socket.io,这是最好不过的,会让你们的开发更加的顺畅。
基础使用
1.websoket
// WebSocket构造函数,创建WebSocket对象
let ws = new WebSocket('ws://localhost:8888')
// 连接成功后的回调函数
ws.onopen = function (params) {
console.log('客户端连接成功')
// 向服务器发送消息
ws.send('hello')
};
// 从服务器接受到信息时的回调函数
ws.onmessage = function (e) {
console.log('收到服务器响应', e.data)
};
// 连接关闭后的回调函数
ws.onclose = function(evt) {
console.log("关闭客户端连接");
};
// 连接失败后的回调函数
ws.onerror = function (evt) {
console.log("连接失败了");
};
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,这样服务端会抛异常。
window.onbeforeunload = function() {
ws.close();
}
如以上代码,基本上已经涵盖了原生ws的所有用法,包括原生ws的事件:open,Message,Error,Close四大事件,两个基本方法:send方法(用于发送数据),close方法(用于关闭连接)
解释:
open事件:websoket连接成功时会执行该方法(也叫做回调函数)
message事件:websoket在收到消息时会执行该方法,例如后台send一个数据给前端,前端收到后就会执行message方法。
error事件:websoket连接错误时会执行该方法,一般是用于处理ws错误的情况
close事件:websoket连接关闭后会执行该回调函数,一般用于处理错误或善后
send方法:websoket发送消息的方法
colse方法:主动关闭ws连接的方法
参考链接:websoket前端基本使用:https://blog.csdn.net/qq_17627195/article/details/128559926
2.socket.io
这里soket.io分为前端和后端,前端使用只需要使用socket.io-client这个库就ok了,如下图官网
引入:
import io from 'socket.io-client';
使用:
<template></template>
<script>
import io from 'socket.io-client';
export default {
data(){
return{
sockets:null,
}
}
mounted(){
// 建立socket连接
this.sockets = io('ws://localhost:8888')
//监听
this.sockets.on('connect', () => {
//监听连接是否成功
this.sockets.emit("news","这是发送事件的内容");
console.log('connect');
});
this.sockets.on('disconnect', (reason) => {
//断开连接时触发此事件
console.log(reason);
});
this.sockets.on("connect_error", () => {
// 连接错误时触发该事件
console.log('connect_error');
});
this.sockets.on('XXXX', (data) => {
//监听后端返回事件,XXXX是与后端约定的字段
console.log('XXXX',data);
});
},
destroyed() {
//断开连接,需要时再执行
this.sockets.close()
},
}
</script>
这里列出了socket.io的主要事件和方法(还有一些没有列出,需要的可以到官网查询)
主要事件有connect,disconnect,connect_error事件以及很重要的自定义消息事件。方法有on,emit,close方法
connect事件:socket.io连接成功后会执行该回调
disconnect事件:socket.io断开连接后会执行该回调
connect_error事件:socket.io连接错误时会执行该回调
自定义消息事件xxxx:socket.io在收到后台发送的xxxx事件后会执行xxxx的事件。这里的自定义消息事件,一定是前后端都需要一致的。比如后台向前端发送了(emit)事件A,内容为’aaaa’,则前端通过监听事件A,则能拿到后台的消息,一致的是事件A的名字。
on方法:监听各种事件
emit方法:用于发送消息
close方法:用于关闭socket.io的连接
参考链接:
1.官网:https://socket.io/docs/v4/client-socket-instance/
2.socket.io前端基本使用:https://blog.csdn.net/dragon_zjl/article/details/124793808
心跳机制和断线重连
心跳机制和断线重连方法出现的原因: 这里想说明的是websoket的基本使用其实是不难的,最主要还是需要去处理实际业务中会出现的问题,如果仅会基础使用的话,你将会面临以下几个问题:1.服务端挂了,半小时后服务重启成功了,但是发现websoket依然没有连接上来。 2.用户断网,断网之后又有网了,发现websoket在断网后断开了连接,但是在用户有网时却没有连接上来 3.在使用了nginx之后发现:websoket会自动断线,且连接不上来。
心跳机制简述:就是客户端每隔一段时间向服务端发送一个特有的心跳消息,每次服务端收到消息后只需将消息返回,此时,若二者还保持连接,则客户端就会收到消息,若没收到,则说明连接断开,此时,客户端就要主动重连,完成一个周期
断线重连简述:就是在用户断线或者服务端掉线之后,每隔一段时间重新new一个websoket实例的过程(new的过程就是连接的过程)。
实现代码(基础版-node后端):
const WebSocket = require("ws")
const wss = new WebSocket.Server({ port: 3000 })
wss.on("connection", (ws) => {
console.log("有人连接进来了")
// ws.on("message", (data) => {
// ws.send(data + "+九月九日忆山东兄弟")
// })
ws.on("error", () => {
console.log("连接错误")
})
ws.on("close", () => {
console.log("有人断开连接了")
})
ws.on("message", (e) => {
console.log(e)
const data = JSON.parse(e)
console.log(data)
switch (data.ModeCode) {
case "message":
console.log("收到消息" + data.msg)
ws.send(e)
break
case "heart_beat":
console.log(`收到心跳${data.msg}`)
ws.send(e)
break
}
})
})
实现代码(基础版-前端):
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link>
|
<router-link to="/about">About</router-link>
<div>
<!-- {{ wsdata }} -->
</div>
</nav>
<router-view />
</div>
</template>
<script>
const ModeCode = {
// websocket消息类型
MSG: 'message', // 普通消息
HEART_BEAT: 'heart_beat' // 心跳
};
export default {
data() {
return {
ws: null,
webSocketState: false, // webSocket的连接状态
heartBeat: {
// 心跳连接的时间设置
time: 5 * 1000, // 心跳时间间隔
timeout: 3 * 1000, // timeout:心跳超时间隔
reconnect: 10 * 1000 // 断线重连时间
},
reconnectTimer: null // 断线重连时间器
};
},
created() {
this.connectWebSocket();
},
methods: {
/*
* 心跳初始函数
* @param time:心跳时间间隔
*/
startHeartBeat(time) {
setTimeout(() => {
this.ws.send(
JSON.stringify({
ModeCode: ModeCode.HEART_BEAT,
msg: new Date()
})
);
this.waitingServer();
}, time);
},
// 延时等待服务端响应,通过webSocketState判断是否连线成功
waitingServer() {
this.webSocketState = false;
setTimeout(() => {
if (this.webSocketState) {
this.startHeartBeat(this.heartBeat.time);
return;
}
console.log('心跳无响应,已断线');
try {
this.ws.close();
} catch (e) {
console.log('连接已关闭,无需关闭');
}
this.reconnectWebSocket();
}, this.heartBeat.timeout);
},
// 重连操作
reconnectWebSocket() {
this.reconnectTimer = setTimeout(() => {
this.reconnectWs();
}, this.heartBeat.reconnect);
},
connectWebSocket() {
this.ws = new WebSocket('ws://localhost:3000');
this.init();
},
init() {
this.ws.addEventListener('open', () => {
// eslint-disable-next-line spaced-comment
this.webSocketState = true; //socket状态设置为连接,做为后面的断线重连的拦截器
// eslint-disable-next-line no-unused-expressions
this.heartBeat && this.heartBeat.time ? this.startHeartBeat(this.heartBeat.time) : ''; // 是否启动心跳机制
console.log('开启');
});
this.ws.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
switch (data.ModeCode) {
case ModeCode.MSG: // 普通消息
console.log('收到消息' + data.msg);
break;
case ModeCode.HEART_BEAT: // 心跳
this.webSocketState = true;
console.log('收到心跳响应' + data.msg);
break;
}
});
this.ws.addEventListener('close', (e) => {
this.webSocketState = false; // socket状态设置为断线
console.log('断开了连接');
});
this.ws.addEventListener('error', (e) => {
this.webSocketState = false; // socket状态设置为断线
this.reconnectWebSocket(); // 重连
console.log('连接发生了错误');
});
},
reconnectWs() {
if (!this.ws) {
// 第一次执行,初始化
this.connectWebSocket();
}
if (this.ws && this.reconnectTimer) {
// 防止多个websocket同时执行
clearTimeout(this.reconnectTimer);
this.ws.reconnectTimer = null;
this.connectWebSocket();
}
}
}
};
</script>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
主要的代码逻辑:
1.在声明周期钩子函数created中进行连接ws,主要是去new出一个ws实例,以及为该实例注册事件。
2.在连接ws成功后会执行open的回调函数,在该回调函数中开启心跳,即定时向后端发送数据,如代码中,向后台每5秒发送一个心跳,并同时等待服务端的响应(waitingServer函数)。在waitingServer函数中将连接状态设置为false(假设没连接),如果此时后台是连接正常的,则会触发message的回调函数,将连接状态设置为true。而且waitingServer函数将会在3秒后进行检测连接的状态。如果3s后的状态正常,则继续发送心跳。但是如果不成功,那么就去关闭本次websoket连接,并触发重连操作。
3.在重连操作中,则是每隔10s进行一次初始化ws的操作。但是在初始化ws时,由于后台服务掉线/用户没网会触发ws的’error’事件,该事件的回调中又去触发重连操作,重连操作中又是去触发初始化ws的操作,所以形成了一次次的重连操作,直到连接上后台服务为止。
最后是模拟重连的截图
a.后台服务掉线
b.用户断网
最后
websocket心跳机制和断线重连的理解可能需要花费更多的时间,重点是需要自己去跑通代码并看其中的逻辑,我这里放的代码是完全没有抽取过的,属于新手比较容易看得懂的代码。如果追求更好的代码封装,可以看下下面这篇文章:
参考链接:心跳机制和断线重连:https://juejin.cn/post/6945057379834675230文章来源:https://www.toymoban.com/news/detail-455856.html
这里放出本次demo的地址,可以下载下来自己去看。
demo地址:
node后台:https://github.com/rui-rui-an/wsnodeserver
前端:https://github.com/rui-rui-an/wswebcode文章来源地址https://www.toymoban.com/news/detail-455856.html
到了这里,关于websocket的基础使用,心跳机制,断线重连的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!