经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
Spring WebSocket实现实时通信的详细教程
来源:cnblogs  作者:lucky_fd  时间:2024/1/29 11:06:06  对本文有异议

简介

WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。

我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。

Spring WebSocket是Spring框架的一部分,提供了在Web应用程序中实现实时双向通信的能力。本教程将引导你通过一个简单的例子,演示如何使用Spring WebSocket建立一个实时通信应用。

准备工作

确保你的项目中已经引入了Spring框架的WebSocket模块。你可以通过Maven添加以下依赖:

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

创建WebSocket配置类(实现WebSocketConfigurer接口)

首先,创建一个配置类,用于配置WebSocket的相关设置。

  1. package com.ci.erp.human.config;
  2. import com.ci.erp.human.handler.WebSocketHandler;
  3. import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  7. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  8. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  9. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  10. /**
  11. *
  12. * Websocket配置类
  13. *
  14. * @author lucky_fd
  15. * @since 2024-01-17
  16. */
  17. @Configuration
  18. @EnableWebSocket
  19. public class WebSocketConfig implements WebSocketConfigurer {
  20. @Override
  21. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  22. // 注册websocket处理器和拦截器
  23. registry.addHandler(webSocketHandler(), "/websocket/server")
  24. .addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");
  25. registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*")
  26. .addInterceptors(webSocketHandleInterceptor()).withSockJS();
  27. }
  28. @Bean
  29. public WebSocketHandler webSocketHandler() {
  30. return new WebSocketHandler();
  31. }
  32. @Bean
  33. public WebSocketHandleInterceptor webSocketHandleInterceptor() {
  34. return new WebSocketHandleInterceptor();
  35. }
  36. }

上面的配置类使用@EnableWebSocket注解启用WebSocket,并通过registerWebSocketHandlers方法注册WebSocket处理器。

  • registerWebSocketHandlers:这个方法是向spring容器注册一个handler处理器及对应映射地址,可以理解成MVC的Handler(控制器方法),websocket客户端通过请求的url查找处理器进行处理

  • addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来做一些事情。

  • setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

  • withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

创建WebSocket消息处理器(实现TextWebSocketHandler 接口)

接下来,创建一个消息处理器,处理客户端发送的消息。

  1. package com.ci.erp.human.handler;
  2. import cn.hutool.core.util.ObjectUtil;
  3. import com.ci.erp.common.core.utils.JsonUtils;
  4. import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
  5. import org.springframework.web.socket.CloseStatus;
  6. import org.springframework.web.socket.TextMessage;
  7. import org.springframework.web.socket.WebSocketSession;
  8. import org.springframework.web.socket.handler.TextWebSocketHandler;
  9. import java.io.IOException;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. /**
  13. *
  14. * websocket处理类
  15. * 实现WebSocketHandler接口
  16. *
  17. * - websocket建立连接后执行afterConnectionEstablished回调接口
  18. * - websocket关闭连接后执行afterConnectionClosed回调接口
  19. * - websocket接收客户端消息执行handleTextMessage接口
  20. * - websocket传输异常时执行handleTransportError接口
  21. *
  22. * @author lucky_fd
  23. * @since 2024-01-17
  24. */
  25. public class WebSocketHandler extends TextWebSocketHandler {
  26. /**
  27. * 存储websocket客户端连接
  28. * */
  29. private static final Map<String, WebSocketSession> connections = new HashMap<>();
  30. /**
  31. * 建立连接后触发
  32. * */
  33. @Override
  34. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  35. System.out.println("成功建立websocket连接");
  36. // 建立连接后将连接以键值对方式存储,便于后期向客户端发送消息
  37. // 以客户端连接的唯一标识为key,可以通过客户端发送唯一标识
  38. connections.put(session.getRemoteAddress().getHostName(), session);
  39. System.out.println("当前客户端连接数:" + connections.size());
  40. }
  41. /**
  42. * 接收消息
  43. * */
  44. @Override
  45. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  46. System.out.println("收到消息: " + message.getPayload());
  47. // 收到客户端请求消息后进行相应业务处理,返回结果
  48. this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));
  49. }
  50. /**
  51. * 传输异常处理
  52. * */
  53. @Override
  54. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  55. super.handleTransportError(session, exception);
  56. }
  57. /**
  58. * 关闭连接时触发
  59. * */
  60. @Override
  61. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  62. System.out.println("触发关闭websocket连接");
  63. // 移除连接
  64. connections.remove(session.getRemoteAddress().getHostName());
  65. }
  66. @Override
  67. public boolean supportsPartialMessages() {
  68. return super.supportsPartialMessages();
  69. }
  70. /**
  71. * 向连接的客户端发送消息
  72. *
  73. * @author lucky_fd
  74. * @param clientId 客户端标识
  75. * @param message 消息体
  76. **/
  77. public void sendMessage(String clientId, TextMessage message) {
  78. for (String client : connections.keySet()) {
  79. if (client.equals(clientId)) {
  80. try {
  81. WebSocketSession session = connections.get(client);
  82. // 判断连接是否正常
  83. if (session.isOpen()) {
  84. session.sendMessage(message);
  85. }
  86. } catch (IOException e) {
  87. System.out.println(e.getMessage());
  88. }
  89. break;
  90. }
  91. }
  92. }
  93. }

通过消息处理器,在开发中我们就可以实现向指定客户端或所有客户端发送消息,实现相应业务功能。

创建拦截器

拦截器会在握手时触发,可以用来进行权限验证

  1. package com.ci.erp.human.interceptor;
  2. import org.springframework.http.server.ServerHttpRequest;
  3. import org.springframework.http.server.ServerHttpResponse;
  4. import org.springframework.web.socket.WebSocketHandler;
  5. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
  6. import java.util.Map;
  7. /**
  8. *
  9. * Websocket拦截器类
  10. *
  11. * @author lucky_fd
  12. * @since 2024-01-17
  13. */
  14. public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {
  15. @Override
  16. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
  17. System.out.println("拦截器前置触发");
  18. return super.beforeHandshake(request, response, wsHandler, attributes);
  19. }
  20. @Override
  21. public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
  22. System.out.println("拦截器后置触发");
  23. super.afterHandshake(request, response, wsHandler, ex);
  24. }
  25. }

创建前端页面客户端

最后,创建一个简单的HTML页面,用于接收用户输入并显示实时聊天信息。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Spring WebSocket Chat</title>
  6. <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
  7. <script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
  8. </head>
  9. <body>
  10. 请输入:<input type="text" id="message" placeholder="Type your message">
  11. <button onclick="sendMessage()">Send</button>
  12. <button onclick="websocketClose()">关闭连接</button>
  13. <div id="chat"></div>
  14. <script>
  15. var socket = null;
  16. if ('WebSocket' in window) {
  17. // 后端服务port为22900
  18. socket = new WebSocket("ws://localhost:22900/websocket/server");
  19. } else if ('MozWebSocket' in window) {
  20. socket = new MozWebSocket("ws://localhost:22900/websocket/server");
  21. } else {
  22. socket = new SockJS("http://localhost:22900/sockjs/server");
  23. }
  24. // 接收消息触发
  25. socket.onmessage = function (event) {
  26. showMessage(event.data);
  27. };
  28. // 创建连接触发
  29. socket.onopen = function (event) {
  30. console.log(event.type);
  31. };
  32. // 连接异常触发
  33. socket.onerror = function (event) {
  34. console.log(event)
  35. };
  36. // 关闭连接触发
  37. socket.onclose = function (closeEvent) {
  38. console.log(closeEvent.reason);
  39. };
  40. //发送消息
  41. function sendMessage() {
  42. if (socket.readyState === socket.OPEN) {
  43. var message = document.getElementById('message').value;
  44. socket.send(message);
  45. console.log("发送成功!");
  46. } else {
  47. console.log("连接失败!");
  48. }
  49. }
  50. function showMessage(message) {
  51. document.getElementById('chat').innerHTML += '<p>' + message + '</p>';
  52. }
  53. function websocketClose() {
  54. socket.close();
  55. console.log("连接关闭");
  56. }
  57. window.close = function () {
  58. socket.onclose();
  59. };
  60. </script>
  61. </body>
  62. </html>

这个页面使用了WebSocket对象来建立连接,并通过onmessage监听收到的消息。通过输入框发送消息,将会在页面上显示。

测试结果:

后端日志:

image

前端界面:

image

Java客户端

添加依赖

  1. <dependency>
  2. <groupId>org.java-websocket</groupId>
  3. <artifactId>Java-WebSocket</artifactId>
  4. <version>1.4.0</version>
  5. </dependency>

创建客户端类(继承WebsocketClient)

  1. package com.river.websocket;
  2. import org.java_websocket.client.WebSocketClient;
  3. import org.java_websocket.handshake.ServerHandshake;
  4. import java.net.URI;
  5. import java.net.URISyntaxException;
  6. public class MyWebSocketClient extends WebSocketClient {
  7. MyWebSocketClient(String url) throws URISyntaxException {
  8. super(new URI(url));
  9. }
  10. // 建立连接
  11. @Override
  12. public void onOpen(ServerHandshake shake) {
  13. System.out.println(shake.getHttpStatusMessage());
  14. }
  15. // 接收消息
  16. @Override
  17. public void onMessage(String paramString) {
  18. System.out.println(paramString);
  19. }
  20. // 关闭连接
  21. @Override
  22. public void onClose(int paramInt, String paramString, boolean paramBoolean) {
  23. System.out.println("关闭");
  24. }
  25. // 连接异常
  26. @Override
  27. public void onError(Exception e) {
  28. System.out.println("发生错误");
  29. }
  30. }

测试websocket

  1. package com.river.websocket;
  2. import org.java_websocket.enums.ReadyState;
  3. import java.net.URISyntaxException;
  4. /**
  5. * @author lucky_fd
  6. * @date 2024-1-17
  7. */
  8. public class Client {
  9. public static void main(String[] args) throws URISyntaxException, InterruptedException {
  10. MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");
  11. client.connect();
  12. while (client.getReadyState() != ReadyState.OPEN) {
  13. System.out.println("连接状态:" + client.getReadyState());
  14. Thread.sleep(100);
  15. }
  16. client.send("测试数据!");
  17. client.close();
  18. }
  19. }

参考链接:

原文链接:https://www.cnblogs.com/lucky-fd/p/17994082

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号