本文是WebSocket的故事系列第二篇,WebSocket的故事系列计划分五篇,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包含以下几篇文章:html
第一篇,什么是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速构建WebSocket广播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(2)
第五篇,Springboot中,实现网页聊天室之自定义WebSocket消息代理
第六篇,Springboot中,实现更灵活的WebSocket前端
承接上文对WebSocket的介绍,由WebSocket的发送接收信息谈起,对STOMP协议作大体介绍,最后,经过Springboot和JS,实际编写一个WebSocket例子,实现广播式消息发送。git
想要了解STOMP协议,以及如何使用Springboot搭建WebSocket服务的同窗。github
上一篇,咱们介绍了WebSocket的握手过程,并未详细介绍信息的发送,只是提到了WebSocket发送是以帧为单位的。而WebSocket协议上也并无规定其消息发送的详细格式。那就意味着每一个使用WebSocket的开发者,都须要本身在服务端和客户端定义一套规则,来传输信息。那么,有没有已经造好的轮子呢?答案确定是有的。这就是STOMP。web
STOMP是一个用于C/S之间进行异步消息传输的简单文本协议, 全称是Simple Text Oriented Messaging Protocol。spring
其实STOMP协议并非为WS所设计的, 它实际上是消息队列的一种协议, 和AMQP,JMS是平级的。 只不过因为它的简单性恰巧能够用于定义WS的消息体格式。 目前不少服务端消息队列都已经支持了STOMP, 好比RabbitMQ, Apache ActiveMQ等。不少语言也都有STOMP协议的客户端解析库,像JAVA的Gozirra,C的libstomp,Python的pyactivemq,JavaScript的stomp.js等等。bash
STOMP是一种基于帧的协议,一帧由一个命令,一组可选的Header和一个可选的Body组成。 STOMP是基于Text的,但也容许传输二进制数据。 它的默认编码是UTF-8,但它的消息体也支持其余编码方式,好比压缩编码。服务器
STOMP服务端被设计为客户端能够向其发送消息的一组目标地址。STOMP协议并无规定目标地址的格式,它由使用协议的应用本身来定义。 例如/topic/a,/queue/a,queue-a对于STOMP协议来讲都是正确的。应用能够本身规定不一样的格式以此来代表不一样格式表明的含义。好比应用本身能够定义以/topic打头的为发布订阅模式,消息会被全部消费者客户端收到,以/user开头的为点对点模式,只会被一个消费者客户端收到。websocket
对于STOMP协议来讲, 客户端会扮演下列两种角色的任意一种:
实际上,WebSocket结合STOMP至关于构建了一个消息分发队列,客户端能够在上述两个角色间转换,订阅机制保证了一个客户端消息能够经过服务器广播到多个其余客户端,做为生产者,又能够经过服务器来发送点对点消息。
COMMAND
header1:value1
header2:value2Body^@
^@表示行结束符
一个STOMP帧由三部分组成:命令,Header(头信息),Body(消息体)
来看一个实际的帧例子:
SEND
destination:/broker/roomId/1
content-length:57
{“type":"ENTER","content":"o7jD64gNifq-wq-C13Q5CRisJx5E"}
更多STOMP协议的细节,若是你们感兴趣,能够参考上述的官方网页,有更多详细的帧结构介绍。下面,咱们将主要介绍用Springboot和JS实现后端和前端,构建一个WebSocket的小型应用场景。
首先,生产者经过发送一条SEND命令消息到某个目的地址(destination),服务端request channel接受到这条SEND命令消息,若是目的地址是应用目的地址则转到相应的由应用本身写的业务方法作处理(对应图中的SimpAnnotationMethod),再转到broker(SimpleBroker)。若是目的地址是非应用目的地址则直接转到broker。broker经过SEND命令消息来构建MESSAGE命令消息, 再经过response channel推送MESSAGE命令消息到全部订阅此目的地址的消费者。 废话很少说,下面直接上代码。
咱们来实现一个简单聊天室的第一步,每当有用户加入聊天室时,该用户向服务器发送加入聊天室的消息,服务器向当前聊天室内的全部用户发送欢迎语。
在Spring中,STOMP消息会被路由到以Controller注解标识的类中。即咱们须要定义一个控制器类,并使用Controller注解来标识它,而后在其中实现具体的消息处理方法,咱们建立一个名为GreetingController的类:
package com.xnpe.club.wbs.controller;
import com.xnpe.club.wbs.data.Greeting;
import com.xnpe.club.wbs.data.HelloMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller //使用Controller注解来标识这是一个控制器类
public class GreetingController {
@MessageMapping("/hello") //使用MessageMapping注解来标识全部发送到“/hello”这个destination的消息,都会被路由到这个方法进行处理.
@SendTo("/topic/greetings") //使用SendTo注解来标识这个方法返回的结果,都会被发送到它指定的destination,“/topic/greetings”.
//传入的参数HelloMessage为客户端发送过来的消息,是自动绑定的。
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // 模拟处理延时
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); //根据传入的信息,返回一个欢迎消息.
}
}
复制代码
总体下来,greeting()方法的做用是,处理全部发到/hello这个destination的信息,并将处理的结果,发送到全部订阅了/topic/greetings这个destination的客户端。其中模拟的延时,其本质是为了演示在WebSocket中,咱们无需考虑超时这样的问题,即上一篇文章提到的,客户端与服务端链接创建后,服务端能够根据实际场景,在“任何有须要”的时候“推送”消息到客户端,直到链接释放。
刚才咱们已经建立了消息处理控制器,也就是咱们的业务处理逻辑。如今咱们要为Spring配置WebSocket和STOMP消息设置。 建立一个名为WebSocketController的类:
package com.xnpe.club.wbs.config;
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 //使用Configuration注解标识这是一个Springboot的配置类.
@EnableWebSocketMessageBroker //使用此注解来标识使能WebSocket的broker.即便用broker来处理消息.
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
//实现WebSocketMessageBrokerConfigurer中的此方法,配置消息代理(broker)
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); //启用SimpleBroker,使得订阅到此"topic"前缀的客户端能够收到greeting消息.
config.setApplicationDestinationPrefixes("/app"); //将"app"前缀绑定到MessageMapping注解指定的方法上。如"app/hello"被指定用greeting()方法来处理.
}
@Override
//用来注册Endpoint,“/gs-guide-websocket”即为客户端尝试创建链接的地址。
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
复制代码
配置主要包含两部份内容,一个是消息代理,另外一个是Endpoint,消息代理指定了客户端订阅地址,以及发送消息的路由地址;Endpoint指定了客户端创建链接时的请求地址。
至此,服务端的配置工做就完成了,很是简单。如今,让咱们实现一个前端页面,来验证服务的工做状况。
针对STOMP,前端咱们采用JavaScript的stomp的客户端实现stomp.js以及WebSocket的实现SockJS。此处只展现核心代码。
//使用SockJS和stomp.js来打开“gs-guide-websocket”地址的链接,这也是咱们使用Spring构建的SockJS服务。
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
//链接成功后的回调方法
setConnected(true);
console.log('Connected: ' + frame);
//订阅/topic/greetings地址,当服务端向此地址发送消息时,客户端便可收到。
stompClient.subscribe('/topic/greetings', function (greeting) {
//收到消息时的回调方法,展现欢迎信息。
showGreeting(JSON.parse(greeting.body).content);
});
});
}
//断开链接的方法
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
//将用户输入的名字信息,使用STOMP客户端发送到“/app/hello”地址。它正是咱们在GreetingController中定义的greeting()方法所处理的地址.
function sendName() {
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
复制代码
本篇例子实现代码连接地址: SpringWebSocket Github
至此,咱们实现了一个最简单的使用Spring,基于STOMP的WebSocket例子。下一篇咱们会基于这个例子,继续完善聊天室功能,实现点对点的通讯功能。即两个用户如何点对点的聊天,敬请期待。
小铭出品,必属精品
欢迎关注xNPE技术论坛,更多原创干货每日推送。