WebSocket是HTML5下一种新的协议(协议名为"ws")。
我们常用的http协议是一种无状态协议,对于事物的处理没有记忆能力,每次请求都是独立的,而且需要浏览器主动向服务器发送请求,服务器再相应浏览器的请求。而websocket在建立连接后,可以互相主动发送请求,且无需像hhttp那样每次请求都发送一次冗长的header。
这里简单的写一个demo,认识一下websocket的使用。
场景:后端定时向前端发送时间、随机颜色,前端再把这些数据实时渲染在页面上。前端也可以点击按钮向服务器端发送数据,效果如下图:
这里后端我用express,并使用express-ws提供websocket服务。
步骤0:任意处新建一个文件夹,命名为server,里面再新建一个文件server.js
步骤1:下载express、express-ws
npm i express express-ws
步骤2:启动服务器,测试是否启动成功
// server.js
// 导入
const express = require("express");
// 创建web服务器
const app = express();
// 监听请求
// 只要用户在浏览器访问http://localhost:3000/test,就会向浏览器相应下面这个h1标签
app.get("/test", (req, res) => {
res.send("<h1>你好,express</h1>");
});
// 启动服务器,端口设置为3000
app.listen(3000, (err) => {
if (!err) {
console.log("服务器启动成功,监听端口3000");
}
});
在server.js中输入以上代码,在命令行输入(进入到根目录输入)node ./server.js,回车,命令行出现以下输出,代表启动服务器成功:
且在浏览器请求localhost:3000/test,出现我们预期的测试信息:
好现在我们已经启动了一个express服务器,我们接着 在server.js加入websocket服务,完整代码如下:
// 导入
const express = require("express");
// 创建web服务器
const app = express();
const ws = require("express-ws");
// 将 ws 混入到 app中,在模块化开发时也可混入在 Router 对象中
const expressWs = ws(app);
// 监听请求
// 只要用户在浏览器访问http://localhost:3000/test,就会向浏览器相应下面这个h1标签
app.get("/test", (req, res) => {
res.send("<h1>你好,express</h1>");
});
app.ws("/socket", (ws, req) => {
// ws:websocket 实例,该实例可以监听来自客户端的消息发送事件
// req:浏览器请求实例
// 使用定时器不停的向客户端推动消息
let timer = setInterval(() => {
ws.send( JSON.stringify({ color: "#" + Math.random().toString(16).slice(2, 8), text: new Date(), }));
}, 3000);
// 监听断开连接
ws.on("close", () => {
console.log("本次连接已关闭");
clearInterval(timer);
timer = null;
});
// 监听浏览器发来的数据
ws.on("message", (res) => {
ws.send( JSON.stringify({ color: "#" + Math.random().toString(16).slice(2, 8), text: `我是浏览器端发送过来的:${res}`})
);
clearInterval(timer);
});
});
// 启动服务器,端口设置为3000
app.listen(3000, (err) => {
if (!err) {
console.log("服务器启动成功,监听端口3000");
}
});
以上,服务器端已准备就绪,接下来就看前端的了。
下面,先为前端代码(vue2):
<template>
<div id="app">
<div class="container" :style="{backgroundColor:present.color}">{{present.text}}</div>
<button class="btn" @click="toSend">向服务器发送数据</button>
</div>
</template>
<script>
const path = "ws://localhost:3000/socket"
export default {
name: 'App',
data(){
return {
socket:null,
present:{
color:'#172b4d',
text:'默认文本'
},
heartCheck:{
timeout: 1000*60*5, //五分钟心跳一次
timer:null,
serverTimer:null,
reset:()=>{
clearTimeout((this.heartCheck.timer))
clearTimeout((this.heartCheck.serverTimer))
return this.heartCheck
},
start:()=>{
this.heartCheck.serverTimer = setInterval(()=>{
if(this.socket.readyState===1){
console.log('虽然没有发送消息,但是现在是连接状态');
this.socket.send('socket heart')
this.heartCheck.reset().start()
}else{
console.log('已断开连接,尝试重连吧');
this.socketReconnect()
}
},this.heartCheck.timeout)
}
},
reconnectTimer:null,// 重连计时器
reconnectLock:false// 重连的锁
}
},
mounted(){
if (typeof (WebSocket) === 'undefined') { console.error('浏览器不支持WebSocket'); return; }
try {
this.initSocket()
} catch (error) {
// 重连
console.log('出来');
this.socketReconnect()
}
},
beforeDestroy(){
this.socket.close()
},
methods:{
initSocket(){
this.socket = new WebSocket(path)
this.socket.onopen = this.openHandler // 连接成功建立的回调
this.socket.onmessage = this.messageHandler // 接收到消息的回调
this.socket.onerror = this.errorHandler // 连接发生错误的回调
this.socket.onsend = this.sendHandler // 向后端发送数据的回调
this.socket.onclose = this.closeHandler // 连接关闭的回调
window.onbeforeunload = () => {
this.socket.close() // 窗口关闭时主动断开请求
};
},
socketReconnect(){
if (this.reconnectLock) {
return;
}
this.reconnectLock = true
this.reconnectTimer && clearTimeout(this.reconnectTimer);
this.reconnectTimer = setTimeout(() => {
console.log("WebSocket:重连中...");
this.reconnectLock = false;
// websocket启动
this.initSocket();
}, 4000);
},
// 连接成功
openHandler(){
this.heartCheck.reset().start();// 建立心跳
},
// 接收到服务器信息
messageHandler(res){
this.present.color = JSON.parse(res.data).color
this.present.text = JSON.parse(res.data).text
this.heartCheck.reset().start() // 初始化心跳时间并重新开始心跳。如果能正常通信,无须心跳检测
},
// 连接错误,重连
errorHandler(){
console.log('错误');
this.socketReconnect()
},
// 向后端发送数据的回调
sendHandler(){},
// 连接关闭的回调
closeHandler(){
this.heartCheck.reset();
},
// 带年纪按钮触发,向服务器发送数据
toSend(){
this.socket.send('我是浏览器端发送的数据')
}
}
}
</script>
<style lang="scss">
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
#app {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 20px;
width: 100vw;
height: 100vh;
.container {
width: 500px;
height: 310px;
text-align: center;
word-wrap: break-word;
word-break: normal;
line-height: 310px;
font-size: 23px;
font-weight: 600;
color: #fff;
background-color: tomato;
}
.btn {
padding: 8px 12px;
border: none;
background-color: #0052cc;
color: #fff;
font-size: 12px;
border-radius: 4px;
&:hover {
background-color: darken($color: #0052cc, $amount: 20%);
}
&:active {
background-color: #0052cc;
}
}
}
</style>
这样启动vue项目,websocket就会自动连上了。当我们关闭服务端时,会按照我们设置的时间间隔不断重连。
如此小demo就大功告成啦。