WebSocket
简介
WebSocket是一种网络通信协议,它在单个TCP连接上进行全双工通讯。WebSocket协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。
特点
1.双向实时通讯:WebSocket允许服务器主动向客户端推送数据,而不是等待客户端请求。 2.低延迟:WebSocket的通信延迟比HTTP请求/应答模式低得多。 3.长连接:WebSocket连接一旦建立,就可以持续通信,直至一方关闭连接。
基本原理
WebSocket通信分为三个阶段:握手、数据交换和关闭连接。 1.握手:客户端通过HTTP请求向服务器发起WebSocket连接请求,包含特殊的Upgrade头部字段。服务器确认请求后,握手完成。 2.数据交换:握手完成后,客户端和服务器可以通过WebSocket连接发送任意数据。数据被封装成帧,以便于解析和传输。 3.关闭连接:任何一方都可以通过发送特定的帧来关闭WebSocket连接。接收到关闭帧的一方将关闭连接。
应用场景
WebSocket广泛应用于实时通信、在线游戏、实时推送等场景。
总结
WebSocket协议提供了一种高效、低延迟的双向实时通信机制,适用于需要实时交互的Web应用。了解WebSocket的基本原理和应用场景,可以帮助我们更好地设计和开发实时Web应用。
Springboot中使用
SpringBoot整合WebSocket的相关依赖
在pom.xml里添加依赖。
<!-- Spring WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>添加WebSocket的配置类在SpringBoot中启用WebSocket
/**
* 开启WebSocket支持
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}WebSokcet工具类记录当前的在线连接对链接进行操作
public class WebsocketUtil {
/**
* 记录当前在线的Session
*/
private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();
/**
* 添加session
* @param userId
* @param session
*/
public static void addSession(String userId, Session session){
// 此处只允许一个用户的session链接。一个用户的多个连接,我们视为无效。
ONLINE_SESSION.putIfAbsent ( userId, session );
}
/**
* 关闭session
* @param userId
*/
public static void removeSession(String userId){
ONLINE_SESSION.remove ( userId );
}
/**
* 给单个用户推送消息
* @param session
* @param message
*/
public static void sendMessage(Session session, String message){
if(session == null){
return;
}
// 同步
RemoteEndpoint.Async async = session.getAsyncRemote ();
async.sendText ( message );
}
/**
* 向所有在线人发送消息
* @param message
*/
public static void sendMessageForAll(String message) {
//jdk8 新方法
ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
}
}WebSocket接口处理类
也就是websocket真正请求的地址,userId相当于房间名,根据房间名进入到房间,开始监听房间里的信息。
@Component
@ServerEndpoint(value = "/chat/{userId}")
public class WebsocketController {
/**
* 连接事件,加入注解
* @param userId
* @param session
*/
@OnOpen
public void onOpen(@PathParam(value = "userId") String userId, Session session) {
String message = "[" + userId + "]加入聊天室!!";
// 添加到session的映射关系中
WebsocketUtil.addSession(userId, session);
// 广播通知,某用户上线了
// WebsocketUtil.sendMessageForAll(message);
}
/**
* 连接事件,加入注解
* 用户断开链接
*
* @param userId
* @param session
*/
@OnClose
public void onClose(@PathParam(value = "userId") String userId, Session session) {
String message = "[" + userId + "]退出了聊天室...";
// 删除映射关系
WebsocketUtil.removeSession(userId);
// 广播通知,用户下线了
WebsocketUtil.sendMessageForAll(message);
}
/**
* 当接收到用户上传的消息
*
* @param userId
* @param session
*/
@OnMessage
public void onMessage(@PathParam(value = "userId") String userId, Session session, String message) {
String msg = "[" + userId + "]:" + message;
System.out.println("接收到信息:" + msg);
// 直接广播
WebsocketUtil.sendMessageForAll(msg);
}
/**
* 处理用户活连接异常
*
* @param session
* @param throwable
*/
@OnError
public void onError(Session session, Throwable throwable) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
throwable.printStackTrace();
}
}服务器端单个用户推送消息
走正常http方式,请求之后就会把信息发到对应房间id里
扫码就是用这种方式,虽然websocket支持双向通信,用户也可以send信息到服务器里,但是因为扫码用的设备是手机,而展示二维码与监听是否登陆成功的是在网页里,是分开的,所以手机那边需要走http,如果是聊天室那种功能,那直接调用send方法就能够把信息发送给服务器。
@PostMapping("/send")
public void send(@RequestParam("id") String id,@RequestParam("message")String message) {
Session session = ONLINE_SESSION.get(id);
WebsocketUtil.sendMessage(session,message);
System.out.println("发送成功");
}https://juejin.cn/post/7372591578755940367?searchId=202408290947537954A9E9F373E8C4D0BC
前端使用
const socket = new WebSocket('ws://localhost:8888/chat/1');
socket.onopen = (event) => {
console.log('WebSocket connection opened:', event);
};
//接收后端消息
socket.onmessage = (event) => {
const data = event.data;
};
socket.onclose = (event) => {
console.log('WebSocket connection closed:', event);
};
function sendMessage(message) {
//发送消息到后端
socket.send(message);
}WebSocket心跳重连机制详解
一、WebSocket的"不稳定性"
想象你和朋友打电话,说着说着信号断了,但双方都不知道(没有提示)。这时你继续说,对方却听不见,造成沟通失效。WebSocket也有类似问题:网络波动时连接可能悄悄断开,但客户端和服务器都不知道。
二、什么是心跳包?
心跳包就是定时发送的"小问候",用来确认双方还活着,连接还在。
比如:
- 客户端每5秒发一个消息:"我还在哦~"(这就是心跳包)
- 服务器收到后回复:"我也在~"
- 如果客户端连续3次没收到回复,就认为连接断了
心跳包的作用:
- 检测连接是否存活
- 防止因长时间不通信被防火墙切断
- 像"体检"一样定期检查连接状态
三、如何实现心跳重连?
以聊天软件为例,实现步骤如下:
1. 建立WebSocket连接
let ws = new WebSocket('ws://example.com');2. 启动心跳定时器
let heartBeatTimer;
let heartBeatInterval = 5000; // 5秒发一次心跳
function startHeartBeat() {
heartBeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send('ping'); // 发送心跳包
}
}, heartBeatInterval);
}3. 处理服务器响应
ws.onmessage = function(event) {
if (event.data === 'pong') {
// 收到服务器的心跳响应,说明连接正常
console.log('心跳响应正常');
} else {
// 处理正常消息
}
};4. 实现重连机制
let reconnectTimer;
let reconnectInterval = 3000; // 3秒尝试重连一次
let maxReconnectTimes = 10; // 最多重连10次
let currentReconnectTimes = 0;
function reconnect() {
if (currentReconnectTimes >= maxReconnectTimes) {
console.log('重连次数过多,放弃');
return;
}
currentReconnectTimes++;
console.log(`尝试第${currentReconnectTimes}次重连...`);
ws = new WebSocket('ws://example.com');
ws.onopen = function() {
console.log('重连成功');
currentReconnectTimes = 0;
startHeartBeat(); // 重连成功后重启心跳
};
ws.onclose = function() {
console.log('连接又断开了,准备再次重连');
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(reconnect, reconnectInterval);
};
}
// 监听连接关闭事件,触发重连
ws.onclose = function() {
console.log('连接已关闭,开始重连');
clearInterval(heartBeatTimer); // 停止心跳
reconnect();
};四、心跳重连的关键参数
心跳间隔:多久发一次心跳包(推荐5-30秒)
- 间隔太短:浪费流量
- 间隔太长:不能及时发现断开
超时时间:多久没收到响应算超时(推荐是心跳间隔的2-3倍)
- 比如心跳间隔5秒,超时设为15秒
重连策略:
- 固定间隔:每次等3秒重连
- 指数退避:每次等待时间翻倍(3秒→6秒→12秒)
五、常见问题与解决方案
1. 心跳包被防火墙拦截
解决方案:
- 让服务器主动发心跳(反向心跳)
- 调整心跳间隔,避免触发防火墙规则
2. 频繁重连导致性能问题
解决方案:
- 实现指数退避策略(等待时间递增)
- 设置最大重连次数,避免无限重连
3. 重连后状态丢失
解决方案:
- 重连成功后,重新发送未完成的请求
- 使用本地缓存(如localStorage)保存关键状态
六、完整代码示例
class WebSocketClient {
constructor(url) {
this.url = url;
this.ws = null;
this.heartBeatTimer = null;
this.reconnectTimer = null;
this.heartBeatInterval = 5000;
this.reconnectInterval = 3000;
this.maxReconnectTimes = 10;
this.currentReconnectTimes = 0;
this.isConnecting = false;
this.connect();
}
connect() {
if (this.isConnecting) return;
this.isConnecting = true;
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('WebSocket连接已建立');
this.isConnecting = false;
this.currentReconnectTimes = 0;
this.startHeartBeat();
};
this.ws.onmessage = (event) => {
if (event.data === 'pong') {
console.log('收到心跳响应');
} else {
this.onMessage(event.data);
}
};
this.ws.onclose = () => {
console.log('WebSocket连接已关闭');
this.stopHeartBeat();
if (!this.isConnecting) {
this.scheduleReconnect();
}
};
this.ws.onerror = (error) => {
console.error('WebSocket发生错误:', error);
this.ws.close();
};
}
startHeartBeat() {
this.stopHeartBeat();
this.heartBeatTimer = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send('ping');
}
}, this.heartBeatInterval);
}
stopHeartBeat() {
if (this.heartBeatTimer) {
clearInterval(this.heartBeatTimer);
this.heartBeatTimer = null;
}
}
scheduleReconnect() {
if (this.currentReconnectTimes >= this.maxReconnectTimes) {
console.log('已达到最大重连次数,停止重连');
return;
}
this.currentReconnectTimes++;
console.log(`准备第${this.currentReconnectTimes}次重连,${this.reconnectInterval/1000}秒后...`);
this.reconnectTimer = setTimeout(() => {
this.connect();
}, this.reconnectInterval);
}
send(message) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(message);
} else {
console.log('WebSocket未连接,消息发送失败');
// 可以选择缓存消息,重连后发送
}
}
onMessage(message) {
// 子类可以重写此方法处理消息
console.log('收到消息:', message);
}
close() {
this.stopHeartBeat();
clearTimeout(this.reconnectTimer);
this.isConnecting = false;
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
}
// 使用示例
const client = new WebSocketClient('ws://example.com');
client.onMessage = (message) => {
console.log('处理业务消息:', message);
};
client.send('Hello, server!');七、总结
心跳重连机制就像是:
- 两个人约定每隔5分钟说一句"我还在"
- 如果连续3次没收到回应,就认为对方失联了
- 尝试打电话重新联系,一次打不通就等一会儿再打
- 如果打了10次都不通,就暂时放弃
这种机制大大提高了WebSocket通信的稳定性,特别适合实时聊天、监控系统、在线游戏等需要长期保持连接的场景。