Spring STOMP通过WebSockets未能调度心跳。

16

我们有一个基于WebSockets的Spring连接,我们正在传递一个CONNECT帧:

CONNECT\naccept-version:1.2\nheart-beat:10000,10000\n\n\u0000

处理程序确认后,会启动一个新会话,然后返回:

CONNECTED
version:1.2
heart-beat:0,0

然而,我们需要这些心跳来保持WebSocket的开启。我们没有使用SockJS。

我逐步浏览了Spring消息处理程序:

StompHeaderAccessor [headers={simpMessageType=CONNECT, stompCommand=CONNECT, nativeHeaders={accept-version=[1.2], heart-beat=[5000,0]}, simpSessionAttributes={}, simpHeartbeat=[J@5eba717, simpSessionId=46e855c9}]

当它收到heart-beat(原生头)后,它设置了一个看起来像内存地址的值 simpHeartbeat=[J@5eba717, simpSessionId=46e855c9}]

需要注意的是,在代理人认证之后:

Processing CONNECT session=46e855c9 (这里的sessionId与simpSessionId不同)?

运行早期的TRACE调试时,我看到了一个通知“计划心跳…”或类似的东西…尽管我现在没有看到它?

有什么想法吗?

谢谢

我在文档中找到了解释:

SockJS任务计划器统计信息来自SockJS任务计划器的线程池,该线程池用于发送心跳。 请注意,当在STOMP级别上协商心跳时,SockJS心跳将被禁用。

SockJS心跳与STOMP心跳不同吗?

3个回答

21

从Spring 4.2开始,您可以在服务器端完全控制使用内置SimpleBroker的Stomp over SockJS的心跳协商结果:

public class WebSocketConfigurer extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        ThreadPoolTaskScheduler te = new ThreadPoolTaskScheduler();
        te.setPoolSize(1);
        te.setThreadNamePrefix("wss-heartbeat-thread-");
        te.initialize();

        config.enableSimpleBroker("/")
                /**
                 * Configure the value for the heartbeat settings. The first number
                 * represents how often the server will write or send a heartbeat.
                 * The second is how often the client should write. 0 means no heartbeats.
                 * <p>By default this is set to "0, 0" unless the {@link #setTaskScheduler
                 * taskScheduler} in which case the default becomes "10000,10000"
                 * (in milliseconds).
                 * @since 4.2
                 */
                .setHeartbeatValue(new long[]{heartbeatServer, heartbeatClient})
                .setTaskScheduler(te);
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint(.....)
                .setAllowedOrigins(....)
                .withSockJS();
    }
}

太棒了,这是我在其他地方找不到的必要内容!谢谢你! - thorinkor
很高兴能帮到你! - artemisian

11

是的,SockJS的心跳与其它方式基本相同,但在SockJS协议中,它们的作用是确保连接看起来不像是“死”的,否则代理可能会主动关闭它。更通用地说,心跳允许双方主动检测到连接问题并清理资源。

在传输层使用STOMP和SockJS时,没有必要同时使用两者,这就是为什么如果使用STOMP心跳,则关闭SockJS心跳。但在这里你没有使用SockJS。

您没有显示任何配置,但我猜测您正在使用内置的简单代理,它不会自动发送心跳。在配置它时,您将看到一个选项来启用心跳,您还需要设置任务计划程序。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
         // ...
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay(...)
                .setTaskScheduler(...)
                .setHeartbeat(...);
    }

}

6
@Rossen,你是不是想写 .enableStompBrokerRelay(...),尽管你猜测你正在使用内置的简单代理?应该改成 .enableSimpleBroker(...),对吧? - Jan Nielsen
此外,如果您没有看到心跳,请检查客户端。客户端需要发送一个非零的写入心跳值,以便Spring考虑它(请参见SimpleBrokerMessageHandler.SessionInfo构造函数)。 - Deathtiny
这是作为"enableSimpleBroker(..)"的附加功能还是替代功能? 不清楚答案是否需要外部代理,或者通过这种方式我们是否可以使用简单代理进行心跳。 - Alessandro Polverini

7
我们遇到了与Spring、Websockets、STOMP和Spring Sessions有关的问题——在服务器端WebSocket没有收到消息时,没有心跳并且Spring会话可能会过期。我们最终使用浏览器每20000毫秒启用STOMP心跳,并将 SimpMessageType.HEARTBEAT 添加到 Spring sessionRepositoryInterceptor 匹配项中,以使 Spring 会话的最后访问时间在 STOMP 心跳(没有消息)时得以更新。我们不得不使用 AbstractSessionWebSocketMessageBrokerConfigurer 来启用内置的 Spring 会话和 WebSocket 会话绑定。请参照 Spring 手册 中的第二个示例。在官方示例中,Spring 会话会在传入的 WebSocket 连接/消息/订阅/取消订阅消息上更新,但不包括心跳。这就是为什么我们需要重新配置两件事情——至少启用传入的心跳并调整 Spring 会话以响应 WebSocket 心跳。
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {

   @Autowired
   SessionRepositoryMessageInterceptor sessionRepositoryInterceptor;

   @Override
   public void configureMessageBroker(MessageBrokerRegistry config) {
       sessionRepositoryInterceptor.setMatchingMessageTypes(EnumSet.of(SimpMessageType.CONNECT,
               SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE,
               SimpMessageType.UNSUBSCRIBE, SimpMessageType.HEARTBEAT));

       config.setApplicationDestinationPrefixes(...);
       config.enableSimpleBroker(...)
             .setTaskScheduler(new DefaultManagedTaskScheduler())
             .setHeartbeatValue(new long[]{0,20000});
   }
}

我们尝试过的另一种方法是重新实现 SessionRepositoryMessageInterceptor 功能,以在 出站 WebSocket 消息上更新 Spring 会话的最后访问时间,并通过监听器维护 WebSocket 会话->Spring 会话映射,但是上面的代码就解决了这个问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接