Spring STOMP over WebSockets 不安排心跳
Spring STOMP over WebSockets not scheduling heartbeats
我们有一个 Spring WebSockets 连接,我们正在传递一个 CONNECT
帧:
CONNECT\naccept-version:1.2\nheart-beat:10000,10000\n\n\u0000
处理程序确认后,开始一个新的 session,然后 returns:
CONNECTED
version:1.2
heart-beat:0,0
但是,我们需要 heart-beats 以便我们可以保持 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
(原生header)后,它设置了一个内存地址simpHeartbeat=[J@5eba717, simpSessionId=46e855c9}]
值得注意的是,在代理验证后:
Processing CONNECT session=46e855c9
(这里的sessionId和simpSessionId不一样)?
当 运行 早些时候 TRACE
调试时,我看到了一个通知 "Scheduling heartbeat..." 或类似的东西......虽然我现在没有看到它?
知道发生了什么事吗?
谢谢
我在documentation中找到了解释:
SockJS Task Scheduler stats from thread pool of the SockJS task
scheduler which is used to send heartbeats. Note that when heartbeats
are negotiated on the STOMP level the SockJS heartbeats are disabled.
SockJS 心跳与 STOMP heart-beats 不同吗?
是的,SockJS 心跳是不同的。本质上是一样的,但它们在 SockJS 协议中的目的是确保连接看起来不像 "dead",在这种情况下代理可以主动关闭它。更一般地说,心跳允许每一方主动检测连接问题并清理资源。
当在传输层使用 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(...);
}
}
从 Spring 4.2 开始,您可以从服务器端完全控制心跳协商结果,使用带有 built-in 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();
}
}
我们在 Spring、Websockets、STOMP 和 Spring 会话中遇到了同样的问题 - 没有心跳和 Spring 会话可能会过期,而 websocket 不会在服务器端接收消息。我们最终每 20000 毫秒从浏览器启用 STOMP 心跳并将 SimpMessageType.HEARTBEAT 添加到 Spring sessionRepositoryInterceptor 匹配以保持 Spring 会话上次访问时间在没有消息的 STOMP 心跳上更新。我们必须使用 AbstractSessionWebSocketMessageBrokerConfigurer 作为启用 in-build Spring 会话和 websocket 会话绑定的基础。 Spring manual、第二个例子。在官方示例中 Spring 会话在入站 websocket CONNECT/MESSAGE/SUBSCRIBE/UNSUBSCRIBE 消息上更新,但不是心跳,这就是为什么我们需要 re-configure 两件事 - 至少启用 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});
}
}
我们尝试的另一种方法是 re-implementing SessionRepositoryMessageInterceptor 功能更新 Spring 会话在 outbound[=24] 上的最后访问时间=] websocket messages plus maintain websocket session->Spring session map via listeners,但上面的代码成功了。
我们有一个 Spring WebSockets 连接,我们正在传递一个 CONNECT
帧:
CONNECT\naccept-version:1.2\nheart-beat:10000,10000\n\n\u0000
处理程序确认后,开始一个新的 session,然后 returns:
CONNECTED
version:1.2
heart-beat:0,0
但是,我们需要 heart-beats 以便我们可以保持 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
(原生header)后,它设置了一个内存地址simpHeartbeat=[J@5eba717, simpSessionId=46e855c9}]
值得注意的是,在代理验证后:
Processing CONNECT session=46e855c9
(这里的sessionId和simpSessionId不一样)?
当 运行 早些时候 TRACE
调试时,我看到了一个通知 "Scheduling heartbeat..." 或类似的东西......虽然我现在没有看到它?
知道发生了什么事吗?
谢谢
我在documentation中找到了解释:
SockJS Task Scheduler stats from thread pool of the SockJS task scheduler which is used to send heartbeats. Note that when heartbeats are negotiated on the STOMP level the SockJS heartbeats are disabled.
SockJS 心跳与 STOMP heart-beats 不同吗?
是的,SockJS 心跳是不同的。本质上是一样的,但它们在 SockJS 协议中的目的是确保连接看起来不像 "dead",在这种情况下代理可以主动关闭它。更一般地说,心跳允许每一方主动检测连接问题并清理资源。
当在传输层使用 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(...);
}
}
从 Spring 4.2 开始,您可以从服务器端完全控制心跳协商结果,使用带有 built-in 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();
}
}
我们在 Spring、Websockets、STOMP 和 Spring 会话中遇到了同样的问题 - 没有心跳和 Spring 会话可能会过期,而 websocket 不会在服务器端接收消息。我们最终每 20000 毫秒从浏览器启用 STOMP 心跳并将 SimpMessageType.HEARTBEAT 添加到 Spring sessionRepositoryInterceptor 匹配以保持 Spring 会话上次访问时间在没有消息的 STOMP 心跳上更新。我们必须使用 AbstractSessionWebSocketMessageBrokerConfigurer 作为启用 in-build Spring 会话和 websocket 会话绑定的基础。 Spring manual、第二个例子。在官方示例中 Spring 会话在入站 websocket CONNECT/MESSAGE/SUBSCRIBE/UNSUBSCRIBE 消息上更新,但不是心跳,这就是为什么我们需要 re-configure 两件事 - 至少启用 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});
}
}
我们尝试的另一种方法是 re-implementing SessionRepositoryMessageInterceptor 功能更新 Spring 会话在 outbound[=24] 上的最后访问时间=] websocket messages plus maintain websocket session->Spring session map via listeners,但上面的代码成功了。