【websocket】spring boot 集成 websocket 的四种方式 - 图1

集成 websocket 的四种方案

1. 原生注解

pom.xml

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>

WebSocketConfig

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.config;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  11. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  12. /**
  13. * @author buhao
  14. * @version WebSocketConfig.java, v 0.1 2019-10-18 15:45 buhao
  15. */
  16. @Configuration
  17. @EnableWebSocket
  18. public class WebSocketConfig {
  19. @Bean
  20. public ServerEndpointExporter serverEndpoint() {
  21. return new ServerEndpointExporter();
  22. }
  23. }

说明:

这个配置类很简单,通过这个配置 spring boot 才能去扫描后面的关于 websocket 的注解

WsServerEndpoint

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.ws;
  8. import org.springframework.stereotype.Component;
  9. import javax.websocket.OnClose;
  10. import javax.websocket.OnMessage;
  11. import javax.websocket.OnOpen;
  12. import javax.websocket.Session;
  13. import javax.websocket.server.ServerEndpoint;
  14. import java.io.IOException;
  15. import java.util.HashMap;
  16. import java.util.Map;
  17. /**
  18. * @author buhao
  19. * @version WsServerEndpoint.java, v 0.1 2019-10-18 16:06 buhao
  20. */
  21. @ServerEndpoint("/myWs")
  22. @Component
  23. public class WsServerEndpoint {
  24. /**
  25. * 连接成功
  26. *
  27. * @param session
  28. */
  29. @OnOpen
  30. public void onOpen(Session session) {
  31. System.out.println("连接成功");
  32. }
  33. /**
  34. * 连接关闭
  35. *
  36. * @param session
  37. */
  38. @OnClose
  39. public void onClose(Session session) {
  40. System.out.println("连接关闭");
  41. }
  42. /**
  43. * 接收到消息
  44. *
  45. * @param text
  46. */
  47. @OnMessage
  48. public String onMsg(String text) throws IOException {
  49. return "servet 发送:" + text;
  50. }
  51. }

说明

这里有几个注解需要注意一下,首先是他们的包都在 javax.websocket 下。并不是 spring 提供的,而 jdk 自带的,下面是他们的具体作用。

  1. @ServerEndpoint
    1. 通过这个 spring boot 就可以知道你暴露出去的 ws 应用的路径,有点类似我们经常用的@RequestMapping。比如你的启动端口是8080,而这个注解的值是ws,那我们就可以通过 ws://127.0.0.1:8080/ws 来连接你的应用
  2. @OnOpen
    1. 当 websocket 建立连接成功后会触发这个注解修饰的方法,注意它有一个 Session 参数
  3. @OnClose
    1. 当 websocket 建立的连接断开后会触发这个注解修饰的方法,注意它有一个 Session 参数
  4. @OnMessage
    1. 当客户端发送消息到服务端时,会触发这个注解修改的方法,它有一个 String 入参表明客户端传入的值
  5. @OnError
    1. 当 websocket 建立连接时出现异常会触发这个注解修饰的方法,注意它有一个 Session 参数

另外一点就是服务端如何发送消息给客户端,服务端发送消息必须通过上面说的 Session 类,通常是在@OnOpen 方法中,当连接成功后把 session 存入 Map 的 value,key 是与 session 对应的用户标识,当要发送的时候通过 key 获得 session 再发送,这里可以通过 session.getBasicRemote().sendText() 来对客户端发送消息。

2. Spring封装

pom.xml

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>

HttpAuthHandler

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.handler;
  8. import cn.coder4j.study.example.websocket.config.WsSessionManager;
  9. import org.springframework.stereotype.Component;
  10. import org.springframework.web.socket.CloseStatus;
  11. import org.springframework.web.socket.TextMessage;
  12. import org.springframework.web.socket.WebSocketSession;
  13. import org.springframework.web.socket.handler.TextWebSocketHandler;
  14. import java.time.LocalDateTime;
  15. /**
  16. * @author buhao
  17. * @version MyWSHandler.java, v 0.1 2019-10-17 17:10 buhao
  18. */
  19. @Component
  20. public class HttpAuthHandler extends TextWebSocketHandler {
  21. /**
  22. * socket 建立成功事件
  23. *
  24. * @param session
  25. * @throws Exception
  26. */
  27. @Override
  28. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  29. Object token = session.getAttributes().get("token");
  30. if (token != null) {
  31. // 用户连接成功,放入在线用户缓存
  32. WsSessionManager.add(token.toString(), session);
  33. } else {
  34. throw new RuntimeException("用户登录已经失效!");
  35. }
  36. }
  37. /**
  38. * 接收消息事件
  39. *
  40. * @param session
  41. * @param message
  42. * @throws Exception
  43. */
  44. @Override
  45. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  46. // 获得客户端传来的消息
  47. String payload = message.getPayload();
  48. Object token = session.getAttributes().get("token");
  49. System.out.println("server 接收到 " + token + " 发送的 " + payload);
  50. session.sendMessage(new TextMessage("server 发送给 " + token + " 消息 " + payload + " " + LocalDateTime.now().toString()));
  51. }
  52. /**
  53. * socket 断开连接时
  54. *
  55. * @param session
  56. * @param status
  57. * @throws Exception
  58. */
  59. @Override
  60. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  61. Object token = session.getAttributes().get("token");
  62. if (token != null) {
  63. // 用户退出,移除缓存
  64. WsSessionManager.remove(token.toString());
  65. }
  66. }
  67. }

说明

通过继承 TextWebSocketHandler 类并覆盖相应方法,可以对 websocket 的事件进行处理,这里可以同原生注解的那几个注解连起来看

  1. afterConnectionEstablished 方法是在 socket 连接成功后被触发,同原生注解里的 @OnOpen 功能
  2. afterConnectionClosed 方法是在 socket 连接关闭后被触发,同原生注解里的 @OnClose 功能
  3. handleTextMessage 方法是在客户端发送信息时触发,同原生注解里的 @OnMessage 功能

    WsSessionManager

    ```java /*
    • *
      • blog.coder4j.cn
      • Copyright (C) 2016-2019 All Rights Reserved. / package cn.coder4j.study.example.websocket.config;

import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession;

import java.io.IOException; import java.util.concurrent.ConcurrentHashMap;

/**

  • @author buhao
  • @version WsSessionManager.java, v 0.1 2019-10-22 10:24 buhao / @Slf4j public class WsSessionManager { /*

    • 保存连接 session 的地方 */ private static ConcurrentHashMap SESSION_POOL = new ConcurrentHashMap<>();

      /**

    • 添加 session *
    • @param key */ public static void add(String key, WebSocketSession session) { // 添加 session SESSION_POOL.put(key, session); }

      /**

    • 删除 session,会返回删除的 session *
    • @param key
    • @return */ public static WebSocketSession remove(String key) { // 删除 session return SESSION_POOL.remove(key); }

      /**

    • 删除并同步关闭连接 *
    • @param key */ public static void removeAndClose(String key) { WebSocketSession session = remove(key); if (session != null) {

      1. try {
      2. // 关闭连接
      3. session.close();
      4. } catch (IOException e) {
      5. // todo: 关闭出现异常处理
      6. e.printStackTrace();
      7. }

      } }

      /**

    • 获得 session *
    • @param key
    • @return */ public static WebSocketSession get(String key) { // 获得 session return SESSION_POOL.get(key); } } ```
      说明
      这里简单通过 ConcurrentHashMap 来实现了一个 session 池,用来保存已经登录的 web socket 的 session。前文提过,服务端发送消息给客户端必须要通过这个 session。

      MyInterceptor

      ```java /*
  • *
    • blog.coder4j.cn
    • Copyright (C) 2016-2019 All Rights Reserved. / package cn.coder4j.study.example.websocket.interceptor;

import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.HashMap; import java.util.Map;

/**

  • @author buhao
  • @version MyInterceptor.java, v 0.1 2019-10-17 19:21 buhao */ @Component public class MyInterceptor implements HandshakeInterceptor {

    /**

    • 握手前 *
    • @param request
    • @param response
    • @param wsHandler
    • @param attributes
    • @return
    • @throws Exception */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { System.out.println(“握手开始”); // 获得请求参数 HashMap paramMap = HttpUtil.decodeParamMap(request.getURI().getQuery(), “utf-8”); String uid = paramMap.get(“token”); if (StrUtil.isNotBlank(uid)) {

      1. // 放入属性域
      2. attributes.put("token", uid);
      3. System.out.println("用户 token " + uid + " 握手成功!");
      4. return true;

      } System.out.println(“用户登录已失效”); return false; }

      /**

    • 握手后 *
    • @param request
    • @param response
    • @param wsHandler
    • @param exception */ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { System.out.println(“握手完成”); }

}

  1. <a name="HDMpR"></a>
  2. ##### 说明
  3. 通过实现 **HandshakeInterceptor** 接口来定义握手拦截器,注意这里与上面 **Handler** 的事件是不同的,这里是建立握手时的事件,分为握手前与握手后,而 **Handler** 的事件是在握手成功后的基础上建立 socket 的连接。所以在如果把认证放在这个步骤相对来说最节省服务器资源。它主要有两个方法 **beforeHandshake** 与 **afterHandshake **,顾名思义一个在握手前触发,一个在握手后触发。
  4. <a name="xVeti"></a>
  5. #### WebSocketConfig
  6. ```java
  7. /*
  8. * *
  9. * * blog.coder4j.cn
  10. * * Copyright (C) 2016-2019 All Rights Reserved.
  11. *
  12. */
  13. package cn.coder4j.study.example.websocket.config;
  14. import cn.coder4j.study.example.websocket.handler.HttpAuthHandler;
  15. import cn.coder4j.study.example.websocket.interceptor.MyInterceptor;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.context.annotation.Configuration;
  18. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  19. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  20. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  21. /**
  22. * @author buhao
  23. * @version WebSocketConfig.java, v 0.1 2019-10-17 15:43 buhao
  24. */
  25. @Configuration
  26. @EnableWebSocket
  27. public class WebSocketConfig implements WebSocketConfigurer {
  28. @Autowired
  29. private HttpAuthHandler httpAuthHandler;
  30. @Autowired
  31. private MyInterceptor myInterceptor;
  32. @Override
  33. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  34. registry
  35. .addHandler(httpAuthHandler, "myWS")
  36. .addInterceptors(myInterceptor)
  37. .setAllowedOrigins("*");
  38. }
  39. }

说明

通过实现 WebSocketConfigurer 类并覆盖相应的方法进行 websocket 的配置。我们主要覆盖 registerWebSocketHandlers 这个方法。通过向 WebSocketHandlerRegistry 设置不同参数来进行配置。其中 addHandler 方法添加我们上面的写的 ws 的 handler 处理类,第二个参数是你暴露出的 ws 路径。addInterceptors 添加我们写的握手过滤器。setAllowedOrigins(“*”) 这个是关闭跨域校验,方便本地调试,线上推荐打开。

3. TIO

pom.xml

  1. <dependency>
  2. <groupId>org.t-io</groupId>
  3. <artifactId>tio-websocket-spring-boot-starter</artifactId>
  4. <version>3.5.5.v20191010-RELEASE</version>
  5. </dependency>

application.xml

  1. tio:
  2. websocket:
  3. server:
  4. port: 8989

说明

这里只配置了 ws 的启动端口,还有很多配置,可以通过结尾给的链接去寻找

MyHandler

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.handler;
  8. import org.springframework.stereotype.Component;
  9. import org.tio.core.ChannelContext;
  10. import org.tio.http.common.HttpRequest;
  11. import org.tio.http.common.HttpResponse;
  12. import org.tio.websocket.common.WsRequest;
  13. import org.tio.websocket.server.handler.IWsMsgHandler;
  14. /**
  15. * @author buhao
  16. * @version MyHandler.java, v 0.1 2019-10-21 14:39 buhao
  17. */
  18. @Component
  19. public class MyHandler implements IWsMsgHandler {
  20. /**
  21. * 握手
  22. *
  23. * @param httpRequest
  24. * @param httpResponse
  25. * @param channelContext
  26. * @return
  27. * @throws Exception
  28. */
  29. @Override
  30. public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
  31. return httpResponse;
  32. }
  33. /**
  34. * 握手成功
  35. *
  36. * @param httpRequest
  37. * @param httpResponse
  38. * @param channelContext
  39. * @throws Exception
  40. */
  41. @Override
  42. public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
  43. System.out.println("握手成功");
  44. }
  45. /**
  46. * 接收二进制文件
  47. *
  48. * @param wsRequest
  49. * @param bytes
  50. * @param channelContext
  51. * @return
  52. * @throws Exception
  53. */
  54. @Override
  55. public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
  56. return null;
  57. }
  58. /**
  59. * 断开连接
  60. *
  61. * @param wsRequest
  62. * @param bytes
  63. * @param channelContext
  64. * @return
  65. * @throws Exception
  66. */
  67. @Override
  68. public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
  69. System.out.println("关闭连接");
  70. return null;
  71. }
  72. /**
  73. * 接收消息
  74. *
  75. * @param wsRequest
  76. * @param s
  77. * @param channelContext
  78. * @return
  79. * @throws Exception
  80. */
  81. @Override
  82. public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
  83. System.out.println("接收文本消息:" + s);
  84. return "success";
  85. }
  86. }

说明

这个同上个例子中的 handler 很像,也是通过实现接口覆盖方法来进行事件处理,实现的接口是IWsMsgHandler,它的方法功能如下

  1. handshake
    1. 在握手的时候触发
  2. onAfterHandshaked
    1. 在握手成功后触发
  3. onBytes
    1. 客户端发送二进制消息触发
  4. onClose
    1. 客户端关闭连接时触发
  5. onText
    1. 客户端发送文本消息触发

      StudyWebsocketExampleApplication

      ```java /*
    • *
      • blog.coder4j.cn
      • Copyright (C) 2016-2019 All Rights Reserved. /

package cn.coder4j.study.example.websocket;

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.tio.websocket.starter.EnableTioWebSocketServer;

@SpringBootApplication @EnableTioWebSocketServer public class StudyWebsocketExampleApplication {

  1. public static void main(String[] args) {
  2. SpringApplication.run(StudyWebsocketExampleApplication.class, args);
  3. }

}

  1. <a name="ID99S"></a>
  2. ##### 说明
  3. 这个类的名称不重要,它其实是你的 spring boot 启动类,只要记得加上**@EnableTioWebSocketServer**注解就可以了
  4. <a name="fWlV6"></a>
  5. ### STOMP
  6. <a name="2bYJm"></a>
  7. #### pom.xml
  8. ```xml
  9. <dependency>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-websocket</artifactId>
  12. </dependency>

WebSocketConfig

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.config;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  10. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  11. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  12. import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
  13. /**
  14. * @author buhao
  15. * @version WebSocketConfig.java, v 0.1 2019-10-21 16:32 buhao
  16. */
  17. @Configuration
  18. @EnableWebSocketMessageBroker
  19. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  20. @Override
  21. public void registerStompEndpoints(StompEndpointRegistry registry) {
  22. // 配置客户端尝试连接地址
  23. registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
  24. }
  25. @Override
  26. public void configureMessageBroker(MessageBrokerRegistry registry) {
  27. // 设置广播节点
  28. registry.enableSimpleBroker("/topic", "/user");
  29. // 客户端向服务端发送消息需有/app 前缀
  30. registry.setApplicationDestinationPrefixes("/app");
  31. // 指定用户发送(一对一)的前缀 /user/
  32. registry.setUserDestinationPrefix("/user/");
  33. }
  34. }

说明
  1. 通过实现 WebSocketMessageBrokerConfigurer 接口和加上@EnableWebSocketMessageBroker来进行 stomp 的配置与注解扫描。
  2. 其中覆盖 registerStompEndpoints 方法来设置暴露的 stomp 的路径,其它一些跨域、客户端之类的设置。
  3. 覆盖 configureMessageBroker 方法来进行节点的配置。
    1. 其中 enableSimpleBroker 配置的广播节点,也就是服务端发送消息,客户端订阅就能接收消息的节点。
    2. 覆盖setApplicationDestinationPrefixes 方法,设置客户端向服务端发送消息的节点。
    3. 覆盖 setUserDestinationPrefix 方法,设置一对一通信的节点。

      WSController

  1. /*
  2. * *
  3. * * blog.coder4j.cn
  4. * * Copyright (C) 2016-2019 All Rights Reserved.
  5. *
  6. */
  7. package cn.coder4j.study.example.websocket.controller;
  8. import cn.coder4j.study.example.websocket.model.RequestMessage;
  9. import cn.coder4j.study.example.websocket.model.ResponseMessage;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.messaging.handler.annotation.MessageMapping;
  12. import org.springframework.messaging.handler.annotation.SendTo;
  13. import org.springframework.messaging.simp.SimpMessagingTemplate;
  14. import org.springframework.stereotype.Controller;
  15. import org.springframework.web.bind.annotation.GetMapping;
  16. import org.springframework.web.bind.annotation.ResponseBody;
  17. /**
  18. * @author buhao
  19. * @version WSController.java, v 0.1 2019-10-21 17:22 buhao
  20. */
  21. @Controller
  22. public class WSController {
  23. @Autowired
  24. private SimpMessagingTemplate simpMessagingTemplate;
  25. @MessageMapping("/hello")
  26. @SendTo("/topic/hello")
  27. public ResponseMessage hello(RequestMessage requestMessage) {
  28. System.out.println("接收消息:" + requestMessage);
  29. return new ResponseMessage("服务端接收到你发的:" + requestMessage);
  30. }
  31. @GetMapping("/sendMsgByUser")
  32. public @ResponseBody
  33. Object sendMsgByUser(String token, String msg) {
  34. simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg);
  35. return "success";
  36. }
  37. @GetMapping("/sendMsgByAll")
  38. public @ResponseBody
  39. Object sendMsgByAll(String msg) {
  40. simpMessagingTemplate.convertAndSend("/topic", msg);
  41. return "success";
  42. }
  43. @GetMapping("/test")
  44. public String test() {
  45. return "test-stomp.html";
  46. }
  47. }

说明
  1. 通过 @MessageMapping 来暴露节点路径,有点类似 @RequestMapping。注意这里虽然写的是 hello ,但是我们客户端调用的真正地址是 /app/hello。 因为我们在上面的 config 里配置了registry.setApplicationDestinationPrefixes(“/app”)
  2. @SendTo这个注解会把返回值的内容发送给订阅了 /topic/hello 的客户端,与之类似的还有一个@SendToUser 只不过他是发送给用户端一对一通信的。这两个注解一般是应答时响应的,如果服务端主动发送消息可以通过 simpMessagingTemplate类的convertAndSend方法。注意 simpMessagingTemplate.convertAndSendToUser(token, “/msg”, msg) ,联系到我们上文配置的 registry.setUserDestinationPrefix(“/user/“),这里客户端订阅的是/user/{token}/msg,千万不要搞错。

    Session 共享的问题

    上面反复提到一个问题就是,服务端如果要主动发送消息给客户端一定要用到 session。而大家都知道的是 session 这个东西是不跨 jvm 的。如果有多台服务器,在 http 请求的情况下,我们可以通过把 session 放入缓存中间件中来共享解决这个问题,通过 spring session 几条配置就解决了。但是 web socket 不可以。他的 session 是不能序列化的,当然这样设计的目的不是为了为难你,而是出于对 http 与 web socket 请求的差异导致的。
    目前网上找到的最简单方案就是通过 redis 订阅广播的形式,主要代码跟第二种方式差不多,你要在本地放个 map 保存请求的 session。也就是说每台服务器都会保存与他连接的 session 于本地。然后发消息的地方要修改,并不是现在这样直接发送,而通过 redis 的订阅机制。服务器要发消息的时候,你通过 redis 广播这条消息,所有订阅的服务端都会收到这个消息,然后本地尝试发送。最后肯定只有有这个对应用户 session 的那台才能发送出去。

    如何选择

  3. 如果你在使用 tio,那推荐使用 tio 的集成。因为它已经实现了很多功能,包括上面说的通过 redis 的 session 共享,只要加几个配置就可以了。但是 tio 是半开源,文档是需要收费的。如果没有使用,那就忘了他。

  4. 如果你的业务要求比较灵活多变,推荐使用前两种,更推荐第二种 Spring 封装的形式。
  5. 如果只是简单的服务器双向通信,推荐 stomp 的形式,因为他更容易规范使用。

    其它

  6. websocket 在线验证

写完服务端代码后想调试,但是不会前端代码怎么办,点这里,这是一个在线的 websocket 客户端,功能完全够我们调试了。

  1. stomp 验证

这个没找到在线版的,但是网上有很多 demo 可以下载到本地进行调试,也可以通过后文的连接找到。

  1. 另外由于篇幅有限,并不能放上所有代码,但是测试代码全都上传 gitlab,保证可以正常运行,可以在 这里 找到

参考链接

  1. SpringBoot 系统 - 集成 WebSocket 实时通信
  2. WebSocket 的故事(二)—— Spring 中如何利用 STOMP 快速构建 WebSocket 广播式消息模式
  3. SpringBoot集成WebSocket【基于纯H5】进行点对点[一对一]和广播[一对多]实时推送
  4. Spring Framework 参考文档(WebSocket STOMP)
  5. Spring Boot中使用WebSocket总结(一):几种实现方式详解
  6. Spring Boot 系列 - WebSocket 简单使用
  7. tio-websocket-spring-boot-starter