目录
- WebSocket 简介
- Java 使用 WebSocket 的方式
- SpringBoot 集成 WebSocket 使用
WebSocket 是什么?
websocket 是一种网络通信协议,类似 http 协议
# http
http://example.com:80/some/path
# websocket
ws://example.com:80/some/path
为什么需要 WebSocket 协议?
Http 协议有一个缺陷:通信只能由客户端发起
在某种场景下,例如,在外卖场景下,骑手位置更新时,服务器端向客户端发送骑手位置。如果使用 http 协议,那么就只能轮询了,由客户端不停地向服务器端发送请求,获取骑手的位置。
轮询的效率低下,有一定的延迟性,并且频繁的发送请求也会造成资源的浪费。
使用 WebSocket 协议可以实现由服务器端主动向客户端推送消息,当然客户端也可以向服务器端发送消息。
Java 使用 WebSocket 的几种方式
- JavaEE 对 WebSocket 的支持
- Spring 对 WebSocket 的支持
- WebSocket 框架:Stomp
这里仅介绍利用 Spring 框架使用 WebSocket 的方式,原因:Spring 使用 WebSocket 简便且易于扩展。
SpringBoot WebSocket 使用
SpringBoot 使用 WebSocket 非常方便,依赖上仅需要添加相应的 Starter 即可。
先给出概要的开发步骤:
- 添加 starter 依赖
- 添加 WebSocket 配置:
- 实现接口
WebSocketConfigurer
,并重写相应方法
- 实现接口
- 添加处理器,作用类似 SpringMVC 的处理器映射:
- 实现接口
WebSocketHandler
,并重写相应方法
- 实现接口
- 添加拦截器(可选),可在 websocket 握手阶段做一些处理,例如校验、保存用户信息。
- 实现接口:HandshakeInterceptor,并重写相应方法
开发步骤
-
添加依赖
-
websocket starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
-
-
添加配置类
@EnableWebSocket // [1] @Configuration public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry .addHandler() // todo [2] .addInterceptors() // todo [3] .setAllowedOrigins("*"); // 解决跨域问题 [4] } }
该步骤中:
[1] 开启 websocket 功能
[2] 添加处理器,具体在下一步创建
[3] 添加拦截器,也在后面创建
[4] 与 http 类似,需要解决跨域问题,这里允许所有请求来源
-
添加处理器
处理器的作用类似于 @RequestMapping 注解的方法,用于处理某一个路径的 websocket 连接
自定义处理器需要实现
WebSocketHandler
接口@Component public class DefaultHandler implements WebSocketHandler { /** * 建立连接 * @param session * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 缓存用户信息: userInfo } /** * 接收消息 * @param session * @param message * @throws Exception */ @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { } /** * 发生错误 * @param session * @param exception * @throws Exception */ @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { // 清除用户缓存信息 } /** * 关闭连接 * @param session * @param closeStatus * @throws Exception */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { // 清除用户缓存信息 } @Override public boolean supportsPartialMessages() { return false; } }
上述自定义 Handler 中,实现了5个方法,其中前四个方法作用,以及我们可做的一些事情举例已写在代码注释中,最后一个方法
supportsPartialMessages()
在较高版本的 SpringBoot 中才有(本文使用的版本 2.2.3.RELEASE),作用是是否支持发送部分消息。然后我们就可以给
WebSocketConfig
添加处理器了@EnableWebSocket @Configuration public class WebSocketConfig implements WebSocketConfigurer { @Resource WebSocketHandler defaultHandler; // [1] @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry .addHandler(defaultHandler, "/ws") // [2] .addInterceptors() .setAllowedOrigins("*"); } }
[1] 处我们通过依赖注入的方式注入我们的自定义处理器
[2] 添加拦截器,前文已经提到过,这里类似于 @RequestMapping 的作用,也就是说该处理器
defaultHandler
会处理 uri 为/ws
的请求 -
添加拦截器
拦截器可以在 websocket 连接握手阶段做一些校验,还可以存储用户的连接信息
实现
HandshakeInterceptor
接口@Component public class DefaultInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { // TODO return false; // [1] } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
[1] 处可以做一些处理,例如校验连接中的参数,保存连接用户信息等,用户信息等有用信息可存储在
Map<String, Object> attributes
中,在 handler 中可使用WebSocketSession#getAttributes()
方法取出相应的数据。返回 false 拒绝连接,true 则通过。
与 websocket 配置类中添加 handler 一样,需要将 interceptor 也添加到配置类中才会起作用。
@EnableWebSocket @Configuration public class WebSocketConfig implements WebSocketConfigurer { @Resource WebSocketHandler defaultHandler; @Resource DefaultInterceptor defaultInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry .addHandler(defaultHandler, "ws") .addInterceptors(defaultInterceptor) .setAllowedOrigins("*"); } }
其实到这里,基础的 websocket 服务已经搭建好了,剩下的可以自己在 handler 与 interceptor 中写自己的业务逻辑了
前端测试页面
引用自菜鸟教程,稍作了一点修改
前端页面:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
console.log("您的浏览器支持 WebSocket!");
// 打开一个 web socket
var ws = new WebSocket("ws://localhost:8080/ws");
ws.onopen = function()
{
// Web Socket 已连接上,使用 send() 方法发送数据
// ws.send("发送数据");
console.log("与服务器建立连接成功!")
};
ws.onmessage = function (evt)
{
var received_msg = evt.data;
console.log("接收数据:" + received_msg)
};
ws.onclose = function()
{
// 关闭 websocket
alert("连接已关闭...");
};
}
else
{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
</script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">运行 WebSocket</a>
</div>
</body>
</html>
测试
先启动服务器端 SpringBoot 应用,再使用前端页面点击测试一下就 ok 了