本文目录导读:

- 案例场景:股票行情实时推送系统
- 第一部分:后端实现(Spring Boot + STOMP)
- 第二部分:前端实现(JavaScript + STOMP.js)
- 第三部分:协议帧分析(底层数据流)
- 总结与关键点
这是一个关于 STOMP 协议的全面解析案例,STOMP(Simple Text Oriented Messaging Protocol,简单文本定向消息协议)是一种轻量级的消息传输协议,广泛应用于实时通信系统(如WebSocket聊天、股票行情推送、游戏对战等)。
下面我将通过场景设定、代码案例和协议分析三个维度,为你详细拆解一个典型的 STOMP 案例。
案例场景:股票行情实时推送系统
假设我们要开发一个金融App,后端有一个 Java 服务(使用 Spring Boot),前端是 Web 浏览器,我们需要将股票价格实时推送给订阅了该股票的用户。
选择 STOMP 的理由: 由于 WebSocket 是底层协议,它只负责传输字节流,不关心消息内容,使用 STOMP 作为 WebSocket 之上的消息协议,可以定义消息格式(帧)、路由(目的地)和订阅机制,让前后端解耦。
第一部分:后端实现(Spring Boot + STOMP)
后端需要配置 WebSocket 端点及消息代理。
配置类(WebSocketConfig.java)
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// 设置消息代理的前缀:消息以 /topic 开头会广播给所有订阅者
// 消息以 /queue 开头会点对点发送(一对一)
config.enableSimpleBroker("/topic", "/queue");
// 设置前端发送消息时的前缀:前端发到 /app/sendMessage,后端的处理类通过 @MessageMapping("/sendMessage") 接收
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 设置 WebSocket 连接端点,前端通过这个地址建立连接
// .withSockJS() 是为了兼容不支持 WebSocket 的浏览器,提供降级方案
registry.addEndpoint("/ws-stomp").setAllowedOrigins("*").withSockJS();
}
}
消息处理控制器(StockController.java)
这里模拟一个定时推送给特定频道的服务。
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class StockController {
// 注入模板类,用于主动推送消息
private final SimpMessagingTemplate messagingTemplate;
public StockController(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
// 模拟客户端订阅某只股票
@MessageMapping("/subscribe/stock/{code}") // 客户端发送到 /app/subscribe/stock/AAPL
@SendTo("/topic/stocks") // 订阅结果广播到 /topic/stocks
public String subscribeStock(@PathVariable String code) {
// 这里返回给所有订阅者的初始响应
return "订阅成功: " + code;
}
// 模拟定时推送股票价格
@Scheduled(fixedRate = 5000) // 每5秒推送一次
public void pushStockPrices() {
// 模拟实时价格波动
String applePrice = String.format("AAPL: $%.2f", Math.random() * 100 + 150);
String googlePrice = String.format("GOOG: $%.2f", Math.random() * 100 + 2800);
// 转换并携带目的地 "/topic/stocks",前端只要订阅了这个目的地就能收到
messagingTemplate.convertAndSend("/topic/stocks", applePrice);
messagingTemplate.convertAndSend("/topic/stocks", googlePrice);
// 如果需要点对点推送,可以使用:
// messagingTemplate.convertAndSendToUser("userId", "/queue/replies", message);
}
}
第二部分:前端实现(JavaScript + STOMP.js)
前端通过 STOMP 客户端连接后端。
代码示例(使用原生 WebSocket + STOMP 客户端库)
<!DOCTYPE html>
<html>
<head>实时股票行情</title>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
<h1>股票实时行情</h1>
<div id="output"></div>
<script type="text/javascript">
var stompClient = null;
// 建立连接
function connect() {
// 注意这里使用的是 SockJS,对应后端的 .withSockJS()
var socket = new SockJS('http://localhost:8080/ws-stomp');
stompClient = Stomp.over(socket);
// 建立连接(可以传用户名密码,也可以不传)
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
// 订阅公共频道
// 1. 订阅后端推送的股票价格
stompClient.subscribe('/topic/stocks', function (message) {
showMessage("更新: " + message.body);
});
// 2. 发送订阅请求到后端
// 注意:前缀是 /app 映射到后端的 @MessageMapping
stompClient.send("/app/subscribe/stock/AAPL", {}, {});
// 这样就完成了 请求->响应->持续推送 的通路
}, function (error) {
alert('连接失败: ' + error);
});
}
function showMessage(text) {
var output = document.getElementById('output');
output.innerHTML += '<p>' + new Date().toLocaleTimeString() + ' - ' + text + '</p>';
}
// 断开连接
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
// 页面加载后自动连接
window.onload = connect;
</script>
</body>
</html>
第三部分:协议帧分析(底层数据流)
当你在浏览器控制台开启日志(stompClient.debug = function(str) { console.log(str); };),你会看到客户端发往服务器的原始 STOMP 帧格式。
连接帧(客户端 -> 服务端)
CONNECT accept-version:1.1,1.0 heart-beat:10000,10000 \0 <!-- 空字符表示帧结束 -->
对应后端的 client.connect({}, ...)
订阅帧(客户端 -> 服务端)
SUBSCRIBE id:sub-0 destination:/topic/stocks \0
对应前端的 stompClient.subscribe('/topic/stocks', ...)
发送帧(客户端 -> 服务端)
SEND destination:/app/subscribe/stock/AAPL content-length:0 \0
对应前端的 stompClient.send(...)
消息帧(服务端 -> 客户端,推送价格时)
MESSAGE subscription:sub-0 message-id:abc123 destination:/topic/stocks AAPL: $175.23 \0
对应后端 messagingTemplate.convertAndSend() 执行后,客户端收到的 message.body
总结与关键点
-
架构层级:
- 传输层:底层是 WebSocket(或 SockJS 降级的 HTTP 长轮询)。
- 协议层:STOMP 定义了帧格式(命令 + 头 + 体)。
- 业务层:前后端根据
destination进行路由。
-
核心概念:
Destination:目的地。/topic(广播)和/queue(点对点)。Send vs Publish:客户端用SEND发到/app,服务端用MESSAGE推送到/topic。
-
优点:
- 简单易用,基于文本调试方便。
- 提供了
ack(消息确认)机制,保证消息可靠送达(可选)。 - 支持事务(
BEGIN/COMMIT),适合复杂的消息操作。
-
缺点:
性能低于纯二进制协议(如自定义的 Protocol Buffers 或 MessagePack 直接走 WebSocket),因为它基于文本且有冗余头信息。
在实际项目中,如果业务逻辑简单(如仅聊天室),直接用 WebSocket + JSON 即可;如果涉及复杂的消息路由、多端订阅或需要与企业级消息队列(如 RabbitMQ、ActiveMQ)无缝对接,STOMP 会是更标准、更省力的选择。