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

栏目: Html5 · 发布时间: 6年前

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

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

第一篇,什么是WebSocket以及它的用途

第二篇,Spring中如何利用STOMP快速构建WebSocket广播式消息模式

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

第四篇,深入Springboot源码,探究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() 方法:

WebSocket的故事(三)—— Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
方法内部会循环调用当前所有订阅此 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 方法

WebSocket的故事(三)—— Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
当客户端发送的消息到达服务端后,会首先根据消息的 destination 来进行匹配,找到对应的处理类。在本例中,即根据 /hello 找到 GreetingController (MessageMapping注解所在位置)。然后即通过 handleMatch 中的 invoke 方法,调用 GreetingController 中的 greeting 方法, greeting 方法返回后,通过 handleRetureValue

处理其返回值,那么它对应的方法又是什么呢?我们往下看:

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

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

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

继续追踪发送逻辑

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

1.在 SendToUser 分支中,无论是广播还是非广播消息,都用到了 messagingTemplate 。这个 messagingTemplate 是什么?

2.广播与非广播的消息发送,都调用了同样的方法,即 convertAndSendToUser 。区别在于非广播时,多了一个 sessionId 参数。这个方法以及这个参数该如何去理解呢?

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

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

这里,我们又接触到一个新类, 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 方法中。

WebSocket的故事(三)—— Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
其中的 resolveDestination 方法能识别带 /user 的订阅路径并做出处理, 此处将 sourceDestination 转化成 /topic/greetings-userau3ev44r , userau3ev44r 中, user 是关键字, au3ev44rSessionID ,这样子就把用户和订阅路径唯一的匹配起来了

WebSocket的故事(三)—— Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
接着,我们拿着 targetDestinations 地址,调用了 SimpMessageTemplate 类中的send方法,最终又来到了 SimpleBrokerMessageHandler 类中,眼熟吧,没错,就是我们在介绍 SendTo 注解时提到的,只不过,这时候它的目的地址,是 /topic/greetings-userau3ev44r 。至此,处理目的地址和封装消息的工作就完成了。之后,会走实际发送过程,客户端会收到返回的 greeting

消息。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法小时代

算法小时代

Serge Abiteboul、Gilles Dowek / 任铁 / 人民邮电出版社 / 2017-10-1 / 39.00元

算法与人工智能是当下最热门的话题之一,技术大发展的同时也引发了令人忧心的技术和社会问题。本书生动介绍了算法的数学原理和性质,描述了算法单纯、本质的功能,分析了算法和人工智能对人类社会现状及未来发展的影响力及其成因。一起来看看 《算法小时代》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具