目录
一、序言
上节中我们在 WebSocket的那些事(2-实操篇)中简单介绍了Spring中对于WebSocket的封装,并实现一个简单的服务端,这节我们将会结合
STOMP
子协议实现WebSocket通信。
二、STOMP详解
1、STOMP简单介绍
WebSocket协议定义了两种消息类型(文本类型和二进制类型),但是消息内容却是未定义的,下面我们介绍一下
STOMP
协议。
STOMP
(Simple Text Oriented Messaging Protocol) 起源于脚本语言,比如Ruby、Python和Perl,用于连接企业消息代理,它可以用于任何可靠的双向网络协议中,如TCP和WebSocket。尽管STOMP是一个面向文本的协议,但消息负载可以是文本或者二进制。
STOMP
基于WebSocket在客户端和服务端之间定义了一种机制,协商通过子协议(更高级的消息协议)来定义可以发送何种消息,每条消息的内容是什么,等等。
2、STOMP协议内容
STOMP是一个基于帧的协议,帧的结构如下:
COMMAND
header1:value1
header2:value2
Body
客户端可以用
SEND
或者
SUBSCRIBE
命令去发送和订阅消息,
destination
头部用来描述消息发送到哪里以及谁应该接收消息,下面的消息结构是客户端订阅股票行情的例子,如下:
SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*
下面的消息结构是客户端发送交易请求的例子,如下:
SEND
destination:/queue/trade
content-type:application/json
content-length:44
{"action":"BUY","ticker":"MMM","shares",44}
STOMP服务端可以使用
MESSAGE
命令广播消息给所有的订阅者,下面的例子为广播股票行情消息给所有消息订阅者。
MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM
{"ticker":"MMM","price":129.45}
3、使用STOMP的好处
在Spring中使用STOMP与原生WebSockets相比提供了更加丰富的编程模型,下面是使用STOMP的优点:
- 不需要发明自定义消息协议和消息格式。
- 在Spring中,STOMP客户端,包括Java客户端都可用。
- 可使用其它消息代理管理消息订阅和广播,如
RabbitMQ
,ActiveMQ
等支持STOMP协议的中间件。 - 应用逻辑处理入口可以像Spring MVC一样统一在
@Controller
实例内,同时消息可以基于STOMP头部消息进行路由,而不是直接用WebSocketHandler
处理原生WebSocket消息。 - 可以基于STOMP目的地和消息类型使用
Spring Security
对消息进行安全传输。
三、代码示例
1、Maven依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
备注:这里我就简单用Thymeleaf作模板引擎,为了方便调试在application.yml中将
spring.thymeleaf.cache
设为
false
来禁用模板缓存。
2、开启WebSocket消息代理
@Configuration@EnableWebSocketMessageBrokerpublicclassWebsocketMessageBrokerConfigimplementsWebSocketMessageBrokerConfigurer{@OverridepublicvoidregisterStompEndpoints(StompEndpointRegistry registry){
registry.addEndpoint("/websocket")// WebSocket握手端口.addInterceptors(newHttpSessionHandshakeInterceptor()).setAllowedOriginPatterns("*")// 设置跨域.withSockJS();// 开启SockJS回退机制}@OverridepublicvoidconfigureWebSocketTransport(WebSocketTransportRegistration registry){// 这里我们设置入站消息最大为8K
registry.setMessageSizeLimit(8*1024);}@OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistry registry){
registry.setApplicationDestinationPrefixes("/app")// 发送到服务端目的地前缀.enableSimpleBroker("/topic");// 开启简单消息代理,指定消息订阅前缀}}
备注:
- 关于SockJS的介绍请参考上篇文章 WebSocket的那些事(1-概念篇)最后部分。
setApplicationDestinationPrefixes
的意思是以目的地以/app
开头的消息将会被路由到Controller实例中的方法进行处理。enableSimpleBroker
将会启用一个内置的内存消息代理,用于订阅、广播和路由消息。
3、控制器
@Slf4j@ControllerpublicclassGreetingController{@GetMapping("/page/greeting")publicModelAndViewturnToGreetingPage(){returnnewModelAndView("/greeting");}@MessageMapping("/greeting")publicStringsayGreeting(String name){
log.info("Message received: {}", name);return"Hello, "+ name;}}
4、前端页面greeting.html
<!DOCTYPEhtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>greeting</title><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.6.1/sockjs.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script><scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script><style>#mainWrapper{width: 600px;margin: auto;}</style></head><body><divid="mainWrapper"><divid="msgWrapper"><pstyle="vertical-align: top">发送的消息:</p><textareaid="msgSent"style="width: 400px;height: 100px"></textarea><pstyle="vertical-align: top">收到的消息:</p><textareaid="msgReceived"style="width: 400px;height: 100px"></textarea></div><divstyle="margin-top: 5px;"><buttononclick="connect()">连接</button><buttononclick="sendMessage()">发送消息</button><buttononclick="disconnect()">断开连接</button></div></div><scripttype="text/javascript">$(()=>{$('#msgSent').val('');$("#msgReceived").val('');});let stompClient =null;// 连接服务器constconnect=()=>{const ws =newSockJS('http://localhost:8080/websocket');
stompClient = Stomp.over(ws);
stompClient.connect({},()=>{$("#msgReceived").val('Connected to websocket server!');
stompClient.subscribe('/topic/greeting',function(message){
console.log(`Received message: ${message.body}`);$("#msgReceived").val(message.body);});});}// 断连constdisconnect=()=>{
stompClient.disconnect(()=>{$("#msgReceived").val('Disconnected from WebSocket server');});}// 发送消息,这里直接发的是文本constsendMessage=()=>{
stompClient.send('/app/greeting',{},$('#msgSent').val());}</script></body></html>
备注:
- greeting.html为
Thymeleaf
模板页面,文件直接放在类路径templates目录下即可.- 这里我们选用了
SockJS
去连接服务端,为什么使用SockJS请参考 WebSocket的那些事(1-概念篇),同时注意协议名称是http而不是ws。
四、测试
浏览器输入
http://localhost:8080/page/greeting
即可跳转到我们的页面,如下:
1、连接服务端
点击连接按钮后,我们发现已经与服务端成功建立连接。我们可以观察到客户端发送了两条命令,一条是
Connect
建立连接命令,另一条是连接建立成功后的
SUBSCRIBE
命令,用来订阅目的地为
/topic/greeting
的消息。
2、发送消息
点击发送按钮发送消息,可以看到服务端也返回了
Hello, Nick
的回复消息。
备注:我们可以看到客户端发送了一条
SEND
命令,目的地为
/app/greeting
,内容长度为4个字节。同时服务端回复了一条
MESSAGE
命令,这条命令的意思是广播消息给所有订阅
/topic/greeting
的客户端。
五、STOMP消息传播流程
一旦STOMP端点暴露,Spring应用对于连接的客户端就变成了STOMP代理,下面我们简单介绍一下服务端消息传播流程。
上面的图展示了三种消息渠道:
clientInboundChannel
: 客户端消息入站通道,用来传递WebSocket客户端接收的消息。clientOutboundChannel
: 客户端消息出站通道,用来发送服务端消息给WebSocket客户端。brokerChannel
: 消息代理通道,用来在服务层传递消息给消息代理。
目的地以
/app
开头的消息会通过clientInboundChannel路由到MessageHandler消息处理器进行处理,然后再将消息通过brokerChannel发送到SimpleBroker中。
目的地以
/topic
开头的消息会通过clientInboundChannel直接路由到SimpleBroker中进行处理。
上图中的SimpleBroker实际上是Spring基于内存的内置消息代理,实际上Spring也支持集成其它支持
STOMP
协议的MQ,如ActiveMQ、RabbitMQ等,集成外部消息代理的消息传播流程图如下:
从上图中,我们可以看到StompBrokerRelay会和外部消息代理进行通信,通过
enableStompBrokerRelay
即可以集成外部消息代理,关于集成外部消息代理的例子和细节我们在后面的章节在讨论。
六、结语
这节我们简单介绍了STOMP协议、Spring中对WebSocket的STOMP支持以及具体的代码集成示例。
下节我们将会具体介绍
@Controller
控制器中相关消息注解的使用示例和细节,如
@MessageMapping
、
@SubscribeMapping
、
@MessageExceptionHandler
、
@SendTo
、
@SendToUser
等。
版权归原作者 凌波漫步& 所有, 如有侵权,请联系我们删除。