【问题标题】:Spring Boot WebSocket: How do I know when a Client has Unsubscribed?Spring Boot WebSocket:我如何知道客户端何时取消订阅?
【发布时间】:2019-07-23 13:44:57
【问题描述】:

我目前有一个带有 STOMP 设置的简单 WebSocket,其中客户端连接到一个主题(带有一个 ID)。控制器立即响应所要求的内容,并设置一个变量来指示客户端订阅的内容。一个带有@Scheduled 注释的方法现在每隔几秒就会向客户端发送它所请求的内容。

在客户端第一次连接之前,调度的方法不会做任何事情。但是,在第一次订阅后,无论客户端是否订阅,它都会继续发布。

@Controller
public class ServiceWebSocketController {

    @Autowired
    private ServiceService serviceService;

    @Autowired
    WebSocketSessionController webSocketSessionController;

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    private Set<Long> services = new HashSet<>();

    @SubscribeMapping("/service/{serviceId}")
    public ServiceDTO subscribe(@DestinationVariable("serviceId") final Long serviceId) throws SQLException {
        System.out.println("Subscribed to Service with ID: " + serviceId);
        services.add(serviceId);
        return serviceService.getServiceWithProperties(serviceId).orElseThrow(() -> new ResourceNotFoundException("Service", "id", serviceId));
    }

    @Scheduled(fixedDelay = 2000)
    public void service() throws SQLException {
        services.removeIf(serviceId -> !webSocketSessionController.hasSubscriptionTo("/topic/service/" + serviceId));

        // Publish specified Service data to each anonymously subscribed client.
        services.forEach(serviceId -> {
            try {
                System.out.println("Publishing Service with ID: " + serviceId);
                // We don't use .convertAndSendToUser here, because all our clients are anonymous.
                simpMessagingTemplate.convertAndSend("/topic/service/" + serviceId, serviceService.getServiceWithProperties(serviceId));
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
    }
}

如何判断客户是否退订?如果存在类似于@UnsubscribeMapping 的东西,我可以简单地将currentSubscriptionServiceId 变量再次设置为null,从而阻止预定方法连续发布数据。

【问题讨论】:

  • 我对 STOMP 支持不够熟悉,无法告诉您正确的方法,但我可以说您正在存储 全局状态(ID)的事实共享控制器内部表明存在整体设计问题。
  • @chrylis 你是完全正确的。我是一个新手程序员,刚开始使用 Spring/WebSockets,所以我还在学习。我最终使用ConcurrentHashMap&lt;String, Long&gt; 来存储哪些客户订阅了哪些服务。使用下面@Bertrand Pestre 的答案,我能够实现我想要的:)
  • 很高兴有帮助。请注意,如果您不清除它,它将存储状态 forever,因此在生产系统中,您通常会执行一些操作,例如将该信息存储在 Redis 或类似的键值后端中过期记录。
  • @chrylis 我更新了我的答案以反映您的建议。我监视连接/断开连接/订阅/取消订阅(使用ApplicationListeners,正如下面 Bertrand Pestre 的回答所建议的那样)以确定哪些客户端已连接并订阅了特定服务。如果客户取消订阅/断开连接,我会删除他们的订阅并停止发布数据。

标签: javascript java spring-boot websocket stomp


【解决方案1】:

你可以像这样收听SessionUnsubscribeEvent的事件:

@Controller
public class SessionUnsubscribeListener implements ApplicationListener<SessionUnsubscribeEvent> {

   @Override
   public void onApplicationEvent(SessionUnsubscribeEvent event) {
       GenericMessage message = (GenericMessage) event.getMessage();

       String simpDestination = (String) message.getHeaders().get("simpDestination");

       if ("/topic/service".equals(simpDestination)) {
           // do stuff
       }
   }
}

【讨论】:

  • 这正是我想要的!非常感谢:)
  • 不幸的是,当发生SessionUnsubscribeEvent 事件时,message.getHeaders().get("simpDestination")` 会返回null
  • @AnonymousAngelo 好吧,至少听者在工作!关于标题,可能数据结构有点不同。您可以在调试中运行它并尝试查看 event 对象以找到您想要的主题
  • 我最终选择了ConcurrentMap&lt;String, String&gt;,我称之为subscriptions。每当客户订阅发布时,我都会存储subscriptionIdsimpDestination。这样,当客户端取消订阅时,我只需删除基于 subscriptionId 的映射条目(您确实从 SessionUnsubscribeEvent 获得)。
猜你喜欢
  • 2021-01-16
  • 2019-06-17
  • 1970-01-01
  • 2023-01-31
  • 2019-10-10
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 2014-11-18
相关资源
最近更新 更多