文章目录
一、 简介
1.1 什么是WebSocket
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。
1.2 WebSocket的优势和劣势
WebSocket的优势包括:
- 实时性: 由于WebSocket的持久化连接,它可以实现实时的数据传输,避免了Web应用程序需要不断地发送请求以获取最新数据的情况。
- 双向通信: WebSocket协议支持双向通信,这意味着服务器可以主动向客户端发送数据,而不需要客户端发送请求。
- 减少网络负载: 由于WebSocket的持久化连接,它可以减少HTTP请求的数量,从而减少了网络负载。
WebSocket的劣势包括: - 需要浏览器和服务器都支持: WebSocket是一种相对新的技术,需要浏览器和服务器都支持。一些旧的浏览器和服务器可能不支持WebSocket。
- 需要额外的开销: WebSocket需要在服务器上维护长时间的连接,这需要额外的开销,包括内存和CPU。
- 安全问题: 由于WebSocket允许服务器主动向客户端发送数据,可能会存在安全问题。服务器必须保证只向合法的客户端发送数据。
二、 WebSocket的基本概念
2.1 WebSocket的协议
WebSocket 协议是一种基于TCP的协议,用于在客户端和服务器之间建立持久连接,并且可以在这个连接上实时地交换数据。WebSocket协议有自己的握手协议,用于建立连接,也有自己的数据传输格式。
当客户端发送一个 WebSocket 请求时,服务器将发送一个协议响应以确认请求。在握手期间,客户端和服务器将协商使用的协议版本、支持的子协议、支持的扩展选项等。一旦握手完成,连接将保持打开状态,客户端和服务器就可以在连接上实时地传递数据。
WebSocket 协议使用的是双向数据传输,即客户端和服务器都可以在任意时间向对方发送数据,而不需要等待对方的请求。它支持二进制数据和文本数据,可以自由地在它们之间进行转换。
总之,WebSocket协议是一种可靠的、高效的、双向的、持久的通信协议,它适用于需要实时通信的Web应用程序,如在线游戏、实时聊天等。
下面是一个简单的 WebSocket 生命周期示意图:
2.2 WebSocket的生命周期
WebSocket 生命周期描述了 WebSocket 连接从创建到关闭的过程。一个 WebSocket 连接包含以下四个主要阶段:
-
连接建立阶段(Connection Establishment): 在这个阶段,客户端和服务器之间的 WebSocket 连接被建立。客户端发送一个 WebSocket 握手请求,服务器响应一个握手响应,然后连接就被建立了。
-
连接开放阶段(Connection Open): 在这个阶段,WebSocket 连接已经建立并开放,客户端和服务器可以在连接上互相发送数据。
-
连接关闭阶段(Connection Closing): 在这个阶段,一个 WebSocket 连接即将被关闭。它可以被客户端或服务器发起,通过发送一个关闭帧来关闭连接。
-
连接关闭完成阶段(Connection Closed): 在这个阶段,WebSocket 连接已经完全关闭。客户端和服务器之间的任何交互都将无效。
需要注意的是,WebSocket 连接在任何时候都可能关闭,例如网络故障、服务器崩溃等情况都可能导致连接关闭。因此,需要及时处理 WebSocket 连接关闭的事件,以确保应用程序的可靠性和稳定性。
2.3 Websocket心跳机制
在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。这样服务器会继续向客户端发送多余的链接,并且这些数据还会丢失。所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态,因此就有了websocket的心跳机制。
心跳机制是客户端每隔一段时间会向服务器发送一个ping数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着。如果服务器还活着的话,就会回传一个pong数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了。需要重连
具体代码
由于我是在vue3中写的,这里就用vue3写一个demo
新建一个vue文件或者ts文件用于连接webSocket
<script lang="ts" setup>
//获取cookie中的方法
import { getCookie } from '/@/utils/cookie';
//定义的全局变量,这里主要用 访问的服务ip
import { globalData } from '/@/setting/global';
// pinia 定义状态管理
import { useWebSocket } from '/@/store/index';
const props = defineProps({
value: {
type: String,
default: '',
},
});
let ws:any = reactive(null); //建立的连接
const lockReconnect = ref(false); //是否真正建立连接
const timeout = ref(30000); //30秒一次心跳
const timeoutObj = ref(null); //心跳心跳倒计时
const serverTimeoutObj = ref(null); //心跳倒计时
const timeoutnum = ref(null); //断开 重连倒计时
let timer = null;
let url = '';
const initWebpack = () => {
ws = new WebSocket(url);
ws.onopen = onopen;
ws.onmessage = onmessage;
ws.onclose = onclose;
ws.onerror = onerror;
};
const startLink = () => {
if (lockReconnect.value) {
//console.log("链接中");
return;
}
//console.log("开始链接");
if (getCookie('TOKEN')) {
// console.log('存在登录用户', window.location.protocol);
// let wstype = window.location.protocol.indexOf('https') == 0 ? 'wss' : 'ws';
url = `ws://${globalData.apiUrl.slice(6)}resource/websocket?Authorization=Bearer ${getCookie(
'TOKEN',
)}&clientId=${getCookie('clientId')}`;
initWebpack();
} else {
//console.log("不存在登录用户");
// lockReconnect.value = false;
// reconnect();
}
};
// 重连
const reconnect = () => {
if (lockReconnect.value) {
return;
}
// 检查 token 是否存在且有效
if (!getCookie('TOKEN')) {
return;
}
//没连接上会一直重连,设置延迟避免请求过多
timeoutnum.value && clearTimeout(timeoutnum.value);
timeoutnum.value = setTimeout(function () {
//新连接
startLink();
}, 5000);
};
const reset = () => {
//重置心跳
//清除时间
timeoutObj.value && clearTimeout(timeoutObj.value);
serverTimeoutObj.value && clearTimeout(serverTimeoutObj.value);
//重启心跳
start();
};
// 开启心跳
const start = () => {
//开启心跳
timeoutObj.value = setTimeout(function () {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
if (ws.readyState == 1 && getCookie('TOKEN')) {
//如果连接正常
ws.send('heartCheck');
} else {
//console.log("链接无回应");
lockReconnect.value = false;
//否则重连
reconnect();
}
serverTimeoutObj.value = setTimeout(function () {
//超时关闭
if (ws && getCookie('TOKEN')) {
ws.close();
lockReconnect.value = false;
//重连
reconnect();
}
}, timeout.value);
}, timeout.value);
};
// 成功打开websocket 链接
const onopen = () => {
//console.log("链接成功,开启心跳💓");
lockReconnect.value = true;
//开启心跳
reset();
};
// 收到服务器消息 所有消息都在这里处理,存储到store中,这样就可以全局使用了
const onmessage = (e) => {
//console.log(e);
//收到服务器信息,心跳重置
reset();
// 心跳不做处理
if (e.data.indexOf('heartCheck') >= 0) {
return;
}
console.log(JSON.parse(e.data));
// 测试
useWebSocket().setPanel(JSON.parse(e.data));
// const msgData = JSON.parse(e.data);
// message.info(JSON.String(msgData));
// console.log('msgData', msgData);
};
const onclose = (e) => {
// 只有在 token 存在的情况下才进行重连
if (getCookie('TOKEN')) {
lockReconnect.value = false;
reconnect();
}
};
const onerror = (e) => {
//console.log("出现错误", e);
lockReconnect.value = false;
//重连
reconnect();
};
onMounted(() => {
startLink();
});
// 4.卸载前, 关闭链接
onBeforeUnmount(() => {
ws && ws.close();
});
onUnmounted(() => {
ws && ws.close();
});
const test = () => {
startLink();
};
const testValue = ref(1);
let arr = [];
const send = () => {
testValue.value++;
// ws.send(testValue.value);
arr = [];
for (let index = 0; index < 96; index++) {
arr.push(Math.random() < 0.5 ? 0 : 1);
}
ws.send(JSON.stringify(arr));
};
</script>
<template>
<a-button type="primary" @click="test">连接</a-button>
<a-button type="primary" @click="send">发送</a-button>
</template>
<style lang="less" scoped></style>
在store 中新建一个 ts文件
export const useWebSocket = defineStore('webSocket', () => {
// ! @Descripttion: webscoket 只在app.vue文件中连接一次, 时时 获得的信息都存储在这里,便于试试更新,
const homeMessage = ref(<string[]>['123456']);
const panel = ref(<number[]>[]);
const setHomeMessage = (value: string[]) => {
homeMessage.value = value;
};
const setPanel = (value: number[]) => {
panel.value = value;
};
return {
homeMessage,
setHomeMessage,
panel,
setPanel,
};
});