传统HTTP的问题:
// 客户端连接示例
const ws = new WebSocket('ws://localhost:8080');
// 监听连接打开
ws.onopen = () => {
console.log('连接建立');
ws.send('Hello Server'); // 发送消息
};
// 监听接收消息
ws.onmessage = (event) => {
console.log('收到消息:', event.data); // 服务器主动推送
};
// 监听错误
ws.onerror = function(error) {
console.log('连接错误:', error);
};
// 监听关闭
//1. 主动关闭:代码调用了 socket.close()
//2. 服务端主动断开连接
//3. 网络中断(断网、WiFi 切换等)
//4. 服务端崩溃或重启
//5. 心跳超时后代码调用 socket.close()
//6. 浏览器关闭页面(如果是普通 WebSocket)
ws.onclose = function(event) {
console.log('连接关闭:', event.code, event.reason);
// event.code: 1000(正常关闭), 1001(离开), 1006(异常关闭)等
};特点:
// 状态特征:
// - WebSocket 对象刚创建
// - 正在进行 TCP 握手 + HTTP 升级
// - 还不能发送消息// 状态特征
// - 握手成功
// - 可以双向通信
// - 可以发送和接收消息// 状态特征:
// - 正在关闭连接
// - 不能发送新消息
// - 可能还在接收最后的消息// 状态特征:
// - 连接完全断开
// - 不能再发送或接收消息
// - 可以重新创建新连接心跳保活是一种通过定时发送小数据包来检测和维护 WebSocket 连接健康状态的机制。
生活类比:
// 简单的心跳实现
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping'); // 发送心跳
}
}, 30000); // 每30秒发送一次
ws.onmessage = (event) => {
if (event.data === 'pong') {
console.log('心跳正常');
}
};WebSocket 虽然叫"持久连接",但实际网络环境中有很多因素会导致连接意外断开:
/*
真实场景:
1. 客户端和服务器建立了 WebSocket 连接
2. 30分钟没有数据传输
3. 路由器/防火墙/NAT认为这个连接已经失效
4. 悄悄把连接断开了
5. 客户端和服务端都不知道连接已断
6. 当有数据要发时,才发现连接已死
*/心跳保活机制的作用:
降级方案:重连 + 消息缓存
当心跳检测到连接异常时,不是简单地放弃,而是通过自动重连和消息缓存来保证服务的连续性和消息的可靠性
基础重连方案:
class BasicReconnection {
constructor(url) {
this.url = url;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5; //最大重连次数
this.reconnectDelay = 1000;
this.isManualClose = false; // 是否正常关闭
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('连接成功');
this.reconnectAttempts = 0; // 重置重连计数
};
this.ws.onclose = (event) => {
console.log(`连接关闭: ${event.code}`);
// 如果不是主动关闭,尝试重连
if (!this.isManualClose && event.code !== 1000) {
this.reconnect();
}
};
this.ws.onerror = (error) => {
console.error('连接错误:', error);
};
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('达到最大重连次数,停止重连');
this.notifyReconnectFailed();
return;
}
// 指数退避算法:1s, 2s, 4s, 8s, 16s...
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
console.log(`${delay}ms 后进行第${this.reconnectAttempts + 1}次重连`);
setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, delay);
}
close() {
this.isManualClose = true;
if (this.ws) {
this.ws.close(1000, '用户主动关闭');
}
}
notifyReconnectFailed() {
// 显示提示给用户
alert('无法连接到服务器,请刷新页面重试');
}
}如果你在应用中使用了 WebSocket 或长轮询,用户每开一个标签页,服务器就会多一个连接。这不仅浪费客户端内存,也会给后端服务器带来巨大的压力。
传统WebWorker的限制:
1. 独立线程:运行在后台,不阻塞UI
2. 多标签共享:同源下所有标签页可连接同一个Worker
3. 生命周期:只要有标签页连接就存活,最后一个断开后销毁
4. 独立作用域:有自己的全局对象,不能访问DOM核心区别:
1. 生命周期不同
- WebWorker: 跟随创建它的标签页,标签页关闭则销毁
- SharedWorker: 所有连接的标签页关闭后才销毁
2. 通信范围不同
- WebWorker: 只能与创建它的页面通信
- SharedWorker: 可以与同源下所有标签页通信
3. 创建方式不同
- WebWorker: new Worker('worker.js')
- SharedWorker: new SharedWorker('worker.js')
4. 通信端口不同
- WebWorker: 直接使用 worker.postMessage()
- SharedWorker: 需要通过 port 通信初始化:第一个打开的页面启动 Shared Worker。
建立连接:Shared Worker 内部建立一个 WebSocket 连接。
分发数据:当 WebSocket 收到服务器消息,Worker 遍历所有已连接的 ports,将数据广播出去。
发送数据:页面将消息发给 Worker,由 Worker 通过唯一的 WebSocket 发送给后端。
