在SpringBoot中实现WebSocket会话管理的方案

 更新时间:2023年11月24日 08:39:46   作者:一只爱撸猫的程序猿  
在构建实时通信应用时,WebSocket 无疑是一个强大的工具,SpringBoot提供了对WebSocket的支持,本文旨在探讨如何在 Spring Boot 应用中实现 WebSocket 会话管理,我们将通过一个模拟的场景一步步展开讨论,需要的朋友可以参考下

场景设定

假设我们正在开发一个在线聊天应用,该应用需要实现以下功能:

  • 用户可以通过 WebSocket 实时发送和接收消息。
  • 系统需要跟踪用户的会话状态,以便在用户重新连接时恢复状态。
  • 为了提高效率和安全性,我们需要监控空闲连接并及时关闭它们。

基于这个场景,我们将探讨四种实现 WebSocket 会话管理的策略:

1. 使用现有的会话标识符

一种常见的做法是利用 HTTP 会话(例如,通过 cookies)来管理 WebSocket 会话。

实现方法

  • 在 WebSocket 握手阶段,从 HTTP 请求中提取会话标识符。
  • 将 WebSocket 会话与提取的会话标识符关联。
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession();
            attributes.put("HTTP_SESSION_ID", session.getId());
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
}

这个拦截器需要在 WebSocket 的配置类中注册。例如,在 WebSocketConfig 类中,你可以这样注册拦截器:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws")
                .addInterceptors(new MyHandshakeInterceptor())
                .setAllowedOrigins("*");
        // 你也可以添加 .withSockJS() 如果你需要SockJS支持
    }

    // ...其他配置...
}

2. 自定义协议消息

另一种方法是在 WebSocket 连接中定义自己的消息格式,包含会话管理信息。

实现方法

  • 定义消息格式(如 JSON),包含会话信息。
  • 在连接建立后,通过 WebSocket 发送和接收这些自定义消息。
@Controller
public class WebSocketController {
    
    @Autowired
    private WebSocketSessionManager sessionManager;

    @MessageMapping("/sendMessage")
    public void handleSendMessage(ChatMessage message, SimpMessageHeaderAccessor headerAccessor) {
        String sessionId = (String) headerAccessor.getSessionAttributes().get("HTTP_SESSION_ID");
        // 使用 sessionId 处理消息
        // 可以通过 sessionManager 获取用户信息
    }

    // ...其他消息处理方法...
}

3. 连接映射

将每个 WebSocket 连接映射到特定的用户会话。

实现方法

  • 在连接建立时,从 WebSocket 握手信息中获取用户身份。
  • 维护一个映射,关联 WebSocket 会话 ID 和用户会话。
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler; 
import java.util.Iterator; 
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

@Component
public class WebSocketSessionManager extends TextWebSocketHandler {

    @Autowired
    private WebSocketHandler webSocketHandler;
    
    private Map<String, String> sessionMap = new ConcurrentHashMap<>();
    private Map<String, Long> lastActiveTimeMap = new ConcurrentHashMap<>();

    public void registerSession(String websocketSessionId, String userSessionId) {
        sessionMap.put(websocketSessionId, userSessionId);
        lastActiveTimeMap.put(websocketSessionId, System.currentTimeMillis());
    }

    public String getUserSessionId(String websocketSessionId) {
        return sessionMap.get(websocketSessionId);
    }

    public void updateLastActiveTime(String websocketSessionId) {
        lastActiveTimeMap.put(websocketSessionId, System.currentTimeMillis());
    }

    public Long getLastActiveTime(String websocketSessionId) {
        return lastActiveTimeMap.get(websocketSessionId);
    }

    public void checkAndCloseInactiveSessions(long timeout) {
        long currentTime = System.currentTimeMillis();
        lastActiveTimeMap.entrySet().removeIf(entry -> {
            String sessionId = entry.getKey();
            long lastActiveTime = entry.getValue();

            if (currentTime - lastActiveTime > timeout) {
                closeSession(sessionId);  // 关闭会话
                sessionMap.remove(sessionId);  // 从用户会话映射中移除
                return true;  // 从活跃时间映射中移除
            }
            return false;
        });
    }

    private void closeSession(String websocketSessionId) {
        // 逻辑来关闭 WebSocket 会话
        // 可能需要与 webSocketHandler 交互
    }
    
    public void unregisterSession(String websocketSessionId) {
        sessionMap.remove(websocketSessionId);
    }
    // 可以添加注销会话的方法等
}

4. 心跳和超时机制

实现心跳消息和超时机制,以管理会话的生命周期。

实现方法

  • 客户端定时发送心跳消息。
  • 服务端监听这些消息,并实现超时逻辑。
function sendHeartbeat() {
    if (stompClient && stompClient.connected) {
        stompClient.send("/app/heartbeat", {}, JSON.stringify({ timestamp: new Date() }));
    }
}
setInterval(sendHeartbeat, 10000); // 每10秒发送一次心跳
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;

@Controller
public class HeartbeatController {

    @Autowired
    private WebSocketSessionManager sessionManager;

    @MessageMapping("/heartbeat")
    public void handleHeartbeat(HeartbeatMessage message, SimpMessageHeaderAccessor headerAccessor) {
        String websocketSessionId = headerAccessor.getSessionId();
        sessionManager.updateLastActiveTime(websocketSessionId);
        // 根据需要处理其他逻辑
    }
}

使用 Spring 的定时任务功能来定期执行会话超时检查,ScheduledTasks 类中的 checkInactiveWebSocketSessions 方法每5秒执行一次,调用 WebSocketSessionManagercheckAndCloseInactiveSessions 方法来检查和关闭超时的会话。

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class ScheduledTasks {

    @Autowired
    private WebSocketSessionManager sessionManager;

    // 定义超时阈值,例如30分钟
    private static final long TIMEOUT_THRESHOLD = 30 * 60 * 1000;

    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    public void checkInactiveWebSocketSessions() {
        sessionManager.checkAndCloseInactiveSessions(TIMEOUT_THRESHOLD);
    }
}

补充:在 WebSocket 连接关闭或用户注销时,可以调用 unregisterSession 方法来清理会话信息。当 WebSocket 连接关闭时,afterConnectionClosed 方法会被调用,这时我们可以通过 sessionManager 移除对应的会话信息。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyWebSocketHandler extends TextWebSocketHandler {

    @Autowired
    private WebSocketSessionManager sessionManager;

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String websocketSessionId = session.getId();
        sessionManager.unregisterSession(websocketSessionId);
        // 进行其他清理工作
    }

    // 实现其他必要的方法
}

总结

实现 WebSocket 会话管理需要综合考虑应用的需求和架构特点。Spring Boot 提供了实现这些功能的强大支持,但正确地应用这些工具和策略是成功的关键。通过本文的讨论,我们看到了如何在一个实际场景中一步步地思考和实现有效的 WebSocket 会话管理。

以上就是在SpringBoot中实现WebSocket会话管理的方案的详细内容,更多关于SpringBoot实现WebSocket会话的资料请关注脚本之家其它相关文章!

相关文章

  • Linux下mysql 8.0.25 安装配置方法图文教程

    Linux下mysql 8.0.25 安装配置方法图文教程

    这篇文章主要为大家详细介绍了Linux下mysql 8.0.25 安装配置方法图文教程,文中安装步骤介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • MySQL数据库基础篇SQL窗口函数示例解析教程

    MySQL数据库基础篇SQL窗口函数示例解析教程

    这篇文章主要为大家介绍了MySQL数据库基础篇之窗口函数示例解析教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-10-10
  • 深入了解MySQL中的慢查询

    深入了解MySQL中的慢查询

    什么是MySQL慢查询呢?其实就是查询的SQL语句耗费较长的时间。具体耗费多久算慢查询呢?这其实因人而异,有些公司慢查询的阈值是100ms,有些的阈值可能是500ms。本文将通过示例和大家聊聊慢查询的危害和常用场景,感兴趣的可以了解一下
    2022-10-10
  • MySQL中IO问题的深入分析与优化

    MySQL中IO问题的深入分析与优化

    据库作为存储系统,所有业务访问数据的操作都会转化为底层数据库系统的IO行为,下面这篇文章主要给大家介绍了关于MySQL中IO问题的深入分析与优化的相关资料,需要的朋友可以参考下
    2022-04-04
  • MySQL存储过程及语法详解

    MySQL存储过程及语法详解

    这篇文章主要介绍了MySQL存储过程及语法详解,存储过程,也叫做存储程序,是一条或者多条SQL语句的集合,可以视为批量处理,但是其作用不仅仅局限于批量处理
    2022-08-08
  • Windows下MySql错误代码1045的解决方法

    Windows下MySql错误代码1045的解决方法

    这篇文章主要介绍了Windows下MySql错误代码1045的解决方法,文中还包含了2个Linux下的解决方法,需要的朋友可以参考下
    2014-06-06
  • mysql使用xtrbackup+relaylog增量恢复注意事项

    mysql使用xtrbackup+relaylog增量恢复注意事项

    这篇文章主要介绍了mysql使用xtrbackup+relaylog增量恢复,本次实验mysql5.7.19.使用了GTID,row格式的binlog,本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • Mysql存储过程学习笔记--建立简单的存储过程

    Mysql存储过程学习笔记--建立简单的存储过程

    我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行它。
    2014-08-08
  • Mysql允许外网访问设置步骤

    Mysql允许外网访问设置步骤

    本文给大家分享Mysql允许外网访问设置步骤,通过简单的五步操作就可以实现mysql允许外围访问功能,非常不错,需要的朋友参考下吧
    2016-10-10
  • 简述MySQL 正则表达式

    简述MySQL 正则表达式

    大家都知道MySQL可以通过 LIKE ...% 来进行模糊匹配,MySQL 同样也支持其他正则表达式的匹配, MySQL中使用 REGEXP 操作符来进行正则表达式匹配。对mysql正则表达式知识感兴趣的朋友一起看看吧
    2016-11-11

最新评论