目录

一、聊聊 WebSocket

从HTML5技术流行至今,WebSocket已经有非常广泛的应用:

  • 在线游戏,提供实时的操作交互体验
  • 社交平台,与好友实时的私信对话
  • 新闻动态,获得感兴趣的主题信息推送

...

这些场景,都需要服务器能主动实时的给浏览器或客户端推送消息,注意关键词是主动,还有实时!

而在HTML5一统江湖之前,由于HTTP在推送场景下的"薄弱",我们需要借助一些复杂或者非标准的手段来实现。

这些方式包括有:

  • Ajax轮询,比如每隔5秒钟,由浏览器对服务器主动请求数据后返回。

在这种方案下,浏览器需要不断的向服务器发出请求,问题是比较明显的,包括:

  1. HTTP请求头部会浪费一些带宽;
  2. 频繁重建连接会造成很大的开销。
  • Comet,这个词好像翻译为"彗星"? 这个是采用 streaming 或 long-pulling 的长连接技术:

    服务器在收到请求时先挂起,等待有事件发生时才返回数据。

Comet 效率提升了不少,它解决了Ajax轮询的部分问题,利用HTTP长连接的特性尽可能的避免了连接、带宽资源的浪费等等,于是在很长一段时间 Comet 成为了Web推送技术的主流。

But ,.. Comet 的实现技术比较复杂,不同框架下的实现方式差异很大,在灵活性、性能上也有些欠缺。

关于服务端Comet的技术可以参考下面这篇经典文章:

https://www.ibm.com/developerworks/cn/web/wa-lo-comet/

  • Flash,通过Flash 插件代码实现Socket通讯,本质上是基于TCP的通讯模式,由于Flash 需要安装插件以及浏览器的兼容性问题,目前已经逐渐废弃。

WebSocket 出场

WebSocket 出现的目的没有别的,就是干掉前面的东西,Both!

最开始WebSocket 协议由 RFC6455 定义,其API标准包含于HTML5 范畴之中。

目前各大主流浏览器已经能完全支持该技术。然后可以看看下面这个图:

如上图,WebSocket 协议中, 浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

那么相比以往的方式,这种方案更加节省资源了,它的实时性、灵活性都要强大不少。

当然,有HTML5标准给它站台,后台杠杠的~

那么一个 WebSocket 的请求响应长成怎么样呢?

看下面这个图:

二、Stomp 是个什么鬼

一开始我一直认为 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 协议,可以很方便的实现一种"订阅-发布"的通用机制,这个就是非常具有竞争力的一个特性了。

三、SpringBoot 整合 WebSocket

在介绍完WebSocket 之后,接下来干什么呢?

可能你看完前面的东西会觉得 WebSocket 是如此之强大,以至于很多场景都应该使用这个技术来实现。

那么如何做? 在此前我所介绍的 SpringBoot 也是如此之强大,那么能不能通过SpringBoot 轻松整合WebSocket 呢?这当然可以!

思索了很久,我决定做一个最简单的应用展示: 尬聊!

为什么是"尬聊”,而不是聊天室...

那么,下面开始讲这个案例,在该样例中会包含一个Controller类、一个HTML页面以及一个JS脚本。

步骤如下:

A. 引入依赖


<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

其他组件的说明

  • webjars 主要是将一些前端的框架打包到Jar包中以方便我们使用,这里我们添加了socketJS、stompWebSocket相关的一些包;
  • jackson 用于支持WebSocket消息的编解码,是必须添加的。

B. WebSocket 配置

参考下面的代码,添加一个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的配置中,有两点需要关注:

  • registerStompEndpoints 用于添加端点,即浏览器通过 ws://xxx 能访问到的路径
  • configureMessageBroker 用于做消息路由配置,包括订阅主题、方法映射路径

C. 控制器

控制层除了支持页面的渲染,还需要对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);
}
}

D. 前端实现

先做一个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篇" ,期待更多精彩内容^-^

作者:美码师

补习系列(20)-大话 WebSocket 与 "尬聊"的实现的更多相关文章

  1. 补习系列(20)-大话 WebSocket 与 "尬聊"的实现

    目录 一.聊聊 WebSocket 二.Stomp 是个什么鬼 三.SpringBoot 整合 WebSocket A. 引入依赖 B. WebSocket 配置 C. 控制器 D. 前端实现 四.参 ...

  2. 补习系列(19)-springboot JPA + PostGreSQL

    目录 SpringBoot 整合 PostGreSQL 一.PostGreSQL简介 二.关于 SpringDataJPA 三.整合 PostGreSQL A. 依赖包 B. 配置文件 C. 模型定义 ...

  3. 补习系列(1)-springboot项目基础搭建课

    目录 前言 一.基础结构 二.添加代码 三.应用配置 四.日志配置 五.打包部署 小结 前言 springboot 最近火的不行,目前几乎已经是 spring 家族最耀眼的项目了.抛开微服务.技术社区 ...

  4. 补习系列(17)-springboot mongodb 内嵌数据库

    目录 简介 一.使用 flapdoodle.embed.mongo A. 引入依赖 B. 准备测试类 C. 完善配置 D. 启动测试 细节 二.使用Fongo A. 引入框架 B. 准备测试类 C.业 ...

  5. 补习系列(17)-springboot mongodb 内嵌数据库【华为云技术分享】

    目录 简介 一.使用 flapdoodle.embed.mongo A. 引入依赖 B. 准备测试类 C. 完善配置 D. 启动测试 细节 二.使用Fongo A. 引入框架 B. 准备测试类 C.业 ...

  6. 一例完整的websocket实现群聊demo

    前言 业余我都会花一些时间在tcp.http和websocket等领域的学习,现在觉得有点收获,所以把一个基于websocket的群聊功能的例子提供给大家玩玩.当然这是一个很完整的例子,包括webso ...

  7. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(20)-多条件模糊查询和回收站还原的实现

    原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(20)-多条件模糊查询和回收站还原的实现 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇)   (1):框架 ...

  8. 补习系列(18)-springboot H2 迷你数据库

    目录 关于 H2 一.H2 用作本地数据库 1. 引入依赖: 2. 配置文件 3. 样例数据 二.H2 用于单元测试 1. 依赖包 2. 测试配置 3. 测试代码 小结 关于 H2 H2 数据库是一个 ...

  9. 补习系列(16)-springboot mongodb 数据库应用技巧

    目录 一.关于 MongoDB 二.Spring-Data-Mongo 三.整合 MongoDB CRUD A. 引入框架 B. 数据库配置 C. 数据模型 D. 数据操作 E. 自定义操作 四.高级 ...

随机推荐

  1. CSPS模拟 89

  2. CSPS模拟 61

    T1 模拟 T2 ?? T3 哈希

  3. NOIP模拟 29

    T1第一眼觉得是网络流 看见4e6条边200次增广我犹豫了 O(n)都过不去的赶脚.. 可是除了网络流板子我还会什么呢 于是交了个智障的EK 还是用dijkstra跑的 居然有50分!$(RP--)$ ...

  4. 使用Typescript重构axios(一)——写在最前面

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  5. st表复习笔记

    st表,一种高效的区间最值查询(RMQ)算法.本质其实是一个动态规划. 其实吧,对于看过线性dp的人来说应该不难理解,只是处理有些麻烦.但是本土狗因为-1的问题居然改了许久... 用两个2^i的区间把 ...

  6. tarjan求lca的神奇

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  7. CF600E Lomsat gelral——线段树合并/dsu on tree

    题目描述 一棵树有$n$个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. 这个题意是真的窒息...具体意思是说,每个节点有一个颜色,你要找的是每个子树中颜色的众数 ...

  8. Vue2.0项目使用bootstrap后提示Module parse failed: Unexpected character

    具体报错如下: 报错原因是: Vue2.0无法识别bootstrap.css中使用的字体,也就是上图中圈出来的地方. 解决方案: // 需要在webpack.config.js增加对不识别文件的处理 ...

  9. jquery swiper3自定义切换效果的方法

    jquery swiper3自定义切换效果的方法 <pre> <div class="swiper-slide"> <div class=" ...

  10. Ansible之常用模块(一)

    ansible之所以功能强大,不是ansible本身,是因为它有众多的模块,前文我们介绍了ansible的基础介绍,系列命令的用法以及选项的说明,通过前文的学习我们知道了ansible是基于pytho ...