一.什么是WebSocket?
WebSocket是一种网络通信协议,专门用于"持久连接"和"实时通信".
它允许浏览器和服务器之间建立一个持续不断的链接通道,双方可以随时主动发消息,而不像HTTP那样必须"你请求我才响应",在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
你可以把HTTP想象成一次性纸杯.而WebSocket更像一根水管,接上之后水(数据)可以随时流动
二.WebSocket的特点
简单来说,webSocket具有双向通信,实时性强,支持二进制,控制开销的特点
- 协议标识符是ws(如果加密.则为wss),服务器网址就是URL
- 实时通信,服务器可以随时主动给客户端下发数据
- 保持连接状态,WebSocket需要先创建连接,所以是一种有状态的协议,之后通信时就可以省略部分状态信息
- 控制开销,连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部先对较小,在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关); 对于客户端到服务器的内容,头部还需要加上额外的4字节的掩码
- 实现简单,建立在TCP协议之上,服务器端的实现比较容易,并且没有同源限制,客户端可以与任意服务器通信
- 支持二进制传输,WebSocker定义了二进制帧,可以发送文本,也可以发送二进制数据
- 与HTTP协议有着良好的兼容性,默认端口也是80和443,并且握手阶段采用HTTP协议,因此握手时不容易屏蔽,能通过各种HTTP代理服务器
- 支持扩展,用户可以扩展协议,实现部分自定义的子协议,如部分浏览器支持压缩等
三. webSocket实例

为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头的信息,其中附加头信息"Upgrade: WebSocker"表明这是一个申请协议升级的HTTP请求,服务器端解析这些附加头信息然后产生应答信息返回客户端,客户端和服务器端的WenSpclet连接就建立起来了,双方就可以通过这个链接通道自由的传递信息,并且这个链接会持续存在知道客户端或者服务器端的某一方主动的关闭连接
1. WebSocket事件
-
onopen: 客户端和服务器建立连接后触发,被称为客户端和服务器之间的初始握手,如果接收到open,说明已经连接成功,可以进行通信了
-
onmessage: 接收到消息时触发,服务器发送给客户端的消息可包括纯文本消息,二进制数据(Blob消息或者ArrayBuffer消息)
-
onerror: 响应意外故障时触发,在错误之后总是会终止连接
-
onclose: 连接关闭时触发,一旦连接关闭后,客户端和服务端将不会再进行消息的收发,也可主动调用close()方法关闭连接
-
2.Websocket方法
send( ): 在连接成功后关闭前,发送消息(onopen后和onclose前才可发送消息)
参数:
data: 要发送的数据,可以是字符串,二进制数据或者Blob对象
Blob
对象 是 Web API 中的一个重要接口,全称是 Binary Large Object(大对象) ,主要用于表示 不可变的、原始数据的类文件对象。你可以把它看作是一种在 JavaScript 中处理二进制数据(如图片、音频、视频、文本等)的方法。
close( ): 关闭连接
参数:
code(可选): 一个数字,表示连接关闭的状态码,常见的状态码有1000表示正常关闭,1001表示端点离开,等等
reason(可选): 一个字符串,表示连接关闭的原因
3.Websocket对象属性
- readyState: 只读属性,表示WebSocket的连接状态
1.CONNECTING- 正在连接中,对应的值为0;
2.OPEN 已经连接并且可以通讯,对应的值为1;
3.CLOSING 连接正在关闭,对应的只为2;
4. CLOSED 连接已关闭或者没有连接成功,对应的值为3 - bufferredAmount: 未发送至服务器的字节数,只读属性,已被send()放入正在队列中的等待传输,但是还没有发出的UTF-8 文本字节数
- bufferedAmount: 未发送至服务器的字节数,只读属性,已被send( )放入正在队列中等待传输,但是还没有发出的UTF-8文本字节数
- binaryType: 使用二进制的数据类型连接,可以是"blob" 或 "arraybuffer"
- extensions: 服务器选择的通信扩展
- protocol: 打开捂手期间使用的协议(服务器选择的子协议)
- url: webSocket的绝对路径
四.WebSocket 和 HTTP有什么区别?
特性 | HTTP(传统) | WebSocket(实时) |
---|---|---|
连接方式 | 一问一答,短连接 | 一次握手,长连接 |
谁能发消息 | 只能客户端发送请求 | 服务端和客户端都可以主动发 |
数据格式 | 文本为主(JSON,HTML) | 可传文本,二进制 |
用途 | 页面加载,API请求 | 聊天室,直播,游戏 |
简单来说,HTTP是请求响应的,而WebSocket是双向通信型的
五. WebSocket的工作原理
(1). 建立连接(握手)
浏览器发起一个HTTP请求,请求协议为 ws://或wss://:
javascript
const socket=new WebSocket("wss://yourserver.com/chat")
这一步是一次HTTP握手,成功后协议升级为WebSocket,建立长连接
(2) 开始通信
建立后,客户端和服务器就可以随时双向发消息了
javascript
//实例化
let ws=new WebSocket('ws://localhost:3001')
//监听连接成功
socket.onopen=()=>{
console.log('连接成功!');
socket.send('你好,服务器')
};
//接收消息
socket.onmessage=(event)=>{
conosle.log('收到服务器消息: ',event.data)
}
//监听关闭
socket.onclose=()=>{
console.log('连接关闭')
}
//错误处理
socket.onerror=(error)=>{
console.log('出错啦',error)
}
六. WebSocket是安全的吗?
-
ws://是不加密的链接
-
wss://是加密的WebSocket (相当于HTTPS)
建议生产环境使用 wss://,以防止数据被中间人攻击(MITM)
七.与WebSocket相关的常见问题
-
我能用WebSocket替代所有HTTP请求吗
不推荐,WebSocket适合"频繁推送","实时性高"的场景; 表单提交,一次性请求用HTTP更合适
-
WebSocket和AJAX有什么不同?
AJAX需要你发一次请求才有响应,WebSocket建立好连接后,服务器可以随时给你发消息,延迟更低
-
需要后端支持吗?
是的,后端也必须支持WebSocket协议.比如用:
Node.js 的ws模块
Python的websockets
java的Spring WebSocket等
八.短轮询和WebSocket区别
1.短轮询(Short Polling)
- 工作原理: 客户端定时向服务器发送请求,询问是否有新的数据,服务器响应后返回最新的数据
- 优点: 简单易实现,支持跨浏览器,适用于低延迟要求不高的场景
- 缺点: 会造成频繁的网络请求和服务器压力,无论有新数据都会发起请求
2. WebSocket
- 工作原理: WebScoket是一种全双工通信协议,通过建立持久连接,双方可以实时进行数据传输
- 优点: 实时性更好,支持服务器主动推送数据,减少了不必要的请求开销
- 缺点: 相对于短轮询更复杂,对服务器和客户端都有一定的要求,可能受到网络限制
适用场景
- 短轮询适用于对实时性要求不高,数据更新频率较低的场景,例如一些简单的数据展示页面
- WebSokcet适用于对实时性要求较高,需要频繁传输大量数据的场景,例如在线聊天,实时数据更新等
九. websocket心跳机制
1.作用: 使WebSocket连接保持长连接,避免断开连接的情况发生,同时,心跳机制也可以检查WebSocket连接的状态,及时处理异常情况,还可以减少WebSocket连接及服务器资源的消耗
2.原理: 是利用心跳包及时发送和接收数据,保证WebSocket长连接不被断开
3.详细流程:
- 客户端发起连接: 客户端通过WebSocket协议建立与服务器的长连接
- 定期发送心跳包: 客户端按照预设的频率(如每5秒)向服务器发送心跳消息
- 服务器响应: 服务器接收到心跳消息后,立即会发一个响应,表明连接仍然活跃
- 超时处理: 如果服务器在规定时间内未收到心跳消息,会触发超时机制,可能关闭连接或尝试重新建立
4.心跳检测的重要性提现在多个方面:
- 维持长连接: 防止因网络层的空闲超时而意外断开连接
- 即使发现异常: 快速检测到网络故障或服务器崩溃等情况
- 优化资源利用: 允许服务器清理无效连接,释放资源
- 保障服务质量: 确保链接始终保持可用状态,提升用户体验
5. 在实现心跳检测时,需要权衡几个关键因素:
- 心跳频率 : 平衡检测精度和网络负载
- 超时时间: 考虑网络延迟和服务器响应时间
- 重试机制: 应对短暂的网络波动
通过合理配置这些参数, 可以构建一个即高效又可靠的心跳检测机制,未WebSocket长连接提供坚实的基础
十.心跳包设计
在WebScocket长连接技术中,心跳包的设计是维持连接稳定性和可靠性的重要环节,本节将详细介绍心跳包的结构,内容和发送频率的选择,为开发者提供实用的实现指南
心跳包是WebSocket通信中用于检测连接状态的小型数据包,他们通常包含简单的标识符或消息,用于确认连接的活性,设计合理的心跳包机制可以有效防止连接因闲置而被意外断开,同时也能及时发现和处理异常情况
心跳包的设计主要包括以下几个方面:
1.结构和内容:
- 使用标准的WebSocket帧格式
- 包含特定的操作码(如PING或自定义操作码)
- 可能包含简单的标识符或序列号
2.发送频率:
- 根据应用需求和网络环境进行权衡
- 通常范围:5-30秒
- 考虑因素:网络延迟,服务器负载,电池寿命(移动设备)
3.超时处理:
- 设置适当的超时阀值
- 超时阀值> 发送频率
- 超时后的动作: 断开连接或触发重连机制
4.优化策略:
- 使用二进制数据帧减少宽带消耗
- 采用自适应算法动态调整频率
- 结合ACK机制提高效率
通过精心设计的心跳包机制,可以有效位置WebSocket连接的长期稳定性,同时最小化对网络和服务器资源的影响,这种机制在实时通信应用中扮演着至关重要的校色,确保了连接的可靠性和连续性
十一. 超时处理
在WebSocket长连接技术中,超时处理是心跳检测机制的核心组成部分,合理的超时处理策略不仅能有效监测连接状态,还能再异常情况下及时采取行动,确保系统的稳定性和可靠性
超时处理主要涉及一下几个关键方面:
1.超时阈值设置:
-
基于心跳包发送频率
-
典型值: 心跳频率的1.5-2倍
-
示例: 心跳频率为30秒,超时阈值为45-60秒
2. 超时检测机制:
-
使用定时器检测
-
触发条件: 超时预设阈值未收到响应
-
动作: 断开当前连接
3.重试策略 :
-
引入指数退避算法
-
初始重连间隔:1-2秒
-
后续重连间隔逐步增加:2^N秒(N为尝试次数)
4.最大重试次数限制:
- 避免无限循环重连
- 建议值: 3-5次
- 达到上限后: 停止尝试,通知用户或采取其他措施
5.异常情况处理:
-
检测网络波动
-
区分临时性故障和持续性故障
临时性故障: 短时间重试
持续性故障: 短暂重连,通知用户
通过合理设置这些参数和机制,可以构建一个既能及时发现异常又能避免过度消耗资源的心跳超时处理方案,这种策略能够在保持连接稳定性的同时,最大限度地减少不必要的网络负担和服务器压力
十二.断线重连机制
1. 断线检测
在WebSocket长连接技术中,断线检测时维护连接稳定性的关键环节,为了准确识别WebSocket连接是否断开,我们需要结合多种方法来全面监控连接状态 ,本节将详细介绍两种主要的断线检测机制:网络状态监听和心跳超时判断
2. 网络状态监听
网络状态监听是通过监听浏览器的在线 / 离线事件来判断网路连接的变化,这种方法的优势在于能够快速感知和网络环境的变化,特别是在用户切换网络或重新连接WI-FI等场景下,具体实现如下:
javascript
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);
function updateOnlineStatus(event) {
if (navigator.onLine) {
console.log('网络已连接');
// 检查WebSocket连接状态
checkWebSocketConnection();
} else {
console.log('网络已断开');
// 断开WebSocket连接
disconnectWebSocket();
}
}
浏览器原生online和offline事件来监听网络状态变化,这是目前前端最主流,兼容性也最好的方式之一
然后,网络状态监听也有其局限性,由于WebSocket基于TCP协议,TCP连接并不能敏锐地感知网络层的变化,因此在网络重新连接后需要额外的机制来判断WebSocket连接是否仍然可用
3. 心跳超时判断
心跳超时判断是另一种常用的断线检测方法,它通过定期发送心跳包并在接收方回应的方式来维持连接的有效性,这种方法虽然不如网络状态监听那样迅速,但能覆盖更多复杂的场景,特别是当网络环境相对稳定但服务器出现问题时
心跳超时判断的基本实现如下:
javascript
let lastPongTime = Date.now(); // 上一次收到 pong 的时间戳
const HEARTBEAT_TIMEOUT = 15000; // 最大允许心跳无响应时间(例如:15 秒)
heartbeatInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping'); // 发 ping
console.log('🔄 发送心跳 ping');
const now = Date.now();
if (now - lastPongTime > HEARTBEAT_TIMEOUT) {
console.warn('⛔️ 心跳超时,可能已断线');
ws.close(); // 主动关闭连接,会触发重连逻辑
}
}
}, HEARTBEAT_INTERVAL); // 每 10 秒一次
在实际应用中,通常会结合使用这两种方法来提高断线检测的准确性,例如,在网络状态由离线转为在线时,可以立即发送一次心跳包来快速检测连接状态,同时,持续的心跳超时判断机制可以作为长期监控连接状态的主要手段
通过综合运用这些断线检测机制,我们可以构建一个更加健壮和可靠的WebSocket长连接系统,有效应对各种复杂的网络环境和服务器状态
十三. 重连策略
在WebSocket长连接技术中,重连策略是确保连接稳定性和可靠性的重要组成部分,合理的重连策略不仅能有效应对应网络波动,还能避免过度消耗资源,本节将详细介绍重连策略的关键要素及其实施细节
1. 重连时机
重连时机的选择通常基于以下几种情况:
1.连接关闭事件: 当WebSocket触发onClose事件时,这是最明显的重连时机
- 心跳超时: 如果服务器在预定时间内未收到心跳包响应,可视为连接失效
3.网络状态变化: 监听浏览器的online/offine事件,当网络重新连接时尝试重连
2. 重连间隔
重连间隔的设置直接影响系统的性能和用户体验,一种常用的方法是采用指数退避算法,这种方法随着重连次数的增加,重连间隔呈指数级增长,例如:
javascript
/ 执行重连逻辑,带指数退避策略和最大尝试次数控制
function attemptReconnect() {
// 如果超出最大重连次数,则停止重连并提示用户
if (retryCount >= MAX_RETRY) {
addLog(`🛑 已达到最大重试次数 (${MAX_RETRY}),停止重连`);
updateStatus('🛑 重连失败,请检查网络或刷新页面');
return;
}
// 计算延迟时间,采用指数退避策略(2^n 秒)
const delay = Math.pow(2, retryCount) * 1000;
// 设置延迟重连
reconnectTimer = setTimeout(() => {
retryCount++;
addLog(`🔁 第 ${retryCount} 次重连中...`);
createWebSocket(); // 再次尝试建立连接
}, delay);
}
这种策略有助于减轻服务器的压力,同时给予网络恢复的机会,然后,需要注意的是,过长的重连间隔可能导致用户体验下降,因此,在实际应用中,还需要根据具体情况进行调整
3.最大重试次数
设置最大重试次数是为了防止无限重连导致资源浪费,通常,可以将最大重试次数设置3-5次,当达到最大重试次数后,可以采取一下措施:
- 显示错误提示给用户
2.记录详细的日志以便后续分析
3.提供手动重连的选项
通过合理设置这些参数,可以构建一个既能有效应对网络波动,又能避免过度消耗资源的重连策略,在实际应用中,还需要根据具体的业务需求和网络环境不断优化这些参数,以达到最佳的效果
4.重连实现
在WebSocket长连接技术中,断线重连机制是确保链接稳定性和可靠性的重要组成部分,本节将详细介绍重连实现的关键方面,包括状态管理,错误处理和恢复机制
重连实现的核心在于设计一个能够有效处理连接中断并自动回复的系统,这个系统,这个系统需要考虑多个方面,以确保在各种复杂网络环境下都能保持良好的连接质量
5.状态管理
状态管理是重连机制的基础,一个典型的状态管理模型包括以下几种状态:
状态 | 描述 |
---|---|
CONNECTED | 正常连接状态 |
DISCONNECTED | 连接已断开 |
RECONNECTING | 正在尝试重连 |
这种状态模型可以帮助我们清晰地区分连接不同阶段,并在每个阶段执行相应的操作,例如,当处于RECONNECTING状态时,可以组织新的连接请求,避免重复连接
6.错误处理
错误处理是重连机制中的关键环节,一个有效的错误处理策略应该能够区分暂时性错误和永久性错误,并采取不同的应对措施,例如:
javascript
function handleWebSocketError(error) {
if (isTemporaryError(error)) {
// 尝试重连
startReconnectAttempt();
} else {
// 永久性错误,通知用户或采取其他措施
alertUserOfPermanentFailure();
}
}
这里的关键是能够准确识别错误类型,对于暂时性错误,可以设置一个重连计时器,在适当的时间间隔后尝试重新连接,而对于永久性错误,可能需要采取更激进的措施,如通知用户或切换到备用服务
十四. 恢复机制
恢复机制是确保重连成功后的关键步骤,一个完整的恢复机制应该包括以下几个方面:
1.重新连接: 重新建议WebSocket连接是最基本的步骤
2.恢复会话状态: 重连后可能需要重新认证或回复会话状态
3.重发未完成的消息: 对于需要保证消息完整性的情况,可能需要重新发送之前未完成的消息
4.同步数据: 如果在断线期间有数据更新,可能需要进行数据同步
通过这些机制,可以最大程度地减少断线带来的影响,确保服务的连续性和数据的完整性
在实际应用中,还需考虑一些特殊情况的处理,如:
1.避免重连风暴: 通过设置合理的重连间隔和最大重连次数,可以防止在短时间内大量重连请求对服务器造成过大压力
2.优雅降级: 在多次重连失败后,可以考虑降级到其他通信方式,如HTTP轮询,以维持最基本的服务
3.用户体验优化: 在重连过程中,应向用户提供明确的提示,避免让用户产生误解
通过这些细致的设计和实现,可以构建一个强大而可靠的WebSocket断线重连机制,为实时通信应用提供坚实的保障
十五. 代码示例(客户端简易聊天室)
javascript
<template>
<div class="chat-wrapper">
<div class="chat-box" ref="container">
<div v-for="(item, index) in log" :key="index" class="chat-message">
<span class="time">{{ item.time }}</span>
<span class="text">{{ item.text }}</span>
</div>
</div>
<div class="input-area">
<input
v-model="msg"
@keyup.enter="sendMsg"
placeholder="输入消息..."
class="input"
/>
<button @click="sendMsg" class="send-button">发送</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick,onBeforeUnmount} from 'vue';
// 用户输入消息
const msg = ref('');
// 聊天记录
const log = ref<{ text: string; time: string }[]>([]);
// 聊天框容器引用 用于自动滚动
const container = ref<HTMLElement | null>(null);
// 心跳定检测时器
let heartbeatInterval: any = null;
//心跳响应超时定时器
let timeoutTimer:any=null; //心跳响应超时定时器
//心跳间隔时间
const HEARTBEAT_INTERVAL = 5000;
//心跳超时阈值
const HEARTBEAT_TIMEOUT = 13000;
// 重连定时器
let reconnectTimer: any = null;
//当前重试次数
let reconnectAttempts = 0;
//最大重连次数
const MAX_RECONNECT_COUNT = 5;
//初始重连间隔(1秒)
let reconnectDelay=1000;
//连接状态标识
let isConnect = false;
//消息缓存队列
const messageQueue: any[] = ref([]);
// WebSocket 实例
let ws: WebSocket;
//模拟用户名
const username='用户001'
// 获取当前时间字符串
const getTime = () => {
const now = new Date();
return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
// 添加聊天消息并自动滚动到底部
const addLog = (text: string) => {
log.value.push({ text, time: getTime() });
nextTick(() => {
if (container.value) {
//scrollTop表示当前容器滚动条距离顶部的像素值,设置它就是让滚动条滚动到某个位置
// scrollHeight表示当前容器内容高度
container.value.scrollTop = container.value.scrollHeight;
}
});
};
// 发送消息函数
const sendMsg = () => {
if (!msg.value.trim()) return;
const content = `${username}:${msg.value.trim()}`;
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(content);
} else {
messageQueue.value.push(content); // 缓存消息
}
// 清空输入框
msg.value = '';
};
// 启动心跳机制:每隔 10 秒发送 "ping"
const startHeartbeat = () => {
stopHeartbeat()
heartbeatInterval = setInterval(() => {
//如果连接是正常的(OPEN状态)就给服务器发送消息
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping');
console.log('🔄 发送心跳 ping');
//启用心跳超时倒计时
timeoutTimer = setTimeout(() => {
console.warn('心跳超时,准备关闭连接');
ws.close();//触发onclose -> 重连逻辑
},HEARTBEAT_TIMEOUT);
}
}, HEARTBEAT_INTERVAL);
};
//停止心跳定时器
const stopHeartbeat = () => {
clearInterval(heartbeatInterval);
clearTimeout(timeoutTimer);
};
//尝试重连(断线后3秒重连) 指数退避
const reconnect = () => {
//如果已经连接上就不再重连了或者 尝试重连次数大于最大尝试次数
if (isConnect|| reconnectAttempts >= MAX_RECONNECT_COUNT) {
addLog('❌ 无法连接服务器,请稍后重试');
console.log('无法连接服务器,请稍后重试')
return;
}
//如果重连定时器,就取消旧的定时器
reconnectTimer && clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(() => {
console.log('尝试重连');
reconnectAttempts++;
//2 ** reconnectAttempts 表示2的reconnectAttempts次幂
reconnectDelay = Math.min(1000 * 2 ** reconnectAttempts, 30000); // 最多退避到
// 30 秒
console.log(`第 ${reconnectAttempts} 次重连中...,秒数${reconnectDelay}`);
createWebSocket();
}, reconnectDelay);
};
//创建WebSocket连接,并设置事件处理函数
const createWebSocket = () => {
console.log('创建连接')
stopHeartbeat(); // 清除旧心跳
reconnectTimer && clearTimeout(reconnectTimer); // 清除重连计时器
try {
ws = new WebSocket('ws://localhost:8080');
} catch (e) {
console.error('WebSocket 创建失败', e);
reconnect();
return;
}
//连接成功
ws.onopen = () => {
console.log('连接成功');
//开始心跳
isConnect = true;
//当前重数次数
reconnectAttempts = 0;
reconnectDelay = 1000;
//发送缓存消息
while(messageQueue.value.length > 0){
const cached=messageQueue.value.shift();
if(cached){
ws.send(cached)
}
}
startHeartbeat();
isConnect = true;
};
//收到服务器消息
ws.onmessage = (e) => {
//如果不是pong 心跳回应,就当做聊天消息展示
if (e.data !== 'pong') {
addLog(e.data);
}else{
console.log('收到pong响应')
}
// 收到任意消息说明连接正常,重置超时判断
clearTimeout(timeoutTimer);
};
// 连接关闭
ws.onclose = () => {
console.log('断开连接');
isConnect = false;
//停止心跳
stopHeartbeat();
//重新连接
reconnect();
};
//出错时
ws.onerror = () => {
addLog('⚠️ WebSocket 错误');
console.log('WebSocket 错误');
};
};
//监听网络状态
const handleOnline=()=>{
console.log('网络恢复,尝试重新连接');
createWebSocket();
}
const handleOffline=()=>{
console.log('当前处于离线状态')
}
// 组件挂载后建立 WebSocket 连接
onMounted(() => {
createWebSocket();
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
});
//卸载时清理所有监听与定时器
onBeforeUnmount(() => {
ws?.close();
stopHeartbeat();
reconnectTimer && clearTimeout(reconnectTimer);
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
});
</script>
<style scoped>
.chat-wrapper {
max-width: 500px;
margin: 30px auto;
padding: 20px;
border: 2px solid #e2e2e2;
border-radius: 10px;
background-color: #ffffff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
font-family: 'Segoe UI', sans-serif;
}
.chat-box {
height: 300px;
overflow-y: auto;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}
.chat-message {
padding: 6px 10px;
background: #f1f1f1;
border-radius: 6px;
margin-bottom: 8px;
word-break: break-word;
}
.chat-message .time {
color: #888;
font-size: 12px;
margin-right: 8px;
}
.input-area {
display: flex;
margin-top: 12px;
gap: 8px;
}
.input {
flex: 1;
padding: 10px;
border-radius: 6px;
border: 1px solid #ccc;
font-size: 14px;
}
.send-button {
padding: 10px 20px;
background-color: #409eff;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background 0.2s;
}
.send-button:hover {
background-color: #66b1ff;
}
</style>
Node.js(服务端代码)
javascript
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
// 每个连接都标记为 alive 状态
wss.on("connection", (ws) => {
ws.isAlive = true;
ws.on("message", (message) => {
if (message.toString() === "ping") {
ws.send("pong"); // 心跳回应
} else {
// 广播给其他客户端
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(message.toString());
}
});
}
});
ws.on("pong", () => {
ws.isAlive = true; // 收到 pong,说明连接还活着
});
});
// 定时检查每个连接是否活着
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) {
console.log("💀 无响应,断开连接");
return ws.terminate(); // 主动断开
}
ws.isAlive = false;
ws.ping(); // 主动发 pong,客户端收到后应返回 pong
});
}, 30000); // 每 30 秒检查一次
// 清理定时器
wss.on("close", () => clearInterval(interval));
console.log("✅ WebSocket 服务器已启动:ws://localhost:8080");