WebSocket的故事(三)—— Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)

前言

最近,偶然在掘金上发现了一个大牛写的这篇文章,感觉作者写的非常好,防止以后找不到了,这里转载记录一下,方便以后使用。

概述

本文是WebSocket的故事系列第三篇第一节,将逐步深入Spring源码进行介绍,本系列的干货也将陆续在后面的几篇文章中放出。WebSocket的故事系列计划分五大篇,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包含如下几篇文章:

第一篇,什么是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速构建WebSocket广播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(2)
第五篇,Springboot中,实现网页聊天室之自定义WebSocket消息代理
第六篇,Springboot中,实现更灵活的WebSocket

本篇的主线

上一篇介绍Spring实现的最简单的STOMP的一种模式,通过@SendTo注解,将消息发送到指定消息代理,只要是订阅过该消息代理的客户端,都会收到这个消息。作为系列的第三篇,我会分三次来详细介绍实现细节,本篇将由@SendTo和@SendToUser开始,深入Spring的WebSocket消息发送关键代码进行讲解。为下一篇点对点消息的讲解铺路。

本篇适合的读者

想要了解STOMP协议,Spring内部代码细节,以及如何使用Springboot搭建WebSocket服务的同学。

前方高能预警

本篇的代码相对较多,我会尽量细致讲解。

神奇的@SendTo和@SendToUser

本篇我们将详细介绍这两个注解背后的故事。

@SendTo

上一篇中,我们利用@SendTo注解,使方法的返回值推送到消息代理器中,由消息代理器广播到订阅路径中去。但并没有详细的介绍消息是怎样被Spring框架处理,最后发送广播出去的。先放上上节中的关键代码:

    @MessageMapping("/hello")   //使用MessageMapping注解来标识所有发送到“/hello”这个destination的消息,都会被路由到这个方法进行处理.
    @SendTo("/topic/greetings") //使用SendTo注解来标识这个方法返回的结果,都会被发送到它指定的destination,“/topic/greetings”.
    //传入的参数Message为客户端发送过来的消息,是自动绑定的。
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // 模拟处理延时
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); //根据传入的信息,返回一个欢迎消息.
    }
}

上面方法中的返回值,会被广播到/topic/greetings这个订阅路径中,只要客户端订阅了这个路径,都会接收到消息。Spring处理消息的主要类是SimpleBrokerMessageHandler, 当需要发送广播消息时,最终会调用其中的sendMessageToSubscribers()方法:

 

 

方法内部会循环调用当前所有订阅此Broker的客户端Session,然后逐个发送消息。这里,入参destination就是Broker的地址,而message,就是我们返回信息的封装,其他细节这里就不展开讲了。

 

那么如果我只是想用WebSocket向服务器发出查询请求,然后服务器你就把查询结果给我就行了,其他用户就不用你广播推送了,简单点,就是我请求,你就推送给我。这又该怎么办呢?是的,@SendToUser就能解决这个问题。

@SendToUser

先上代码片段:

    @MessageMapping("/hello") //使用MessageMapping注解来标识所有发送到“/hello”这个destination的消息,都会被路由到这个方法进行处理.
    @SendToUser("/topic/greetings") //使用SendToUser注解来标识这个方法返回的结果,都会被发送到请求它的用户的destination.
    //传入的参数Message为客户端发送过来的消息,是自动绑定的。
    public Greeting greeting(HelloMessage message) throws Exception {
        Thread.sleep(1000); // 模拟处理延时
        return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); //根据传入的信息,返回一个欢迎消息.
    }
}

可以看到,这里我只是修改了注解,基于上节中我们的示例代码,我们启动程序,试验一下效果,结果发现并没有收到返回信息,这是为什么呢?让我们深入代码实现的关键节点来看看。

@SendToUser背后的实现细节

首先,在我们查看代码细节之前,应该先静态分析一下。根据之前我们介绍过的内容,很容易想到:

1.Spring WebSocket通道的建立最开始是源于Http协议的第一次握手,握手成功之后,就打开了客户端和服务器的WebSocket通道,即客户端与服务端通过一个Session来维持通信。就像建立一条管道一样,你有内容就传给我,我有内容就传给你。
2.上面的greeting方法,实际上是框架提供给开发者一个处理客户端请求的一个时机,开发者可以根据业务需要,对信息处理加工后,返回给客户端需要的响应结果。那么当这个方法return的时候,也就是响应信息由服务端向客户端返送的开始。

基于上述两个基本结论,我们开始分析代码,首先就是从return之后开始,看看代码跑到了哪里: AbstractMethodMessageHandler.java中的handleMatch方法

 

 

当客户端发送的消息到达服务端后,会首先根据消息的destination来进行匹配,找到对应的处理类。在本例中,即根据/hello找到GreetingController(MessageMapping注解所在位置)。然后即通过handleMatch中的invoke方法,调用GreetingController中的greeting方法,greeting方法返回后,通过handleRetureValue处理其返回值,那么它对应的方法又是什么呢?我们往下看:

 

顺着这个方法,我们到了一个重要的类,SendToMethodReturnValueHandler.java

 

 

 

从类的名字就可以看出来,它是用来专门处理SendTo相关注解的类。当用SendTo注解的方法返回后,即调用此类中的handleReturnValue方法来进行处理。代码流程很清晰,大家参考图片内的注释即可。

继续追踪发送逻辑

两个值得我们继续追踪的点:

1.在SendToUser分支中,无论是广播还是非广播消息,都用到了messagingTemplate。这个messagingTemplate是什么?
2.广播与非广播的消息发送,都调用了同样的方法,即convertAndSendToUser。区别在于非广播时,多了一个sessionId参数。这个方法以及这个参数该如何去理解呢?

带着这样的疑问继续追踪,还是在SendToMethodReturnValueHandler.java这个类中:

 

 

 

这里,我们又接触到一个新类,SimpMessagingTemplate。它实现了convertAndSendToUser方法,我们有必要详细介绍一下这个方法,它的代码量不大,但却至关重要:

public void convertAndSendToUser(String user, String destination, Object payload, @Nullable Map<String, Object> headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
    Assert.notNull(user, "User must not be null");
    user = StringUtils.replace(user, "/", "%2F");
    destination = destination.startsWith("/") ? destination : "/" + destination;
    super.convertAndSend(this.destinationPrefix + user + destination, payload, headers, postProcessor);
}

介绍一下输入参数:

user:用户标识,这里就是客户端与服务端链接的sessionId
destination:这是SendToUser注解后括号内的参数值
payload:Object类型,它标识Controller中定义的方法的返回值,这里就是GreetingController类中greeting方法的返回值
headers:返回信息的消息头
postProcessor:此处为Null\

首先对入参进行校验和归一化,重点在最后一行,入参处做了字符串拼接,将原来的destination拼接为/user/userID/topic/greetingsuserID是客户端的SessionID。拼接结果destination=“/user/au3ev44r/topic/greetings“。好,接下来,我们来看一下这个方法:

AbstractMessageSendingTemplate<D>.java中:

public void convertAndSend(D destination, Object payload, @Nullable Map<String, Object> headers, @Nullable MessagePostProcessor postProcessor) throws MessagingException {
    Message<?> message = this.doConvert(payload, headers, postProcessor);
    this.send(destination, message);
}

它将要发送的Body信息与Header信息进行整合,得到Message信息。之后,调用send方法发送。之后经过一系列加工方法的流转,最后到达了UserDestinationMessageHandler类中的handleMessage方法中。

 

 

其中的resolveDestination方法能识别带/user的订阅路径并做出处理,此处将sourceDestination转化成/topic/greetings-userau3ev44r,userau3ev44r中,user是关键字,au3ev44rSessionID,这样子就把用户和订阅路径唯一的匹配起来了

 

 

 

接着,我们拿着targetDestinations地址,调用了SimpMessageTemplate类中的send方法,最终又来到了SimpleBrokerMessageHandler类中,眼熟吧,没错,就是我们在介绍SendTo注解时提到的,只不过,这时候它的目的地址,是/topic/greetings-userau3ev44r。至此,处理目的地址和封装消息的工作就完成了。之后,会走实际发送过程,客户端会收到返回的greeting消息。

 

总结

上例中,我们通过代码,详细讲解了一条客户端消息到达服务端后,是如何通过代码流转,找到下面两个关键参数的整个流程的。

  • 消息的目的地址
  • 封装返回消息 希望大家能静下心来仔细研读,读懂这部分代码,会对后续的文章理解有很大帮助,同时也能提高大家对Spring设计理念的感悟。了解更多Spring的实现细节。

本篇涉及到的代码

SpringWebSocket Github


作者:xNPE
链接:https://juejin.im/post/5b7706d451882542fe288e26
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家评论,一起探讨,代码如有问题,欢迎各位大神指正!

给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot WebSocket Stomp是一种基于Spring Boot框架的WebSocket协议的实现方式,它可以实现实时通信和消息推送功能。Stomp是一种简单的消息传输协议,它可以在WebSocket之上提供一个可靠的消息传输机制。使用Spring Boot WebSocket Stomp可以轻松地实现WebSocket通信,同时也可以使用Stomp协议来传输消息。这种方式非常适合实现实时通信和消息推送功能,例如在线聊天、实时监控等场景。 ### 回答2: springboot websocket stomp是一种基于Java的开源框架,它可以帮助我们实现实时通信功能。它采用了WebSocket协议作为底层通信协议,并结合了STOMP(Simple Text Oriented Messaging Protocol)协议来进行消息的传输和解析。 使用springboot websocket stomp可以很方便地实现客户端和服务器之间的实时通信,比如聊天室、实时数据展示等功能。它的好处是能够降低开发成本,提高开发效率,同时还可以提供较好的用户体验。 在使用springboot websocket stomp时,首先需要进行相关的配置和依赖,然后在代码定义好相关的消息处理器,用于处理客户端发送过来的消息和服务器推送的消息。接下来,我们可以使用JS等前端技术来调用WebSocket对象,连接到指定的WebSocket服务端,并发送和接收消息。 在WebSocket连接建立之后,我们可以使用STOMP协议进行消息的发送和订阅。我们可以使用STOMP协议的几个关键命令,比如SEND、SUBSCRIBE、UNSUBSCRIBE等来进行消息的发送和订阅操作。 springboot websocket stomp还提供了一些注解,用于标识和定义消息的处理器、消息的目的地等属性。通过这些注解,我们可以很方便地控制消息的发送和接收。 总的来说,springboot websocket stomp提供了一种简单且效率高的方式来实现实时通信功能。它的易用性、扩展性和可靠性使得它在实际应用得到广泛的应用。 ### 回答3: Spring Boot是一种用于简化Spring应用程序开发的框架,它提供了许多便利的功能和自动配置的特性。WebSocket是一种在客户端和服务器之间建立持久连接的协议,它为实时双向通信提供了一个解决方案。Stomp是一种在WebSocket之上建立消息传递协议的简单文本协议。 Spring Boot提供了对WebSocketStomp的支持,使开发人员能够轻松实现实时通信功能。通过使用Spring Boot的WebSocketStomp支持,可以快速构建具有实时功能的应用程序。 在Spring Boot使用WebSocketStomp,首先需要在pom.xml文件添加相关依赖。然后,在应用程序的配置类使用@EnableWebSocketMessageBroker注解启用WebSocketStomp消息代理功能。接下来,使用@MessageMapping注解来定义处理WebSocket消息的方法。 在处理WebSocket消息的方法,可以使用@SendTo注解将消息发送到指定的目的地,也可以使用SimpMessagingTemplate来主动推送消息给客户端。 另外,还可以使用@SubscribeMapping注解来定义处理订阅请求的方法。通过在订阅请求方法返回需要订阅的数据,可以在客户端成功订阅后立即将数据发送给客户端。 通过使用Spring Boot的WebSocketStomp支持,我们可以轻松地实现实时通信功能,使应用程序能够实时传递消息和数据。这对于需要实时更新的应用程序非常有用,如聊天室、股票交易系统等。 总而言之,Spring Boot提供了对WebSocketStomp的支持,使开发人员能够方便地构建具有实时通信功能的应用程序。通过使用WebSocketStomp,我们可以实现实时传递消息和数据的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值