概述
本文通过一个实际的场景来介绍在前后端分离的项目中通过 WebSocket 来实现服务器端主动向客户端发送消息的应用。主要内容如下
- WebSocket 是什么
- 服务器端 向 客户端 主动发送消息的案例说明
- SpringBoot 后端中 Websocket 的配置和使用
- 后端 Websocket 实现原理
- Vue 前端 Websocket 的配置和使用
WebSocket 是什么
Websocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 连接成功后,服务端与客户端可以双向通信。在需要消息推送的场景,Websocket 相对于轮询能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
具体如下特点
- 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 依赖于 TCP 协议
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL
服务器端 向 客户端 主动发送消息的案例说明
在客户端的列表数据中有个 status 字段,服务器端需要花费较长的时间进行处理,处理完成后才会更新对应数据的 status 字段值,通过 Websocket 的处理流程如下:
- 前端页面列表数据加载后,初始化一组 Websocket 客户端对象
- 服务器端 接收到 前端数据状态的查询请求
- 服务器端 每隔一段时间查询一下数据库,然后返回给客户端
- 客户端 根据返回的数据状态,再更新页面数据
后端 SpringBoot 中 Websocket 的配置和使用
Maven 依赖
1 | <dependency> |
配置
通过注入 ServerEndpointExporter 类,用于在项目启动的时候自动将使用了 @ServerEndpoint
注解声明的 Websocket endpoint 注册到 WebSocketContainer 中。
1 | package com.ckjava.config; |
通过 @ServerEndpoint
注解标注实现类
- 通过在类上增加
@ServerEndpoint
和@Component
注解,用于标注 Websocket 的实现类 - 通过 ConcurrentHashMap 管理多个客户端的 Session
- 通过 ScheduledExecutorService 和 init 方法实现定时对客户端进行消息发送
@OnOpen
标注的方法,用于接收客户端的 连接请求,其中的@PathParam
用于接收 url 中的参数@OnClose
标注的方法,用于接收客户端的 关闭请求。@OnMessage
标注的方法,用于接收客户端的 消息。@OnError
标注的方法,用于错误处理
1 | package com.ckjava.websocket; |
后端 Websocket 实现原理
为什么增加一个 ServerEndpointExporter Bean,并通过在一个类上增加 @ServerEndpoint
和 @Component
注解就可以实现服务器端 Websocket 功能,这里简单解析一下。
ServerEndpointExporter 的核心方法
- ServerEndpointExporter 实现了 spring 中的 SmartInitializingSingleton 接口,并重写了 afterSingletonsInstantiated 方法,具体如下
1 |
|
- 在 registerEndpoints 方法中可以发现,通过 ApplicationContext 中的 getBeanNamesForAnnotation 方法,从 spring 的 ioc 容器中获取含有
@ServerEndpoint
注解的类。
1 | /** |
- 在 registerEndpoint 方法中,通过 ServerContainer 的 addEndpoint 方法,最终将 endpoint 实现类注册到 ServerContainer 中。
1 | private void registerEndpoint(Class<?> endpointClass) { |
ServerContainer
java 定义了一套 javax.servlet-api, 一个 HttpServlet 就是一个 HTTP 服务。java websocket 并非基于 servlet-api 简单扩展, 而是新定义了一套 javax.websocket-api。
一个 websocket 服务对应一个 Endpoint。与 ServletContext 对应, websocket-api 也定义了 WebSocketContainer, 而编程方式注册 websocket 的接口是继承自 WebSocketContainer 的 ServerContainer。
一个 websocket 可以接受并管理多个连接, 因此可被视作一个 server。主流 servlet 容器都支持 websocket, 如 tomcat, jetty 等。看 ServerContainer api 文档, 可从 ServletContext attribute 找到 ServerContainer。
Vue 前端 Websocket 的配置和使用
- 在 created 方法中调用了 getPageData 方法,用于接收到列表数据后,通过 initWs 方法 给 每个数据 id 初始化一个 WebSocket 客户端
- 通过 onerror 事件绑定 出现异常 时候的回调方法
- 通过 onopen 事件绑定 连接成功 的回调方法
- 通过 onmessage 事件绑定 接收到服务器端消息 的回调方法,这里收到消息后,再更新前端的数据
- 通过 onclose 事件绑定 关闭连接 的回调方法
- 在 unmounted 方法中调用了 onbeforeunload 方法,用于关闭所有的 WebSocket 连接
1 | <script> |