目录
从HTML5技术流行至今,WebSocket已经有非常广泛的应用:
...
这些场景,都需要服务器能主动实时的给浏览器或客户端推送消息,注意关键词是主动,还有实时!
而在HTML5一统江湖之前,由于HTTP在推送场景下的"薄弱",我们需要借助一些复杂或者非标准的手段来实现。
这些方式包括有:
在这种方案下,浏览器需要不断的向服务器发出请求,问题是比较明显的,包括:
Comet 效率提升了不少,它解决了Ajax轮询的部分问题,利用HTTP长连接的特性尽可能的避免了连接、带宽资源的浪费等等,于是在很长一段时间 Comet 成为了Web推送技术的主流。
But ,.. Comet 的实现技术比较复杂,不同框架下的实现方式差异很大,在灵活性、性能上也有些欠缺。
关于服务端Comet的技术可以参考下面这篇经典文章:
https://www.ibm.com/developerworks/cn/web/wa-lo-comet/
WebSocket 出场
WebSocket 出现的目的没有别的,就是干掉前面的东西,Both!
最开始WebSocket 协议由 RFC6455 定义,其API标准包含于HTML5 范畴之中。
目前各大主流浏览器已经能完全支持该技术。然后可以看看下面这个图:
如上图,WebSocket 协议中, 浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
那么相比以往的方式,这种方案更加节省资源了,它的实时性、灵活性都要强大不少。
当然,有HTML5标准给它站台,后台杠杠的~
那么一个 WebSocket 的请求响应长成怎么样呢?
看下面这个图:
一开始我一直认为 Stomp是暴风雨(误看为 Storm),然后觉得说这个技术挺犀利的。
然后在看了 Stomp 的协议介绍后发现,它是如此的简单..
Stomp 的 全称叫 Simple Text Orientated Messaging Protocol,就是一个简单的文本定向消息协议,
除了设计为简单易用之外,它的支持者也非常多。就比如目前主流的消息队列服务器如RabbitMQ、ActiveMQ都支持Stomp 协议。
开源地址:
http://stomp.github.io/
Stomp 定义了一些简单的指令,如下:
命令 | 说明 |
---|---|
CONNECT | 建立连接 |
SEND | 发送消息 |
SUBSCRIBE | 订阅主题 |
UNSUBSCRIBE | 取消订阅 |
BEGIN | 开启事务 |
COMMIT | 提交事务 |
ABORT | 回滚事务 |
ACK | 确认消费 |
NACK | 消息丢弃 |
DISCONNECT | 断开连接 |
一个简单的STOMP消息大致如下:
CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000\n\n\u0000
SEND
destination:/app/message\ncontent-length:6
发送内容\u0000
好的,你现在应该了解 Stomp是个什么了,那么为什么要介绍这个?
WebSocket 为我们提供了Web 双向通信的通道,但对于消息的交互协议还需要我们来自己实现(WebSocket 果然不够意思)
借助Stomp 协议,可以很方便的实现一种"订阅-发布"的通用机制,这个就是非常具有竞争力的一个特性了。
在介绍完WebSocket 之后,接下来干什么呢?
可能你看完前面的东西会觉得 WebSocket 是如此之强大,以至于很多场景都应该使用这个技术来实现。
那么如何做? 在此前我所介绍的 SpringBoot 也是如此之强大,那么能不能通过SpringBoot 轻松整合WebSocket 呢?这当然可以!
思索了很久,我决定做一个最简单的应用展示: 尬聊!
为什么是"尬聊”,而不是聊天室...
那么,下面开始讲这个案例,在该样例中会包含一个Controller类、一个HTML页面以及一个JS脚本。
步骤如下:
org.springframework.boot
spring-boot-starter-thymeleaf
${springboot.version}
org.slf4j
slf4j-api
org.springframework.boot
spring-boot-starter-websocket
${springboot.version}
org.springframework.boot
spring-boot-devtools
${springboot.version}
true
org.webjars
webjars-locator-core
0.32
org.webjars
sockjs-client
1.0.2
org.webjars
stomp-websocket
2.3.3
org.webjars
jquery
2.1.4
org.foo.springboot
base
1.0-SNAPSHOT
com.fasterxml.jackson.core
jackson-databind
2.8.3
com.fasterxml.jackson.core
jackson-core
2.8.3
添加spring-boot-starter-websocket 会自动引入spring-websocket的依赖,而后者就实现了WebSocket 操作的高级封装。
还有一个好消息,就是spring-websocket 也默认支持了 Stomp协议(看吧,Stomp支持者太多了)。
而除此之外,还内置了一个叫 SocketJS 的东西。
SocketJS是一个流行的JS库,主要是在WebSocket之上封装了一层API,用于支持浏览器不兼容WebSocket的情况。
其项目地址:
https://github.com/sockjs/sockjs-client
其他组件的说明
参考下面的代码,添加一个JavaConfig风格的配置类:
WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//设置订阅通道(客户端可订阅)
config.enableSimpleBroker("/topic");
//接收APP(客户端)消息的路由前缀,可通过@MessageMapping 映射到方法
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//websocket 连接端点
registry.addEndpoint("/backend").withSockJS();
}
@Override
public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
//配置拦截器
registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
@Override
public WebSocketHandler decorate(final WebSocketHandler handler) {
return new WebSocketHandlerDecorator(handler) {
@Override
public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
String username = session.getPrincipal() != null? session.getPrincipal().getName(): "GUEST";
logger.info("{} connect.", username);
super.afterConnectionEstablished(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
String username = session.getPrincipal() != null? session.getPrincipal().getName(): "GUEST";
logger.info("{} disconnect.", username);
super.afterConnectionClosed(session, closeStatus);
}
};
}
});
super.configureWebSocketTransport(registration);
}
}
在WebSocketConfig的配置中,有两点需要关注:
控制层除了支持页面的渲染,还需要对WebSocket消息进行处理,实现如下:
ConsoleController
@Controller
public class ConsoleController {
//输出数据频道
public static final String CHANNEL_CONSOLE = "/topic/console";
@Autowired
private SimpMessagingTemplate template;
/**
* 控制台页面
*
* @return
*/
@GetMapping("/console")
public String console() {
return "console";
}
/**
* 接收WebSocket消息方法
* @param message
*/
@MessageMapping("/message")
public void onMessage(String message) {
template.convertAndSend(CHANNEL_CONSOLE, "我收到了你的消息:" + message);
}
}
先做一个HTML页面,编辑templates/console.html
Web控制台
然后是实现 JS 脚本,编辑public/static/console.js
$(document).ready(function(){
//首次打开页面自动连接
connect();
})
//执行连接
function connect() {
//接入端点/backend
var socket = new SockJS('/backend');
window.stompClient = Stomp.over(socket);
window.stompClient.connect({}, function (frame) {
log('Connected: ' + frame);
//订阅服务端输出的 Topic
stompClient.subscribe('/topic/console', function (message) {
log("[服务器说]:" + message.body);
});
});
}
//断开连接
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
log("Disconnected");
}
//重新连接
function reconnect(){
clearConsole();
disconnect();
connect();
}
//发送消息
function sendMessage(){
var content = $("#word").val();
if(!content){
alert("请输入消息!")
return;
}
//向应用Topic发送消息
stompClient.send("/app/message", {}, content);
log("[你说]:" + content);
}
//记录控制台消息
function log(message){
$("").text(message).appendTo($("#console"));
}
//清空控制台
function clearConsole(){
$("#console").empty();
}
这样,Web控制台已经制作好了,运行主程序后,打开地址
http://localhost:8080/console
进行体验,如下:
好了,这个案例的确很尴尬..
但是我认为,在这上面做一做改造,应该可以实现一个诸如"美女聊天室" 的功能的,或者,你可以动手试试。
码云同步代码
https://spring.io/guides/gs/messaging-stomp-websocket/
https://blog.coding.net/blog/spring-static-resource-process
https://zh.wikipedia.org/wiki/WebSocket
https://halfrost.com/websocket/
欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^
作者:美码师