Flow of Messages

一旦 STOMP 端点被暴露,Spring 应用程序就成为连接客户端的 STOMP 代理。本节描述了服务器端的消息流。

spring-messaging 模块包含了对消息传递应用的基础支持,这些支持起源于 Spring Integration,后来被提取并整合到 Spring 框架中,以便在许多Spring 项目 和应用场景中更广泛地使用。下面的列表简要地描述了一些可用的消息传递抽象:

Java 配置(即 @EnableWebSocketMessageBroker)和 XML 命名空间配置(即 <websocket:message-broker>)都使用前面的组件来组装一个消息工作流。下图显示了启用简单内置消息代理时使用的组件:
信息的流动 - 图1
前面的图显示了三个消息通道(channel):

  • clientInboundChannel:用于传递从 WebSocket 客户端收到的消息。
  • clientOutboundChannel:用于向 WebSocket 客户端发送服务器消息。
  • brokerChannel:用于从服务器端的应用程序代码中向消息代理发送消息。

下图显示了当外部代理(如 RabbitMQ)被配置为管理订阅和广播消息时使用的组件。

信息的流动 - 图2
前面两张图的主要区别是使用 「代理中继」(右下角黄色的哪一个组件),通过 TCP 将消息向上传递给外部 STOMP 代理,并从代理向下传递消息给订阅的客户端。

当收到来自 WebSocket 连接的消息时,它们被解码为 STOMP 帧,变成 Spring 消息表示,并被发送到 clientInboundChannel 进行进一步处理。例如,目的地标题以 /app开头的 STOMP 消息可以被路由到被 @MessageMapping注解控制器中的方法,而 /topic/queue消息可以直接被路由到消息代理。

一个处理来自客户端的 STOMP 消息的注解的 @Controller可以通过 brokerChannel 向消息代理发送消息,而代理则通过clientOutboundChannel 将消息广播给匹配的订阅者。同样的控制器也可以在响应 HTTP 请求时做同样的事情,所以客户端可以执行 HTTP POST,然后一个 @PostMapping方法可以发送消息给消息代理,以广播给订阅的客户端。

我们可以通过一个简单的例子来追踪这个流程。考虑一下下面的例子,它设置了一个服务器:

  1. @Configuration
  2. @EnableWebSocketMessageBroker
  3. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  4. @Override
  5. public void registerStompEndpoints(StompEndpointRegistry registry) {
  6. registry.addEndpoint("/portfolio");
  7. }
  8. @Override
  9. public void configureMessageBroker(MessageBrokerRegistry registry) {
  10. registry.setApplicationDestinationPrefixes("/app");
  11. registry.enableSimpleBroker("/topic");
  12. }
  13. }
  14. @Controller
  15. public class GreetingController {
  16. @MessageMapping("/greeting")
  17. public String handle(String greeting) {
  18. return "[" + getTimestamp() + ": " + greeting;
  19. }
  20. }

前面的例子支持以下流程:

  1. 客户端连接到 http://localhost:8080/portfolio,一旦建立 WebSocket 连接,STOMP 帧就开始在其上流动。

  2. 客户端发送一个 SUBSCRIBE 帧,目标头为 /topic/greeting。一旦收到并解码,该消息将被发送到 clientInboundChannel,然后被路由到消息代理,该代理存储客户的订阅。

  3. 客户端向 /app/greeting发送一个 SEND 帧。/app前缀有助于将其路由到有注解的控制器。在 /app前缀被剥离后,剩余的 /greeting部分的目的地被映射到 GreetingController 的 @MessageMapping方法。

  4. 从 GreetingController 返回的值被转化为 Spring 消息,其有效载荷基于返回值和默认的目的地头 /topic/greeting(从输入目的地派生出来的,/app/topic替代)。产生的消息被发送到 brokerChannel并由消息代理处理。

  5. 消息代理找到所有匹配的订阅者,并通过 clientOutboundChannel向每个订阅者发送一个 MESSAGE 帧,从那里消息被编码为 STOMP 帧并在 WebSocket 连接上发送。

下一节将提供关于注解方法的更多细节,包括支持的参数和返回值的种类。

一个例子

前面的例子,这里来演示下上面描述的流程,关于 stomp js 的 api 可以参考它的文档

由于前面的例子服务端已经搭建好了,只需要增加一个 controller 就可以了

  1. package cn.mrcode.study.springdocsread.websocket;
  2. import org.springframework.messaging.handler.annotation.MessageMapping;
  3. import org.springframework.stereotype.Controller;
  4. /**
  5. * @author mrcode
  6. */
  7. @Controller
  8. public class StompController {
  9. // 接收 send 消息:通过 /app/greeting 发送的消息
  10. @MessageMapping("/greeting")
  11. public String handle(String greeting) {
  12. return "[" + getTimestamp() + ": " + greeting;
  13. }
  14. private String getTimestamp() {
  15. return System.currentTimeMillis() + "";
  16. }
  17. }

html 订阅消息,并且发送消息给服务端

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>STOMP</title>
  6. </head>
  7. <body>
  8. </body>
  9. <script type="text/javascript" src="/node_modules/webstomp-client/dist/webstomp.min.js"></script>
  10. <script type="text/javascript"
  11. src="https://cdnjs.loli.net/ajax/libs/sockjs-client/1.6.0/sockjs.js"></script>
  12. <script>
  13. // 这里使用 sockJs 库链接
  14. var socket = new SockJS("http://localhost:8080/portfolio");
  15. // 文章上说可以使用 WebSocket 链接。 实际上我这里测试不可以,会报错
  16. // var socket = new WebSocket("ws://localhost:8080/portfolio");
  17. var stompClient = webstomp.over(socket);
  18. // 链接上 服务器时
  19. stompClient.connect({}, function (frame) {
  20. console.log(frame)
  21. // 订阅消息
  22. stompClient.subscribe("/topic/greeting", msg => {
  23. console.log("收到消息:" + msg)
  24. })
  25. // 链接上服务器时,像服务器发送一个消息
  26. stompClient.send("/app/greeting", "我的第一个消息")
  27. })
  28. </script>
  29. </html>

访问页面后,控制台的信息如下
image.png
订阅接收到的消息对象如下图所示
image.png