目录javascript
从HTML5技术流行至今,WebSocket已经有很是普遍的应用:css
...html
这些场景,都须要服务器能主动实时的给浏览器或客户端推送消息,注意关键词是主动,还有实时!
而在HTML5一统江湖以前,因为HTTP在推送场景下的"薄弱",咱们须要借助一些复杂或者非标准的手段来实现。前端
这些方式包括有:java
在这种方案下,浏览器须要不断的向服务器发出请求,问题是比较明显的,包括:jquery
Comet 效率提高了很多,它解决了Ajax轮询的部分问题,利用HTTP长链接的特性尽量的避免了链接、带宽资源的浪费等等,因而在很长一段时间 Comet 成为了Web推送技术的主流。
But ,.. Comet 的实现技术比较复杂,不一样框架下的实现方式差别很大,在灵活性、性能上也有些欠缺。
关于服务端Comet的技术能够参考下面这篇经典文章:
https://www.ibm.com/developerworks/cn/web/wa-lo-comet/git
WebSocket 出现的目的没有别的,就是干掉前面的东西,Both!
最开始WebSocket 协议由 RFC6455 定义,其API标准包含于HTML5 范畴之中。
目前各大主流浏览器已经能彻底支持该技术。而后能够看看下面这个图:github
如上图,WebSocket 协议中, 浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。
那么相比以往的方式,这种方案更加节省资源了,它的实时性、灵活性都要强大很多。
固然,有HTML5标准给它站台,后台杠杠的~web
那么一个 WebSocket 的请求响应长成怎么样呢?
看下面这个图:spring
一开始我一直认为 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脚本。
步骤以下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>${springboot.version}</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!--websocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>${springboot.version}</version> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator-core</artifactId> <version>0.32</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>org.foo.springboot</groupId> <artifactId>base</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- jackson version --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.3</version> </dependency>
添加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
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"></meta> <title>Web控制台</title> <script th:src="@{/webjars/sockjs-client/sockjs.min.js}"></script> <script th:src="@{/webjars/stomp-websocket/stomp.min.js}"></script> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script type="text/javascript" th:src="@{/static/console.js}"></script> <style type="text/css"> body { font-family: "Microsoft YaHei" ;} .span-tv{padding-right:12px} #console p {padding: 0px; margin: 0px;} </style> </head> <body> <div style="background-color:#AAA; padding: 5px; border-bottom: 1px solid #333"> <input type="text" id="word" style="width:100px"></input> <button onclick="sendMessage()">发送消息</button> <button onclick="reconnect()">从新链接</button> <button onclick="clearConsole()">清空内容</button> </div> <div id="console" style="padding:5px; font-size:10px"></div> </body> </html>
而后是实现 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){ $("<p></p>").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篇" ,期待更多精彩内容^-^