【问题标题】:Spring STOMP over WebSockets not scheduling heartbeatsSpring STOMP over WebSockets 不调度心跳
【发布时间】:2017-01-06 08:01:48
【问题描述】:

我们有一个 Spring over WebSockets 连接,我们正在传递一个 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 调试时,我看到了“正在调度心跳...”或类似的通知...虽然我现在没有看到?

知道发生了什么吗?

谢谢

我在documentation找到了解释:

来自 SockJS 任务线程池的 SockJS 任务调度器统计信息 用于发送心跳的调度程序。 注意,当心跳 在 STOMP 级别进行协商,SockJS 心跳被禁用。

SockJS 的心跳与 STOMP 的心跳有什么不同?

【问题讨论】:

    标签: stomp spring-websocket


    【解决方案1】:

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

    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();
        }
    }
    

    【讨论】:

    • 太棒了,这是我在其他任何地方都找不到的东西!谢谢大佬!
    • 很高兴它帮助了你!
    【解决方案2】:

    是的,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(...);
        }
    
    }
    

    【讨论】:

    • 您是否打算,@Rossen,写.enableStompBrokerRelay(...),即使您“猜测您正在使用内置的简单代理”?好像应该是.enableSimpleBroker(...)吧?
    • 另外,如果您没有检测到您的心跳,请检查客户端。客户端需要发送一个非零的写入心跳值以便 Spring 考虑它(参见 SimpleBrokerMessageHandler.SessionInfo 构造函数)
    【解决方案3】:

    我们在 Spring、Websockets、STOMP 和 Spring Sessions 上遇到了同样的问题 - 没有心跳和 Spring 会话可能会过期,而 websocket 不会在服务器端接收消息。我们最终每 20000 毫秒从浏览器启用 STOMP 心跳,并将 SimpMessageType.HEARTBEAT 添加到 Spring sessionRepositoryInterceptor 匹配项,以保持 Spring 会话最后访问时间在没有消息的 STOMP 心跳上更新。我们必须使用 AbstractSessionWebSocketMessageBrokerConfigurer 作为基础来启用内置 Spring 会话和 websocket 会话绑定。 Spring manual示例。在官方示例中,Spring 会话在入站 websocket CONNECT/MESSAGE/SUBSCRIBE/UNSUBSCRIBE 消息上更新,但不是心跳,这就是为什么我们需要重新配置 2 件事 - 至少启用 inbound 心跳并调整 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 功能,以更新 Spring 会话在 出站 websocket 消息上的最后访问时间,并通过维护 websocket session->Spring 会话映射听众,但上面的代码成功了。

    【讨论】:

      猜你喜欢
      • 2014-03-17
      • 2016-07-26
      • 2016-01-28
      • 2016-06-28
      • 1970-01-01
      • 2017-02-16
      • 1970-01-01
      • 1970-01-01
      • 2018-03-30
      相关资源
      最近更新 更多