SpringBoot集成STOMP
参考:
【stomp 实战】Spring websocket使用详解和基本原理_spring stomp websocket-CSDN博客
WebSocket与STOMP通信技术详解_stomp websocket-CSDN博客
上一篇我们详细介绍了WebSocket作为一种全双工通信协议的基本概念、工作原理和实现方式。然而,在企业级应用或复杂系统中,仅仅使用原生WebSocket可能还不足以满足更高级的消息传递需求。这就是STOMP(Simple Text Oriented Messaging Protocol)协议发挥作用的地方。本文将深入探讨STOMP协议的本质、它与WebSocket的结合优势以及实际应用示例
STOMP是什么?
STOMP(Simple Text Oriented Messaging Protocol,简单文本导向消息协议)是一种应用层协议,设计用于在客户端和消息代理(如消息队列、消息中间件)之间进行异步消息传递。它最初是为Apache ActiveMQ设计的,现在已成为多种消息中间件支持的标准协议。
STOMP协议的核心特点:
- 简单易用:基于文本的协议,命令结构类似HTTP
- 跨语言支持:几乎所有主流编程语言都有STOMP客户端库
- 基于帧:所有通信基于帧(frame)进行,包含命令、头信息和消息体
- 发布/订阅模式:支持标准的发布/订阅消息模式
- 目的地导向:消息通过目的地(destination)进行路由
为什么要将STOMP与WebSocket结合使用
WebSocket提供了浏览器和服务器之间的双向通信通道,但它并没有定义应用层的消息格式和路由机制。这就像有了高速公路,但没有交通规则一样。将STOMP与WebSocket结合使用,可以获得以下几个方面的好处:
标准化的消息格式
原生WebSocket没有定义消息的格式,开发人员需要自行设计消息结构,这可能导致不一致和兼容性问题。STOMP提供了标准化的消息格式,包括命令、头信息和消息体,使消息交换更加规范和统一。
消息路由机制
STOMP的目的地(destination)概念允许消息按照特定的主题或队列进行路由,这使得复杂的消息分发变得简单。客户端可以订阅特定的目的地,只接收感兴趣的消息。
消息确认和事务支持
STOMP支持消息确认机制,确保消息被正确处理。同时,它还支持事务,可以将多个操作组合成一个原子单元
与消息中间件的无缝集成
STOMP可以与ActiveMQ、RabbitMQ、Apache Kafka等主流消息中间件无缝集成,使WebSocket应用能够利用这些成熟中间件的强大功能。
错误处理机制
STOMP提供了标准的错误处理机制,当出现问题时,服务器可以发送错误帧给客户端,包含详细的错误信息。
后端相关配置代码
依赖引入
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
这个包实际上引入了两个依赖spring-messaging 和 spring-websocket
stomp相关配置代码
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {// 注册STOMP端点,客户端将使用这个端点连接到STOMP服务器registry.addEndpoint("/upload-websocket").withSockJS() // 启用SockJS降级支持.setHeartbeatTime(10000); // 心跳间隔}@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {// 启用一个简单的基于内存的消息代理,将消息返回给前缀为"/topic"的目的地config.enableSimpleBroker("/topic");// 指定应用程序目的地前缀// 客户端发送消息的目的地应该以"/app"开头config.setApplicationDestinationPrefixes("/app");}}
后端创建端点
registry.addEndpoint(“/upload-websocket”); 添加一个端点,前端可以通过这个端点,进行websocket通信,对应的前端代码可以这么写
前端订阅
config.enableSimpleBroker(“/topic”, “/queue”); 这个是启用消息broker。广播消息的前缀。当我们需要发送广播消息给客户端时,需要满足这个前缀条件。
前端这么订阅消息,是topic前缀
// 订阅公共聊天室话题
stompClient.subscribe('/topic/public', onMessageReceived);
后端代码通过消息broker,可以将此消息发送给订阅了"/topic/boardCast/hello"的客户端。
private final SimpMessageSendingOperations messagingTemplate;messagingTemplate.convertAndSend("/topic/public", chatMessage);
前端发送消息给后端
config.setApplicationDestinationPrefixes(“/app”); 这个是前端可以往这个路径发送消息。
前端代码这么写的:
后端可以定义一个controller,来接收这个消息,所以这个/app的意思可以理解为所有@MessageMapping的前缀。
@Controller
@Slf4j
public class ChatController {/*** 接收前端发送* stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));** @param chatMessage* @return*/@MessageMapping("/chat.sendMessage")@SendTo("/topic/public")public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {return chatMessage;}}
后端发送消息给前端
messagingTemplate.convertAndSend
方式
private final SimpMessageSendingOperations messagingTemplate;messagingTemplate.convertAndSend("/topic/public", chatMessage);
@MessageMapping + @SendTo
方式
代码示例
前端往/app/chat.sendMessage发送了一条消息
function sendMessage() {const messageContent = document.querySelector('#message-input').value.trim();if(messageContent && stompClient) {const chatMessage = {sender: sid,content: messageContent,type: 'CHAT'};// 发送消息到服务器stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));document.querySelector('#message-input').value = '';}
}
后端需要注册/app前缀
然后定义一个Controller来接收用户消息, @MessageMapping(“/chat.sendMessage),这里就是子路径了,拼起来正好是/app/chat.sendMessage,这时Wesocket请求会到达sendMessage方法。
@Controller
public class ChatController {@MessageMapping("/chat.sendMessage")@SendTo("/topic/public")public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {return chatMessage;}
}
上面示例代码的执行流程,消息的流转如下图所示
- 消息通过inboundChannel到服务器
- 此时根据消息的前缀,会匹配出/app开头的,是需要找SimpAnnotationMethodMessageHandler。这个处理器,是找Controller来执行
- Controller中收到该消息,其方法中调用了一个发送方法。发往/topic/boardCast/hello
- 此时也会根据消息的前缀,找到消息处理器,SimpleBrokerMessageHandler
- SimpleBrokerMessageHandler遍历用户会话,找到订阅了/topic/boardCast/hello的用户。通过outboundChannel将消息发送出去
以上就是用户发送一个消息,服务端接收。服务端同时再发送一条广播消息给对应的客户端的过程。