SpringBoot - WebSocket

目录

  1. WebSocket 简介
  2. Java 使用 WebSocket 的方式
  3. 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 的几种方式

  1. JavaEE 对 WebSocket 的支持
  2. Spring 对 WebSocket 的支持
  3. WebSocket 框架:Stomp

这里仅介绍利用 Spring 框架使用 WebSocket 的方式,原因:Spring 使用 WebSocket 简便且易于扩展。

SpringBoot WebSocket 使用

SpringBoot 使用 WebSocket 非常方便,依赖上仅需要添加相应的 Starter 即可。

先给出概要的开发步骤:

  1. 添加 starter 依赖
  2. 添加 WebSocket 配置:
    1. 实现接口WebSocketConfigurer,并重写相应方法
  3. 添加处理器,作用类似 SpringMVC 的处理器映射:
    1. 实现接口 WebSocketHandler,并重写相应方法
  4. 添加拦截器(可选),可在 websocket 握手阶段做一些处理,例如校验、保存用户信息。
    1. 实现接口:HandshakeInterceptor,并重写相应方法

开发步骤

  1. 添加依赖

    1. websocket starter

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>
      
  2. 添加配置类

    @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 类似,需要解决跨域问题,这里允许所有请求来源

  3. 添加处理器

    处理器的作用类似于 @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 的请求

  4. 添加拦截器

    拦截器可以在 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 了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容