Springboot 整合 WebSocket ,使用STOMP协议 ,前后端整合实战 (一)

x33g5p2x  于2021-11-21 转载在 Spring  
字(10.0k)|赞(0)|评价(0)|浏览(562)

前言

websocket ,对于我来说已经是老朋友了。 

很久很久以前,我写过两篇websocket 相关的文章。

一篇极简风,最最最基础的方式整合websocket :

《SpringBoot 整合WebSocket 简单实战案例》

地址: SpringBoot 整合WebSocket 简单实战案例_默默不代表沉默-CSDN博客_springboot websocket

一篇极限风,最最最完善的方式整合websocket (stomp+rabbitmq处理负载):

Springboot 整合Websocket,Stomp协议,使用rabbitmq做消息代理,消息缓存

地址: Springboot 整合Websocket+Stomp协议+RabbitMQ做消息代理 实例教程_默默不代表沉默-CSDN博客_rabbitmq stomp协议

但是按照最近看官们给我反应情况来看, 一个极简不符合需求,因为看官们虽然想简单,但是也想用stomp。

然而那个极限的呢,又太复杂,看官们不乐意去整合rabbitmq。

那么,这篇文章的意义就出来了。
 

正文

本篇内容:

1.后端整合websocket (STOMP协议)

2.群发、指定单发

3.前端简单页面示例(接收、发送消息)

事不宜迟,开始敲代码。

先看下这次实战案例项目结构:

  1. pom.xml 核心依赖的导入:
     
  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-websocket</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-test</artifactId>
  13. <scope>test</scope>
  14. </dependency>
  15. </dependencies>

2.application.yml  (我就单纯设置一下端口)
 

  1. server:
  2. port: 9908

3.WebSocketConfig.java

  1. import org.springframework.context.annotation.Configuration;
  2. import org.springframework.messaging.simp.config.MessageBrokerRegistry;
  3. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
  4. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
  5. import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
  6. /**
  7. * @Author JCccc
  8. * @Description EnableWebSocketMessageBroker-注解开启STOMP协议来传输基于代理的消息,此时控制器支持使用@MessageMapping
  9. * @Date 2021/6/30 8:53
  10. */
  11. @Configuration
  12. @EnableWebSocketMessageBroker
  13. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  14. @Override
  15. public void configureMessageBroker(MessageBrokerRegistry config) {
  16. //topic用来广播,user用来实现点对点
  17. config.enableSimpleBroker("/topic", "/user");
  18. }
  19. /**
  20. * 开放节点
  21. * @param registry
  22. */
  23. @Override
  24. public void registerStompEndpoints(StompEndpointRegistry registry) {
  25. //注册两个STOMP的endpoint,分别用于广播和点对点
  26. //广播
  27. registry.addEndpoint("/publicServer").setAllowedOrigins("*").withSockJS();
  28. //点对点
  29. registry.addEndpoint("/privateServer").setAllowedOrigins("*").withSockJS();
  30. }
  31. }

4.推送消息的实体类 Message.java

  1. /**
  2. * @Author JCccc
  3. * @Description
  4. * @Date 2021/8/20 9:26
  5. */
  6. public class Message {
  7. /**
  8. * 消息编码
  9. */
  10. private String code;
  11. /**
  12. * 来自(保证唯一)
  13. */
  14. private String form;
  15. /**
  16. * 去自(保证唯一)
  17. */
  18. private String to;
  19. /**
  20. * 内容
  21. */
  22. private String content;
  23. public String getCode() {
  24. return code;
  25. }
  26. public void setCode(String code) {
  27. this.code = code;
  28. }
  29. public String getForm() {
  30. return form;
  31. }
  32. public void setForm(String form) {
  33. this.form = form;
  34. }
  35. public String getTo() {
  36. return to;
  37. }
  38. public void setTo(String to) {
  39. this.to = to;
  40. }
  41. public String getContent() {
  42. return content;
  43. }
  44. public void setContent(String content) {
  45. this.content = content;
  46. }
  47. }

5.前端简单调试页面 

① publicExample.html 监听广播消息的测试页面

  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. <title>等系统推消息</title>
  5. <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
  6. <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
  7. <script src="https://code.jquery.com/jquery-3.2.0.min.js"
  8. integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>
  9. <script type="text/javascript">
  10. var stompClient = null;
  11. function setConnected(connected) {
  12. document.getElementById("connect").disabled = connected;
  13. document.getElementById("disconnect").disabled = !connected;
  14. $("#response").html();
  15. }
  16. function connect() {
  17. var socket = new SockJS("http://localhost:9908/publicServer");
  18. stompClient = Stomp.over(socket);
  19. stompClient.connect({}, function (frame) {
  20. setConnected(true);
  21. console.log('Connected: ' + frame);
  22. stompClient.subscribe('/topic/all', function (response) {
  23. var responseData = document.getElementById('responseData');
  24. var p = document.createElement('p');
  25. p.style.wordWrap = 'break-word';
  26. p.appendChild(document.createTextNode(response.body));
  27. responseData.appendChild(p);
  28. });
  29. },{});
  30. }
  31. function disconnect() {
  32. if (stompClient != null) {
  33. stompClient.disconnect();
  34. }
  35. setConnected(false);
  36. console.log("Disconnected");
  37. }
  38. function sendMsg() {
  39. var content = document.getElementById('content').value;
  40. stompClient.send("/all",{},JSON.stringify({'content': content}));
  41. }
  42. </script>
  43. </head>
  44. <body onload="disconnect()">
  45. <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
  46. enabled. Please enable
  47. Javascript and reload this page!</h2></noscript>
  48. <div>
  49. <div>
  50. <labal>连接广播频道</labal>
  51. <button id="connect" onclick="connect();">Connect</button>
  52. <labal>取消连接</labal>
  53. <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
  54. </div>
  55. <div id="conversationDiv">
  56. <labal>广播消息</labal>
  57. <input type="text" id="content"/>
  58. <button id="sendMsg" onclick="sendMsg();">Send</button>
  59. </div>
  60. <div>
  61. <labal>接收到的消息:</labal>
  62. <p id="responseData"></p>
  63. </div>
  64. </div>
  65. </body>
  66. </html>

简析: 

趁热打铁,我们模拟系统后端给前端推送广播消息,通过接口模拟:
 

TestController.java

  1. /**
  2. * @Author JCccc
  3. * @Description
  4. * @Date 2021/8/20 8:53
  5. */
  6. @Controller
  7. public class TestController {
  8. @Autowired
  9. public SimpMessagingTemplate template;
  10. /**
  11. * 广播
  12. *
  13. * @param msg
  14. */
  15. @ResponseBody
  16. @RequestMapping("/pushToAll")
  17. public void subscribe( @RequestBody Message msg) {
  18. template.convertAndSend("/topic/all", msg.getContent());
  19. }
  20. }

简析:

我们推送消息,直接用 SimpMessagingTemplate ,

用的是convertAndSend 广播方式推送到对于的主题目的地 destination 。

(可以看到其实还有convertAndSendToUser ,不着急,后面会说,这是发送给某个连接用户的)

直接把项目跑起来,打开页面开始测试:

我们先点击connect ,连接成功:

可以看到实际上stomp.min.js 最终也是转化成为 ws/wss这种方式成功连接:

调用测试接口,推送广播消息:

在console其实也能看到:

广播功能就到这,接下来是 点对点。

前端页面:

privateExample.html

  1. <html>
  2. <head>
  3. <meta charset="UTF-8">
  4. <title>聊起来</title>
  5. <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
  6. <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
  7. <script src="https://code.jquery.com/jquery-3.2.0.min.js"
  8. integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>
  9. <script type="text/javascript">
  10. var stompClient = null;
  11. function setConnected(connected) {
  12. document.getElementById("connect").disabled = connected;
  13. document.getElementById("disconnect").disabled = !connected;
  14. $("#response").html();
  15. }
  16. function connect() {
  17. var socket = new SockJS("http://localhost:9908/privateServer");
  18. stompClient = Stomp.over(socket);
  19. stompClient.heartbeat.outgoing = 20000;
  20. // client will send heartbeats every 20000ms
  21. stompClient.heartbeat.incoming = 0;
  22. stompClient.connect({}, function (frame) {
  23. setConnected(true);
  24. console.log('Connected: ' + frame);
  25. stompClient.subscribe('/user/' + document.getElementById('user').value + '/message', function (response) {
  26. var responseData = document.getElementById('responseData');
  27. var p = document.createElement('p');
  28. p.style.wordWrap = 'break-word';
  29. p.appendChild(document.createTextNode(response.body));
  30. responseData.appendChild(p);
  31. });
  32. });
  33. }
  34. function disconnect() {
  35. if (stompClient != null) {
  36. stompClient.disconnect();
  37. }
  38. setConnected(false);
  39. console.log("Disconnected");
  40. }
  41. function sendMsg() {
  42. var headers = {
  43. login: 'mylogin',
  44. passcode: 'mypasscode',
  45. // additional header
  46. 'accessToken': 'HWPO325J9814GBHJF933'
  47. };
  48. var content = document.getElementById('content').value;
  49. var to = document.getElementById('to').value;
  50. stompClient.send("/alone", {'accessToken': 'HWPO325J9814GBHJF933'}, JSON.stringify({
  51. 'content': content,
  52. 'to': to
  53. }));
  54. }
  55. </script>
  56. </head>
  57. <body onload="disconnect()">
  58. <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
  59. enabled. Please enable
  60. Javascript and reload this page!</h2></noscript>
  61. <div>
  62. <div>
  63. <labal>连接用户</labal>
  64. <input type="text" id="user"/>
  65. <button id="connect" onclick="connect();">Connect</button>
  66. </div>
  67. <div>
  68. <labal>取消连接</labal>
  69. <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
  70. </div>
  71. <div id="conversationDiv">
  72. <labal>发送消息</labal>
  73. <div>
  74. <labal>内容</labal>
  75. <input type="text" id="content"/>
  76. </div>
  77. <div>
  78. <labal>发给谁</labal>
  79. <input type="text" id="to"/>
  80. </div>
  81. <button id="sendMsg" onclick="sendMsg();">Send</button>
  82. </div>
  83. <div>
  84. <labal>接收到的消息:</labal>
  85. <p id="responseData"></p>
  86. </div>
  87. </div>
  88. </body>
  89. </html>

简析:

趁热打铁,我们模拟系统后端给前端推送点对点消息(指定某个用户),通过接口模拟:

  1. /**
  2. * 点对点
  3. */
  4. @ResponseBody
  5. @RequestMapping("/pushToOne")
  6. public void queue(@RequestBody Message msg) {
  7. System.out.println("进入方法");
  8. /*使用convertAndSendToUser方法,第一个参数为用户id,此时js中的订阅地址为
  9. "/user/" + 用户Id + "/message",其中"/user"是固定的*/
  10. template.convertAndSendToUser(msg.getTo(), "/message", msg.getContent());
  11. }

用的是convertAndSendToUser 推送到指定的用户 ,对于的主题目的地 destination(/message) 

也许看到这里,你会觉得很奇怪,为什么我们推的主题是 /message,但是前端订阅的却是
 
"/user/" + 用户Id + "/message"

我们直接看源码:

ok,应该不用多说,代码帮我们自己拼接起来了,跟前端订阅规则保持一致。

直接跑项目,测试一下:

模拟我们连接的用户标识 19901 ,连接成功

使用postman调用我们的测试接口,模拟系统指定推送消息到 19901 这个人 :

然后也给20011发送一下消息:

然后看到对应的用户也能收到对应的消息:

对点推送,广播推送,也已经完毕了 。

这种情况就是相当于使用http接口方式,去撮合后端服务做 消息推送。

其实spring还提供了一些好玩的注解,

@MessageMapping  这个注解是对称于  @EnableWebSocketMessageBroker

也就是说,如果我们使用@EnableWebSocketMessageBroker ,那么我们在接口上面其实就能直接使用  @MessageMapping。

怎么用?

然后前端代码里面 的使用:

这样我们就可以通过前端直接调用相关接口了:

个人认为其实没有必要使用这个注解,直接通过前端调用后端服务代码,我们服务端来根据Message里面 的 发送方、接收方、消息类型(点对点、广播)就可以直接完成相关也业务场景了。

ok,该篇就到此。

下一篇,给大家带来怎么解决websocket负载问题 。

相关文章