本节简单介绍下如何在Spring Boot引入WebSocket,实现简单的客户端与服务端建立长连接并互发送文本消息。

框架搭建

新建一个Spring Boot项目,artifactId为spring-boot-websocket-socketjs,项目结构如下图所示:

Spring Boot整合WebSocket - 图1

项目的pom内容如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.5.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>cc.mrbird</groupId>
  12. <artifactId>spring-boot-websocket-socketjs</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>spring-boot-websocket-socketjs</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-websocket</artifactId>
  27. </dependency>
  28. </dependencies>
  29. <build>
  30. <plugins>
  31. <plugin>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-maven-plugin</artifactId>
  34. </plugin>
  35. </plugins>
  36. </build>
  37. </project>

引入了spring-boot-starter-websocket和spring-boot-starter-web依赖。

构建服务端

在cc.mrbird.socket目录下新建handler包,然后在该包下新建MyStringWebSocketHandler继承TextWebSocketHandler

  1. @Component
  2. public class MyStringWebSocketHandler extends TextWebSocketHandler {
  3. private Logger log = LoggerFactory.getLogger(this.getClass());
  4. @Override
  5. public void afterConnectionEstablished(WebSocketSession session) {
  6. log.info("和客户端建立连接");
  7. }
  8. @Override
  9. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  10. session.close(CloseStatus.SERVER_ERROR);
  11. log.error("连接异常", exception);
  12. }
  13. @Override
  14. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  15. super.afterConnectionClosed(session, status);
  16. log.info("和客户端断开连接");
  17. }
  18. @Override
  19. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  20. // 获取到客户端发送过来的消息
  21. String receiveMessage = message.getPayload();
  22. log.info(receiveMessage);
  23. // 发送消息给客户端
  24. session.sendMessage(new TextMessage(fakeAi(receiveMessage)));
  25. // 关闭连接
  26. // session.close(CloseStatus.NORMAL);
  27. }
  28. private static String fakeAi(String input) {
  29. if (input == null || "".equals(input)) {
  30. return "你说什么?没听清︎";
  31. }
  32. return input.replace('你', '我')
  33. .replace("吗", "")
  34. .replace('?', '!')
  35. .replace('?', '!');
  36. }
  37. }

该类重写了父类AbstractWebSocketHandler的四个方法:

Spring Boot整合WebSocket - 图2

  • afterConnectionEstablished,和客户端链接成功的时候触发该方法;
  • handleTransportError,和客户端连接失败的时候触发该方法;
  • afterConnectionClosed,和客户端断开连接的时候触发该方法;
  • handleTextMessage,和客户端建立连接后,处理客户端发送的请求。

WebSocketSession对象代表每个客户端会话,包含许多实用方法:

Spring Boot整合WebSocket - 图3

方法见名知意,就不赘述了。

此外,因为我们的目的是实现和客户端的通信,并且内容为文本内容,所以我们继承的是TextWebSocketHandler;如果传输的是二进制内容,则可以继承BinaryWebSocketHandler,更多信息可以自行查看WebSocketHandler的子类。

接着在cc.mrbird.socket目录下新建configure包,然后在该包下新建WebSocketServerConfigure配置类:

  1. @Configuration
  2. @EnableWebSocket
  3. public class WebSocketServerConfigure implements WebSocketConfigurer {
  4. @Autowired
  5. private MyStringWebSocketHandler myStringWebSocketHandler;
  6. @Override
  7. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  8. registry.addHandler(myStringWebSocketHandler, "/connect").withSockJS();
  9. }
  10. }

@EnableWebSocket用于开启WebSocket相关功能,我们注入了上面创建的MyStringWebSocketHandler,并将其注册到了WebSocketHandlerRegistry

上面代码的含义是,当客户端通过/connecturl和服务端连接通信时,使用MyStringWebSocketHandler处理会话。withSockJS的含义是,通信的客户端是通过SockJS实现的,下面会介绍到。

构建客户端

SockJS是一个JS插件,用于构建WebSocket,兼容性好。

在resources目录下新建static包,然后在该包下新建client.html:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>WebSocket客户端</title>
  6. <script src="https://cdn.bootcss.com/sockjs-client/0.3.4/sockjs.min.js"></script>
  7. <link href="https://cdn.bootcss.com/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
  8. </head>
  9. <body>
  10. <style>
  11. .jumbotron {
  12. width: 100%;
  13. }
  14. #text {
  15. height: 3rem;
  16. font-size: 1rem;
  17. line-height: 3rem;
  18. margin: 1rem;
  19. }
  20. .btn {
  21. margin-right: 5px;
  22. }
  23. #connect {
  24. margin-left: 1rem;
  25. }
  26. #log {
  27. margin: 1rem 0 0 1rem;
  28. }
  29. </style>
  30. <div class="container">
  31. <div class="row">
  32. <div class="jumbotron">
  33. <input type="text" placeholder="请输入你想传输的内容" id="text" class="col-lg-12"/>
  34. <input type="button" value="连接" class="btn btn-info" id="connect" onclick="connect()"/>
  35. <input type="button" value="发送" class="btn btn-success" id="sent" disabled="disabled" onclick="sent()"/>
  36. <input type="button" value="断开" class="btn btn-danger" id="disconnect" disabled="disabled"
  37. onclick="disconnect()"/>
  38. <div id="log">
  39. <p>聊天记录:</p>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. <script type="text/javascript">
  45. let text = document.querySelector('#text');
  46. let connectBtn = document.querySelector("#connect");
  47. let sentBtn = document.querySelector("#sent");
  48. let disconnectBtn = document.querySelector("#disconnect");
  49. let logDiv = document.querySelector("#log");
  50. let ws = null;
  51. function connect() {
  52. let targetUri = "/connect";
  53. ws = new SockJS(targetUri);
  54. ws.onopen = function () {
  55. setConnected(true);
  56. log('和服务端连接成功!');
  57. };
  58. ws.onmessage = function (event) {
  59. log('服务端说:' + event.data);
  60. };
  61. ws.onclose = function () {
  62. setConnected(false);
  63. log('和服务端断开连接!')
  64. }
  65. }
  66. function sent() {
  67. if (ws != null) {
  68. ws.send(text.value);
  69. log('客户端说:' + text.value);
  70. } else {
  71. log('请先建立连接!')
  72. }
  73. }
  74. function disconnect() {
  75. if (ws != null) {
  76. ws.close();
  77. ws = null;
  78. }
  79. setConnected(false);
  80. }
  81. function log(value) {
  82. let content = document.createElement('p');
  83. content.innerHTML = value;
  84. logDiv.appendChild(content);
  85. text.value = '';
  86. }
  87. function setConnected(connected) {
  88. connectBtn.disabled = connected;
  89. disconnectBtn.disabled = !connected;
  90. sentBtn.disabled = !connected;
  91. }
  92. </script>
  93. </body>
  94. </html>

html,css那些都不重要,重要的是我们引入了SockJS库。在connect()方法中,我们通过new SockJS(/connect)和上面的服务端建立了Socket通信。SockJS对象包含几个常用的实用方法:

  • onopen,和服务端讲了连接后的回调方法;
  • onmessage,服务端返回消息时的回调方法;
  • onclose,和服务端断开连接的回调方法;
  • send,发送消息给服务端;
  • close,断开和服务端的连接。

上面的JS较为简单,其他逻辑自己看看吧。

通信测试

启动项目,浏览器访问:http://localhost:8080/client.html

Spring Boot整合WebSocket - 图4

源码连接:https://github.com/wuyouzhuguli/SpringAll/tree/master/76.spring-boot-websocket-socketjs。