使用Spring和sockJS消息丢失websocket

时间:2015-03-22 12:45:21

标签: spring websocket stomp sockjs spring-websocket

在客户端javascript我有

    stomp.subscribe("/topic/path", function (message) {
        console.info("message received");
    });

在服务器端

public class Controller {
  private final MessageSendingOperations<String> messagingTemplate;
  @Autowired
  public Controller(MessageSendingOperations<String> messagingTemplate) {
      this.messagingTemplate = messagingTemplate;
  }
  @SubscribeMapping("/topic/path")
  public void subscribe() {
     LOGGER.info("before send");
     messagingTemplate.convertAndSend(/topic/path, "msg");
  }
}

从这个设置中,偶尔(大约30次刷新一次)我会遇到消息丢失,这意味着我既看不到&#34;收到消息&#34;客户端的消息,Chrome调试工具的websocket流量。

&#34;发送之前&#34;始终记录在服务器端。

当我在subscribe()方法中调用它时,看起来MessageSendingOperations尚未就绪。 (如果我把Thread.sleep(50);在调用messagingTemplate.convertAndSend之前,问题就会消失(或者不太可能被复制)。

我想知道是否有人经历过同样的事情,如果有事件可以告诉我MessageSendingOperations是否准备就绪。

3 个答案:

答案 0 :(得分:4)

您面临的问题是clientInboundChannel的性质,默认为ExecutorSubscribableChannel

它有3个subscribers

0 = {SimpleBrokerMessageHandler@5276} "SimpleBroker[DefaultSubscriptionRegistry[cache[0 destination(s)], registry[0 sessions]]]"
1 = {UserDestinationMessageHandler@5277} "UserDestinationMessageHandler[DefaultUserDestinationResolver[prefix=/user/]]"
2 = {SimpAnnotationMethodMessageHandler@5278} "SimpAnnotationMethodMessageHandler[prefixes=[/app/]]"

taskExecutor内调用,因此是异步的。

此处的第一个(SimpleBrokerMessageHandler(或StompBrokerRelayMessageHandler),如果您使用broker-relay),则有责任为subscription注册topic

您的messagingTemplate.convertAndSend(/topic/path, "msg")操作可能在该WebSocket会话的订阅注册之前执行,因为它们是在单独的线程中执行的。因此,经纪人处理程序并不知道您将消息发送到会话。

可以使用@SubscribeMapping在方法上配置return,其中此方法的结果将作为对客户端上subscription函数的回复发送。

HTH

答案 1 :(得分:1)

这是我的解决方案。它是沿着相同的路线。添加了ExecutorChannelInterceptor并发布了自定义SubscriptionSubscribedEvent。关键是要在AbstractBrokerMessageHandler处理完消息后发布事件,这意味着订阅已在代理中注册。

@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.interceptors(new ExecutorChannelInterceptorAdapter() {

        @Override
        public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
            SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message);
            if (accessor.getMessageType() == SimpMessageType.SUBSCRIBE && handler instanceof AbstractBrokerMessageHandler) {
                /*
                 * Publish a new session subscribed event AFTER the client
                 * has been subscribed to the broker. Before spring was
                 * publishing the event after receiving the message but not
                 * necessarily after the subscription occurred. There was a
                 * race condition because the subscription was being done on
                 * a separate thread.
                 */
                applicationEventPublisher.publishEvent(new SessionSubscribedEvent(this, message));
            }
        }
    });

}

答案 2 :(得分:0)

有点晚了,但我想我会添加我的解决方案。在通过消息传递模板发送数据之前,我遇到了相同的订阅问题。由于与DefaultSubscriptionRegistry竞争,这个问题很少发生且不可预测。

不幸的是,我不能只使用@SubscriptionMapping的return方法,因为我们使用的是根据用户类型动态更改的自定义对象映射器(本质上是属性过滤)。

我搜索了Spring代码,发现SubscriptionMethodReturnValueHandler负责发送订阅映射的返回值,并且与我的异步控制器的自动装配SimpMessagingTemplate有一个不同的messagingTemplate !!

因此解决方案是将MessageChannel clientOutboundChannel自动装入我的异步控制器并使用它来创建SimpMessagingTemplate。 (你不能直接连接它,因为你只是让模板进入经纪人。)

在订阅方法中,我使用直接模板,而在其他方法中,我使用了转发到代理的模板。