You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebSocket无法监听SessionDisconnectEvent问题求助

Spring WebSocket 前后端分离场景下无法监听SessionDisconnectEvent

我参考Baeldung的WebSocket Spring项目实现了Demo,编写了WebSocketListener组件监听WebSocket相关事件。在非前后端分离环境中,打开/关闭连接或页面时能正常捕获所有事件,但切换到前后端分离架构后,仅能监听到SessionConnectEvent,SessionDisconnectEvent完全无法触发。

后端Listener代码

@Component
@Slf4j
public class WebSocketListener {

    @EventListener
    public void handleWebSocketConnectListener(SessionConnectEvent sessionConnectEvent) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionConnectEvent.getMessage());
        log.debug("connect, sessionId = {}", headerAccessor.getSessionId());
    }

    @EventListener
    public void handleWebSocketConnectedListener(SessionConnectedEvent sessionConnectedEvent) {
        log.debug("connected ");
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent sessionDisconnectEvent) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(sessionDisconnectEvent.getMessage());
        log.debug("disconnect, session id = {}", sessionDisconnectEvent.getSessionId());
    }

    @EventListener
    public void handleWebSocketSubscribeEvent(SessionSubscribeEvent sessionSubscribeEvent) {
        log.debug("subscribe, {}", sessionSubscribeEvent.toString());
    }

    @EventListener
    public void handleWebSocketUnsubscribeEvent(SessionUnsubscribeEvent sessionUnsubscribeEvent) {
        log.debug("unsubscribe, {}", sessionUnsubscribeEvent.getSource());
    }
}

前端Stomp代码

function chatClient(): CompatClient {
    const socket = SockJS('http://localhost/ws');
    const client = Stomp.over(socket);
    return client;
}
client = chatClient();
client.connect(
    {
        Authorization: getAuthorization(),
    },
    (frame: any) => {
        console.log("ws success", frame);
        client.subscribe(`/user/${userStore.userInfo.username}/messages`, function (message) {
            console.log('msg', message.body);
        });
    }, (err: any) => {
        console.log("ws err");
    });

问题原因

前后端分离场景下,当页面关闭、刷新或客户端异常断开时,浏览器可能直接终止TCP连接,不会主动发送STOMP标准的DISCONNECT帧给服务器。Spring WebSocket只有在收到明确的DISCONNECT帧或者通过心跳检测到连接超时后,才会触发SessionDisconnectEvent。而非前后端分离环境中,同域页面的关闭流程会触发浏览器的正常断开逻辑,服务器能收到断开信号。

解决方案

1. 前端主动发送断开信号

在页面卸载前(比如beforeunload事件),主动调用Stomp客户端的disconnect方法,确保服务器能收到明确的断开指令:

window.addEventListener('beforeunload', () => {
    if (client && client.connected) {
        client.disconnect(() => {
            console.log('主动断开WebSocket连接');
        });
    }
});

2. 配置服务器端心跳检测

通过配置Spring WebSocket的心跳机制,让服务器主动检测失效连接,超时后触发断开事件:

修改WebSocket配置类

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/user");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*") // 替换为实际前端域名更安全
                .withSockJS();
    }

    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
        registration.setSendTimeLimit(10000)
                    .setSendBufferSizeLimit(512 * 1024)
                    .setMessageSizeLimit(512 * 1024);
    }

    // 配置心跳参数
    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        registration.taskExecutor().corePoolSize(4).maxPoolSize(10);
    }
}

添加配置文件参数

在application.properties中添加心跳间隔:

# 服务器发送心跳间隔,单位毫秒
spring.websocket.message-broker.heartbeat.send-interval=30000
# 服务器接收心跳超时时间,单位毫秒
spring.websocket.message-broker.heartbeat.receive-interval=30000

配置后,服务器每30秒向客户端发送心跳包,如果超过30秒未收到客户端响应,就会判定连接失效,触发SessionDisconnectEvent。

3. 检查跨域配置

确保WebSocket端点的跨域配置正确,避免跨域限制导致断开信号无法正常发送:

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("https://your-frontend-domain.com") // 指定前端域名
            .withSockJS();
}

内容的提问来源于stack exchange,提问作者mouren Zhu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:05:25