一、WebSocket相关定义
1. WebSocket定义
WebSocket
是一种基于
TCP
的
全双工
通信协议,它提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法。数据可以作为“数据包”在
两个方向
上传递,而无需中断连接也无需额外的
HTTP
请求。
-
使用场景:对于需要连续数据交换的服务,例如网络游戏,实时交易系统等,WebSocket 尤其有用。最典型的场景就是聊天室。
2. WebSocket产生原因
HTTP
协议有一个缺陷:通信只能由客户端发起。
这种
单向请求
的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用”轮询”:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。
-
(可以查看我的上一篇文章)
【Web通信】短轮询与长轮询(Long Polling)是什么?
轮询的效率低,非常浪费资源(因为必须不停连接,或者
HTTP
连接始终打开)。因此,工程师们一直在思考,有没有更好的方法—-所以
WebSocket
就是应运而生。
根据信息的传送方向,【串行通信】可以进一步分为
单工
、
半双工
和
全双工
三种。
信息只能单向传送为”单工”;
信息能双向传送但不能同时双向传送称为”半双工”;
信息能够同时双向传送则称为”全双工”。
二、WebSocket 的使用
1. 创建链接
WebSocket
对象作为一个构造函数,用于新建
WebSocket
实例。
我们还需要在
url
中使用特殊的协议
ws
创建实例:
let ws = new WebSocket("ws://javascript.info");
执行上面语句之后,客户端就会与服务器进行连接。
它同样也有一个加密的
wss://
协议。类似于
WebSocket
中的
HTTPS
。(具体见本文下方第四节:nginx配置websocket)
类比如下图:
2. WebSocket 的事件
一旦
WebSocket
连接被建立,我们就应该监听实例上的事件。一共有 4 个事件:
- onopen – 建立连接时触发
- onmessage – 客户端接受服务端数据时触发
- onerror – 通信错误时触发
- onclose – 连接关闭时触发
3. WebSocket 的方法
- send(body) – 向服务器发送文本或二进制数据:调用允许 body 是字符串或二进制格式,包括 Blob,ArrayBuffer 等。不需要额外的设置:直接发送它们就可以了。
- close() – 关闭连接
4. WebSocket.readyState
readyState
属性返回实例对象的当前状态,共有四种状态
- CONNECTING: 值为0 – 表示正在连接,连接尚未建立
- OPEN:值为1 – 表示连接成功,可以进行通信
- CLOSING:值为2 – 表示连接正在关闭
- CLOSED:值为3 – 表示连接已经关闭,或者打开连接失败
5. 简单示例
// 创建一个WebSocket对象-接口地址如"ws://javascript.info"
var ws = new WebSocket("接口地址")
// 连接成功时触发
ws.onopen = function() {
alert("连接成功")
}
// 连接失败时触发
ws.onerror = function() {
alert("连接失败")
}
// 发送数据
ws.send("这是文本或者二进制数据"); // 向服务端发送请求
// 接收消息时触发
ws.onmessage = function(MessagEvent) {
console.log(MessagEvent.data)
}
// 连接关闭的回调函数
ws.onclose = function(){
alert("close")
}
三、在Vue中封装WebSocket
WebSocket心跳机制
:
在使用
websocket
过程中,可能会出现网络断开的情况,比如信号不好,或者网络临时性关闭,
这时候
websocket
的连接已经断开,而浏览器不会执行
websocket
的
onclose
方法,我们无法知道是否断开连接,也就无法进行重连操作。
基于以上问题:如果当前发送websocket数据到后端,一旦请求超时,
onclose
便会执行,这时候便可进行绑定好的重连操作—这就是
WebSocket心跳机制
。
心跳机制
是每隔一段时间会向服务器发送一个数据包,告诉服务器自己还活着,同时客户端会确认服务器端是否还活着,如果还活着的话,就会回传一个数据包给客户端来确定服务器端也还活着,否则的话,有可能是网络断开连接了,需要重连。
自己封装的socket.ts:
/*
* 这里的封装export了三个方法
* initWebSocket 用来初始化websock,可传入url
* sendWebsocket 用来发送数据
* closeWebsocket 用来关闭连接
*/
import { ElMessage } from 'element-plus';
// import { Session } from '/@/utils/storage'; // 引入自定义存储方法
interface IWebSocket {
url: string; // webSocket地址
heartbeatTimeCycle?: number; // 心跳周期时间
}
let ws:any = null; // webSocket实例
let socketUrl = "wss://javascript.info/article/websocket/demo/hello";
let lockReconnect = false; // 是否真正建立连接
let heartbeatTimeCycle:number = 20 * 1000; // 定义心跳周期默认为20s
let timeoutObj:any = null; //心跳心跳倒计时
let serverTimeoutObj:any = null; //心跳倒计时
let timeoutNum:any = null;
let global_callback:any = null;
/**
* @description: 初始化webSocket
* @param {string} url 传入的ws链接,例如"wss://javascript.info/article/websocket/demo/hello"
*/
export const initWebSocket = (params:IWebSocket) => {
socketUrl = params.url; // socket地址
heartbeatTimeCycle = params.heartbeatTimeCycle || 20000; // 心跳周期
if (!window.WebSocket) {
ElMessage.error({
message: "您的浏览器不支持websocket,请升级或更换浏览器!",
type: "error",
center: true,
});
return;
}
if (!ws) {
ws = new WebSocket(socketUrl); // 实例化WebSocket对象为ws
socketOnOpen(); // 开启链接
socketOnClose(); // 监听webSocket是否关闭
socketOnError(); // 监听webSocket是否出现错误,如果出现则重连
socketOnMessage(); // 监听后端消息
}
}
const socketOnOpen = () => {
ws.onopen = () => {
console.log("socket连接成功");
// 开启心跳
startHeartbeat();
};
}
const socketOnClose = () => {
ws.onclose = () => {
console.log("socket已经关闭");
};
}
/**
* @description: 向后端发送webSocket数据
* @param {any} data 字符串或二进制格式
*/
const socketOnSend = (data:any) => {
// 如果连接正常
if (ws.readyState == 1) {
ws.send(data);
}
}
const socketOnError = () => {
ws.onerror = () => {
reconnect();
console.log("socket链接错误");
};
}
// 监听后端返回的信息
const socketOnMessage = () => {
ws.onmessage = (event:any) => {
if(event.data.includes("heartbeat")){
// 在心跳周期内收到后端消息, 则重置心跳
resetHeartbeat();
}else{
// 把后端返回值给回调函数
console.log("来自后端的数据---",event.data);
global_callback(event.data);
}
};
}
// 开启心跳
const startHeartbeat = () => {
timeoutObj && clearTimeout(timeoutObj);
serverTimeoutObj && clearTimeout(serverTimeoutObj);
console.log("开启心跳---heartbeatTimeCycle心跳周期:(毫秒)",heartbeatTimeCycle);
timeoutObj = setTimeout(() => {
// 如果连接正常
if (ws.readyState == 1) {
// 这里发送一个心跳,后端收到后,返回一个心跳消息,
ws.send('{"heartbeat":"发送给后端的心跳heartbeat"}');
} else {
// 否则重连
reconnect();
}
serverTimeoutObj = setTimeout(() => {
// 超时关闭
console.log("心跳超时未响应---",heartbeatTimeCycle);
ws.close();
}, heartbeatTimeCycle);
}, heartbeatTimeCycle);
}
//重置心跳
const resetHeartbeat = () => {
//清除时间
clearTimeout(timeoutObj);
clearTimeout(serverTimeoutObj);
//重启心跳
startHeartbeat();
}
// 断线重连
const reconnect = () => {
if (lockReconnect) {
return;
}
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
timeoutNum && clearTimeout(timeoutNum);
timeoutNum = setTimeout(() => {
//新连接
initWebSocket({
url: socketUrl,
heartbeatTimeCycle: 20
});
lockReconnect = false;
}, 5000);
}
export const sendWebsocket = (agentData:any, callback:any) => {
global_callback = callback;
socketOnSend(agentData);
}
/**
* @description: 前端主动关闭链接
*/
export const closeWebsocket = () => {
if (ws) {
ws.close();
}
clearTimeout(timeoutObj);
clearTimeout(serverTimeoutObj);
}
其他地方调用:
注意调用时机,与页面取消挂载时也需要关闭websocket链接
import { initWebSocket, sendWebsocket } from '/@/utils/screen/socket';
。。。其他代码
// 页面挂载时
onMounted(() => {
// socket
initWebSocket({
url: "wss://javascript.info/article/websocket/demo/hello",//"ws://10.6.60.165:10051/websocket",
heartbeatTimeCycle: 6000, // 定义心跳周期默认为5s
});
sendWebsocket("发送的数据",sentMsg);
});
// 回调函数
const sentMsg = (e:any) => {
console.log(e);
if(e.includes("refresh_workOrder_bigScreen")){
let echartsMapRef = document.getElementById('echartsMap');
setTimeout(() => {
initEchartsMap(echartsMapRef);
}, 1000);
}
};
// 页面取消挂载时
onBeforeUnmount(() => {
if (!state.chart) {
return
}
// 销毁chart实例
state.chart.dispose();
state.chart = null;
// 关闭webSocket链接
closeWebsocket();
});
界面接口效果:
四、nginx配置websocket
表明是websocket连接进入的时候,进行一个连接升级将http连接变成websocket的连接。
http {
server {
listen 80;
server_name zhyl.whhksj.com.cn;
location / {
# 所有GET请求直接301跳转不用管,非GET请求的用proxy_pass来转发,将参数传递给服务
if ($request_method ~ ^(POST|DELETE|OPTIONS)$) {
proxy_pass https://zhyl.whhksj.com.cn;
break ;
}
#把http的域名请求转成https
rewrite ^(.*)$ https://${server_name}$1 permanent;
}
#配置wss开始 ---------- 走http请求,前端需要用 ws://zhyl.whhksj.com.cn/websocket进行连接 ^~ 代表通配符
location ^~ /websocket {
proxy_pass http://123.60.20.985:10051/websocket;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 600s;
proxy_read_timeout 600;
proxy_send_timeout 600s;
}
#配置wss结束------------------------------------------
}
#nginx证书配置 https://cloud.tencent.com/document/product/400/35244
server {
#SSL 默认访问端口号为 443
listen 443 ssl;
#请填写绑定证书的域名
#server_name zhyl.whhksj.com.cn;
#请填写证书文件的相对路径或绝对路径
ssl_certificate /etc/pki/tls/certs/nginx/whhksj.com.cn.pem;
#请填写私钥文件的相对路径或绝对路径
ssl_certificate_key /etc/pki/tls/certs/nginx/whhksj.com.cn.key;
ssl_session_timeout 5m;
#请按照以下协议配置
ssl_protocols TLSv1.2 TLSv1.3;
#请按照以下套件配置,配置加密套件,写法遵循 openssl 标准。
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
#静态资源访问配置
server_name localhost;
root /usr/local/webapp/after-sales-cloud;
keepalive_timeout 600;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
index index.html index.htm;
add_header Access-Control-Max-Age 86400;
add_header Pragma no-cache;
add_header Expires 0;
}
#解决前端跨域请求问题 80到10051
location /api {
proxy_pass http://123.60.20.985:10051;
}
location /wx {
proxy_pass http://123.60.20.985:10051;
}
location /public {
proxy_pass http://123.60.20.985:10051;
}
location /data/wx/wx_files/ {
alias /data/wx/wx_files/;
}
location /data/pc/pc_files/ {
alias /data/pc/pc_files/;
}
location /login {
try_files $uri /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
#配置wss开始 ----------
#走wss请求,前端需要用 wss://zhyl.whhksj.com.cn/websocket 进行连接
#两个位置都配置 则前端通过ws/wss均可建立连接 ^~ 代表通配符
location ^~ /websocket {
proxy_pass http://123.60.20.985:10051/websocket;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_connect_timeout 600s;
proxy_read_timeout 600;
proxy_send_timeout 600s;
}
#配置wss结束------------------------------------------
}
}
-
其他用法可参照
midway-socket-io
📢欢迎——点赞👍/ 收藏⭐/ 留言📝——如有错误敬请指正!
后续持续更新中…