前言
HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的缺点,如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
一、为什么需要STOMP?
WebSocket 协议是一种相当低级的协议。它定义了如何将字节流转换为帧。帧可以包含文本或二进制消息。由于消息本身不提供有关如何路由或处理它的任何其他信息,因此很难在不编写其他代码的情况下实现更复杂的应用程序。幸运的是,WebSocket 规范允许在更高的应用程序级别上使用子协议。
另外,单单使用WebSocket完成群聊、私聊功能时,需要自己管理session信息,通过STOMP协议时,spring已经封装好,开发者只需要关注自己的主题、订阅关系即可。
二、STOMP详解
STOMP 中文为“面向消息的简单文本协议”,STOMP 提供了能够协作的报文格式,以至于 STOMP 客户端可以与任何 STOMP 消息代理(Brokers)进行通信,从而为多语言,多平台和 Brokers 集群提供简单且普遍的消息协作。STOMP 协议可以建立在WebSocket 之上,也可以建立在其他应用层协议之上。通过 Websocket建立 STOMP 连接,也就是说在 Websocket 连接的基础上再建立 STOMP 连接。最终实现如上图所示,这一点可以在代码中有一个良好的体现。
业界已经有很多优秀的 STOMP 的服务器/客户端的开源实现
- STOMP 服务器:ActiveMQ、RabbitMQ、StompServer、…
- STOMP 客户端库:stomp.js(javascript)
Stomp 的特点是客户端的实现很容易,服务端相当于消息队列的 broker 或者是 server,一般不需要我们去实现,所以重点关注一下客户端如何使用
- CONNECT:启动与服务器的流或 TCP 连接
- SEND:发送消息
- SUBSCRIBE:订阅主题
- UNSUBSCRIBE:取消订阅
- BEGIN:启动事物
- COMMIT:提交事物
- ABORT:回滚事物
- ACK:确认来自订阅的消息的消费
- NACK:告诉服务器客户端没有消费该消息
- DISCONNECT:断开连接
其实STOMP协议并不是为WS所设计的,它其实是消息队列的一种协议,和AMQP、JMS是平级的。 只不过由于它的简单性恰巧可以用于定义WS的消息体格式。 目前很多服务端消息队列都已经支持了STOMP,比如RabbitMQ、Apache ActiveMQ等。很多语言也都有STOMP协议的客户端解析库,像JAVA的Gozirra、C的libstomp、Python的pyactivemq、JavaScript的stomp.js等等。
STOMP协议官方文档
三、SpringBoot集成STOMP代码示例
3.1 架构图
3.2、服务端代码
- 1、添加依赖
- 2、WebSocket的配置类
@EnableWebSocketMessageBroker注解启用 WebSocket 消息处理,由消息代理支持。
SockJS 有一些浏览器中缺少对 WebSocket 的支持,而 SockJS 是一个浏览器的 JavaScript库,它提供了一个类似于网络的对象,SockJS 提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和 Web 服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS 的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持 WebSocket,会自动降为轮询的方式。如果你使用 Java 做服务端,同时又恰好使用 Spring Framework 作为框架,那么推荐使用SockJS。
- 3、控制器代码
-
@MessageMapping:功能与RequestMapping注解类似。send指令发送信息时添加此注解。
-
@SendTo/@SendToUser:将信息输出到该主题。客户端订阅同样的主题后就会收到信息。
-
在只有指定@MessageMapping时@MessageMapping == “/topic” + @SendTo。
-
如果想使用rest接口发送消息。可以通过SimpMessagingTemplate进行发送。
-
点对点聊天时,可以使用SimpMessagingTemplate.convertAndSendToUser方法发送。个人意味比注解@SendToUser更加容易理解,更加方便。
-
convertAndSendToUser方法和convertAndSend类似,区别在于convertAndSendToUser方法会在主题默认添加/user/为前缀。因此,示例代码中convertAndSend方法直接传入"/topic/user/"+message.getUserId ()+"/sendToUser" 也是点对点发送。topic其中是默认前缀。
-
如果想修改convertAndSendToUser默认前缀可在配置类进行配置,可在WebSocketConfig类中查看。
四、SimpMessagingTemplate的作用
-
1、SimpMessagingTemplate可以在应用的任意地方发送消息。 Spring的SimpMessagingTemplate能够在应用的任何地方发送消息,甚至不必以首先接收一条消息作为前提。使用SimpMessagingTemplate的最简单方式是将它(或者其接口SimpMessageSendingOperations)自动装配到所需的对象中。
-
2、SimpMessagingTemplate可以为指定的用户发送消息。 SimpMessagingTemplate还提供了convertAndSendToUser()方法。convertAndSendToUser()方法能够让我们给特定用户发送消息。
客户端接收一对一消息的主题是"/users/"+usersId+"/message",这里的用户Id可以是一个普通字符串,只要每个客户端都使用自己的Id并且服务器端知道每个用户的Id就行了。
五、神奇的@SendTo和@SendToUser
5.1 @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 就能解决这个问题。
5.2 @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()) + "!"); //根据传入的信息,返回一个欢迎消息. }spring参考文档
websocket参考文档
到此这篇关于SpringBoot+STOMP协议实现就介绍到这了。
参考: https://www.jb51.net/article/214655.htm
https://developer.aliyun.com/article/763747
https://www.codercto.com/a/22176.html
https://www.cnblogs.com/goloving/p/15023025.html
https://www.cnblogs.com/question-sky/p/9636756.html
http://events.jianshu.io/p/c9ea50050acd