【转】springboot+websocket+sockjs进行消息推送【基于STOMP协议】

原文地址:https://blog.csdn.net/liyongzhi1992/article/details/81221103

1.浅谈WebSocket

WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就可以建立一条快速通道,两者就可以实现数据互传了。说白了,就是打破了传统的http协议的无状态传输(只能浏览器请求,服务端响应),websocket全双工通讯,就是浏览器和服务器进行一次握手,浏览器可以随时给服务器发送信息,服务器也可以随时主动发送信息给浏览器了。对webSocket原理有兴趣的客官,可以自行百度。

2.环境搭建

因为是根据项目的需求来的,所以这里我只介绍在SpringBoot下使用WebSocket的其中一种实现【STOMP协议】。因此整个工程涉及websocket使用的大致框架为SpringBoot+Maven+websocket,其他框架的基础搭建,我这里就不说了,相信各位也都很熟悉,我就直接集成websocket了。

在pox.xml加上对springBoot对WebSocket的支持:

 

<!-- webSocket -->

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

这样SpringBoot就和WebSocket集成好了,我们就可以直接使用SpringBoot提供对WebSocket操作的API了

3.编码实现

①在Spring上下文中添加对WebSocket的配置

 

import org.springframework.context.annotation.Configuration;

import org.springframework.messaging.simp.config.MessageBrokerRegistry;

import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

 

/**

* 配置WebSocket

*/

@Configuration

//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样

@EnableWebSocketMessageBroker

public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

@Override

//注册STOMP协议的节点(endpoint),并映射指定的url

public void registerStompEndpoints(StompEndpointRegistry registry) {

//注册一个STOMP的endpoint,并指定使用SockJS协议

registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS();

}

@Override

//配置消息代理(Message Broker)

public void configureMessageBroker(MessageBrokerRegistry registry) {

//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理

registry.enableSimpleBroker("/topic","/user");

//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/

registry.setUserDestinationPrefix("/user");

}

}

介绍以上几个相关的注解和方法:

1.@EnableWebSocketMessageBroker:开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样。

2.AbstractWebSocketMessageBrokerConfigurer:继承WebSocket消息代理的类,配置相关信息。

3.registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS(); 添加一个访问端点“/endpointGym”,客户端打开双通道时需要的url,允许所有的域名跨域访问,指定使用SockJS协议。

4. registry.enableSimpleBroker("/topic","/user"); 配置一个/topic广播消息代理和“/user”一对一消息代理

5. registry.setUserDestinationPrefix("/user");点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/

 

②实现服务器主动向客户端推送消息

SpringBoot封装得太好,webSocket用起来太简单(好处:用起来方便,坏处:你不知道底层实现)

1.一对多的实现:

先上后台java的代码

 
  1. package com.cheng.sbjm.boot;

  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.messaging.simp.SimpMessagingTemplate;

  5. import org.springframework.scheduling.annotation.Scheduled;

  6. import org.springframework.stereotype.Controller;

  7. import com.cheng.sbjm.domain.User;

  8.  
  9. @Controller

  10. public class WebSocketController {

  11.  
  12. @Autowired

  13. private SimpMessagingTemplate template;

  14.  
  15. //广播推送消息

  16. @Scheduled(fixedRate = 10000)

  17. public void sendTopicMessage() {

  18. System.out.println("后台广播推送!");

  19. User user=new User();

  20. user.setUserName("oyzc");

  21. user.setAge(10);

  22. this.template.convertAndSend("/topic/getResponse",user);

  23. }

  24. }

简单介绍一下

1.SimpMessagingTemplate:SpringBoot提供操作WebSocket的对象

2.@Scheduled(fixedRate = 10000):为了测试,定时10S执行这个方法,向客户端推送

3.template.convertAndSend("/topic/getResponse",new AricResponse("后台实时推送:,Oyzc!")); :直接向前端推送消息。

3.1参数一:客户端监听指定通道时,设定的访问服务器的URL

3.2参数二:发送的消息(可以是对象、字符串等等)

 

在上客户端的代码(PC现代浏览器)

html页面:

 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <title>websocket.html</title>

  5. <meta name="keywords" content="keyword1,keyword2,keyword3">

  6. <meta name="description" content="this is my page">

  7. <meta name="content-type" content="text/html" charset="UTF-8">

  8. <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

  9. </head>

  10. <body>

  11. <div>

  12. <p id="response"></p>

  13. </div>

  14.  
  15. <!-- 独立JS -->

  16. <script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>

  17. <script type="text/javascript" src="webSocket.js" charset="utf-8"></script>

  18. <script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>

  19. <script type="text/javascript" src="stomp.js" charset="utf-8"></script>

  20. </body>

  21. </html>

JS代码[webSocket.js]

 
  1. var stompClient = null;

  2. //加载完浏览器后 调用connect(),打开双通道

  3. $(function(){

  4. //打开双通道

  5. connect()

  6. })

  7. //强制关闭浏览器 调用websocket.close(),进行正常关闭

  8. window.onunload = function() {

  9. disconnect()

  10. }

  11. function connect(){

  12. var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"

  13. stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端

  14. stompClient.connect({},function(frame){//连接WebSocket服务端

  15. console.log('Connected:' + frame);

  16. //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息

  17. stompClient.subscribe('/topic/getResponse',function(response){

  18. showResponse(JSON.parse(response.body));

  19. });

  20. });

  21. }

  22.  
  23. //关闭双通道

  24. function disconnect(){

  25. if(stompClient != null) {

  26. stompClient.disconnect();

  27. }

  28. console.log("Disconnected");

  29. }

  30. function showResponse(message){

  31. var response = $("#response");

  32. response.append("<p>"+message.userName+"</p>");

  33. }

值得注意的是,只需要在连接服务器注册端点endPoint时,写访问服务器的全路径URL:

new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc');

其他监听指定服务器广播的URL不需要写全路径

 stompClient.subscribe('/topic/getResponse',function(response){
                showResponse(JSON.parse(response.body));

            });

2.一对一的实现

先上后台java的代码

 
  1. package com.cheng.sbjm.boot;

  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.messaging.simp.SimpMessagingTemplate;

  5. import org.springframework.scheduling.annotation.Scheduled;

  6. import org.springframework.stereotype.Controller;

  7. import com.cheng.sbjm.domain.User;

  8.  
  9.  
  10. @Controller

  11. public class WebSocketController {

  12.  
  13. @Autowired

  14. private SimpMessagingTemplate template;

  15.  
  16. //一对一推送消息

  17. @Scheduled(fixedRate = 10000)

  18. public void sendQueueMessage() {

  19. System.out.println("后台一对一推送!");

  20. User user=new User();

  21. user.setUserId(1);

  22. user.setUserName("oyzc");

  23. user.setAge(10);

  24. this.template.convertAndSendToUser(user.getUserId()+"","/queue/getResponse",user);

  25. }

  26. }

简单介绍一下:

1.SimpMessagingTemplate:SpringBoot提供操作WebSocket的对象

2.@Scheduled(fixedRate = 10000):为了测试,定时10S执行这个方法,向客户端推送

3.template.convertAndSendToUser(user.getUserId()+"","/queue/getResponse",user); :直接向前端推送消息。

3.1参数一:指定客户端接收的用户标识(一般用用户ID)

3.2参数二:客户端监听指定通道时,设定的访问服务器的URL(客户端访问URL跟广播有些许不同)

3.3参数三:向目标发送消息体(实体、字符串等等)

 

在上客户端的代码(PC现代浏览器)

html页面:

 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <title>websocket.html</title>

  5.  
  6. <meta name="keywords" content="keyword1,keyword2,keyword3">

  7. <meta name="description" content="this is my page">

  8. <meta name="content-type" content="text/html" charset="UTF-8">

  9. <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

  10. <!-- 独立css -->

  11. </head>

  12. <body>

  13. <div>

  14. <p id="response"></p>

  15. </div>

  16. <!-- 独立JS -->

  17. <script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>

  18. <script type="text/javascript" src="webSocket.js" charset="utf-8"></script>

  19. <script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>

  20. <script type="text/javascript" src="stomp.js" charset="utf-8"></script>

  21. </body>

  22. </html>

JS代码[webSocket.js]

 
  1. var stompClient = null;

  2. //加载完浏览器后 调用connect(),打开双通道

  3. $(function(){

  4. //打开双通道

  5. connect()

  6. })

  7. //强制关闭浏览器 调用websocket.close(),进行正常关闭

  8. window.onunload = function() {

  9. disconnect()

  10. }

  11. function connect(){

  12. var userId=1;

  13. var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"

  14. stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端

  15. stompClient.connect({},function(frame){//连接WebSocket服务端

  16. console.log('Connected:' + frame);

  17. //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息

  18. stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){

  19. var code=JSON.parse(response.body);

  20. showResponse(code)

  21. });

  22. });

  23. }

  24. //关闭双通道

  25. function disconnect(){

  26. if(stompClient != null) {

  27. stompClient.disconnect();

  28. }

  29. console.log("Disconnected");

  30. }

  31. function showResponse(message){

  32. var response = $("#response");

  33. response.append("<p>只有userID为"+message.userId+"的人才能收到</p>");

  34. }

与广播不同的是,在指定通道的URL加个用户标识:

 stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){
            var code=JSON.parse(response.body);                      
            showResponse(code)              

            });

该标识userId必须与服务器推送消息时设置的用户标识一致

 

以上就是实现服务器实时向客户端推送消息,各位可以按照各自的需求进行配合使用。

 

③实现客户端与服务器之间的直接交互,聊天室demo[在②的基础上添加了一些代码]

1.在webSocket配置中,增加2个WebSocket的代理

 
  1.  
  2. package com.cheng.sbjm.configure;

  3.  
  4. import org.springframework.context.annotation.Configuration;

  5. import org.springframework.messaging.simp.config.MessageBrokerRegistry;

  6. import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;

  7. import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;

  8. import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

  9.  
  10. /**

  11. * 配置WebSocket

  12. */

  13. @Configuration

  14. //注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样

  15. @EnableWebSocketMessageBroker

  16. public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

  17.  
  18. @Override

  19. //注册STOMP协议的节点(endpoint),并映射指定的url

  20. public void registerStompEndpoints(StompEndpointRegistry registry) {

  21. //注册一个STOMP的endpoint,并指定使用SockJS协议

  22. registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS();

  23. }

  24.  
  25. @Override

  26. //配置消息代理(Message Broker)

  27. public void configureMessageBroker(MessageBrokerRegistry registry) {

  28. //点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理,群发(mass),单独聊天(alone)

  29. registry.enableSimpleBroker("/topic","/user","/mass","/alone");

  30. //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/

  31. registry.setUserDestinationPrefix("/user");

  32.  
  33. }

  34.  
  35. }

"/mass"用以代理群发消息

"/alone"用以代码一对一聊天

2.java后台实现

 
  1. package com.cheng.sbjm.boot;

  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.messaging.handler.annotation.MessageMapping;

  5. import org.springframework.messaging.handler.annotation.SendTo;

  6. import org.springframework.messaging.simp.SimpMessagingTemplate;

  7. import org.springframework.stereotype.Controller;

  8. import com.cheng.sbjm.onput.ChatRoomRequest;

  9. import com.cheng.sbjm.onput.ChatRoomResponse;

  10.  
  11.  
  12. @Controller

  13. public class WebSocketController {

  14.  
  15. @Autowired

  16. private SimpMessagingTemplate template;

  17. //客户端主动发送消息到服务端,服务端马上回应指定的客户端消息

  18. //类似http无状态请求,但是有质的区别

  19. //websocket可以从服务器指定发送哪个客户端,而不像http只能响应请求端

  20.  
  21. //群发

  22. @MessageMapping("/massRequest")

  23. //SendTo 发送至 Broker 下的指定订阅路径

  24. @SendTo("/mass/getResponse")

  25. public ChatRoomResponse mass(ChatRoomRequest chatRoomRequest){

  26. //方法用于群发测试

  27. System.out.println("name = " + chatRoomRequest.getName());

  28. System.out.println("chatValue = " + chatRoomRequest.getChatValue());

  29. ChatRoomResponse response=new ChatRoomResponse();

  30. response.setName(chatRoomRequest.getName());

  31. response.setChatValue(chatRoomRequest.getChatValue());

  32. return response;

  33. }

  34.  
  35. //单独聊天

  36. @MessageMapping("/aloneRequest")

  37. public ChatRoomResponse alone(ChatRoomRequest chatRoomRequest){

  38. //方法用于一对一测试

  39. System.out.println("userId = " + chatRoomRequest.getUserId());

  40. System.out.println("name = " + chatRoomRequest.getName());

  41. System.out.println("chatValue = " + chatRoomRequest.getChatValue());

  42. ChatRoomResponse response=new ChatRoomResponse();

  43. response.setName(chatRoomRequest.getName());

  44. response.setChatValue(chatRoomRequest.getChatValue());

  45. this.template.convertAndSendToUser(chatRoomRequest.getUserId()+"","/alone/getResponse",response);

  46. return response;

  47. }

  48. }

简单介绍新的注解一下:

一.@MessageMapping("/massRequest"):类似与@RequestMapping,客户端请求服务器的URL,前提是双方端点已经打开

二.@SendTo("/mass/getResponse"):作用跟convertAndSend类似,广播发给与该通道相连的客户端

其他已经在前面解释过了。

3.html代码

 
  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <title>login.html</title>

  5.  
  6. <meta name="keywords" content="keyword1,keyword2,keyword3">

  7. <meta name="description" content="this is my page">

  8. <meta name="content-type" content="text/html" charset="UTF-8">

  9. <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->

  10. <!-- 独立css -->

  11. <link rel="stylesheet" type="text/css" href="chatroom.css">

  12. </head>

  13.  
  14. <body>

  15. <div>

  16. <div style="float:left;width:40%">

  17. <p>请选择你是谁:</p>

  18. <select id="selectName" onchange="sendAloneUser();">

  19. <option value="1">请选择</option>

  20. <option value="ALong">ALong</option>

  21. <option value="AKan">AKan</option>

  22. <option value="AYuan">AYuan</option>

  23. <option value="ALai">ALai</option>

  24. <option value="ASheng">ASheng</option>

  25. </select>

  26. <div class="chatWindow">

  27. <p style="color:darkgrey">群聊:</p>

  28. <section id="chatRecord" class="chatRecord">

  29. <p id="titleval" style="color:#CD2626;"></p>

  30. </section>

  31. <section class="sendWindow">

  32. <textarea name="sendChatValue" id="sendChatValue" class="sendChatValue"></textarea>

  33. <input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendMassMessage()" value="发送">

  34. </section>

  35. </div>

  36. </div>

  37.  
  38.  
  39. <div style="float:right; width:40%">

  40. <p>请选择你要发给谁:</p>

  41. <select id="selectName2">

  42. <option value="1">请选择</option>

  43. <option value="ALong">ALong</option>

  44. <option value="AKan">AKan</option>

  45. <option value="AYuan">AYuan</option>

  46. <option value="ALai">ALai</option>

  47. <option value="ASheng">ASheng</option>

  48. </select>

  49. <div class="chatWindow">

  50. <p style="color:darkgrey">单独聊:</p>

  51. <section id="chatRecord2" class="chatRecord">

  52. <p id="titleval" style="color:#CD2626;"></p>

  53. </section>

  54. <section class="sendWindow">

  55. <textarea name="sendChatValue2" id="sendChatValue2" class="sendChatValue"></textarea>

  56. <input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendAloneMessage()" value="发送">

  57. </section>

  58. </div>

  59. </div>

  60. </div>

  61. <!-- 独立JS -->

  62. <script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>

  63. <script type="text/javascript" src="chatroom.js" charset="utf-8"></script>

  64. <script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>

  65. <script type="text/javascript" src="stomp.js" charset="utf-8"></script>

  66. </body>

  67. </html>

JS代码[chatroom.js]:

 
  1. var stompClient = null;

  2.  
  3. //加载完浏览器后 调用connect(),打开双通道

  4. $(function(){

  5. //打开双通道

  6. connect()

  7. })

  8.  
  9. //强制关闭浏览器 调用websocket.close(),进行正常关闭

  10. window.onunload = function() {

  11. disconnect()

  12. }

  13.  
  14. //打开双通道

  15. function connect(){

  16. var socket = new SockJS('http://172.16.0.56:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointAric"

  17. stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端

  18. stompClient.connect({},function(frame){//连接WebSocket服务端

  19.  
  20. console.log('Connected:' + frame);

  21. //广播接收信息

  22. stompTopic();

  23.  
  24. });

  25. }

  26.  
  27. //关闭双通道

  28. function disconnect(){

  29. if(stompClient != null) {

  30. stompClient.disconnect();

  31. }

  32. console.log("Disconnected");

  33. }

  34.  
  35. //广播(一对多)

  36. function stompTopic(){

  37. //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(广播接收信息)

  38. stompClient.subscribe('/mass/getResponse',function(response){

  39. var message=JSON.parse(response.body);

  40. //展示广播的接收的内容接收

  41. var response = $("#chatRecord");

  42. response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");

  43. });

  44. }

  45.  
  46. //列队(一对一)

  47. function stompQueue(){

  48.  
  49. var userId=$("#selectName").val();

  50. alert("监听:"+userId)

  51. //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(队列接收信息)

  52. stompClient.subscribe('/user/' + userId + '/alone/getResponse',function(response){

  53. var message=JSON.parse(response.body);

  54. //展示一对一的接收的内容接收

  55. var response = $("#chatRecord2");

  56. response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");

  57. });

  58. }

  59.  
  60. //选择发送给谁的时候触发连接服务器

  61. function sendAloneUser(){

  62. stompQueue();

  63. }

  64.  
  65. //群发

  66. function sendMassMessage(){

  67. var postValue={};

  68. var chatValue=$("#sendChatValue");

  69. var userName=$("#selectName").val();

  70. postValue.name=userName;

  71. postValue.chatValue=chatValue.val();

  72. if(userName==1||userName==null){

  73. alert("请选择你是谁!");

  74. return;

  75. }

  76. if(chatValue==""||userName==null){

  77. alert("不能发送空消息!");

  78. return;

  79. }

  80. stompClient.send("/massRequest",{},JSON.stringify(postValue));

  81. chatValue.val("");

  82. }

  83. //单独发

  84. function sendAloneMessage(){

  85. var postValue={};

  86. var chatValue=$("#sendChatValue2");

  87. var userName=$("#selectName").val();

  88. var sendToId=$("#selectName2").val();

  89. var response = $("#chatRecord2");

  90. postValue.name=userName;

  91. postValue.chatValue=chatValue.val();

  92. postValue.userId=sendToId;

  93. if(userName==1||userName==null){

  94. alert("请选择你是谁!");

  95. return;

  96. }

  97. if(sendToId==1||sendToId==null){

  98. alert("请选择你要发给谁!");

  99. return;

  100. }

  101. if(chatValue==""||userName==null){

  102. alert("不能发送空消息!");

  103. return;

  104. }

  105. stompClient.send("/aloneRequest",{},JSON.stringify(postValue));

  106. response.append("<p><span>"+userName+":</span><span>"+chatValue.val()+"</span></p>");

  107. chatValue.val("");

  108. }

chatroom.css

 
  1. .chatWindow{

  2.  
  3. width: 100%;

  4. height: 500px;

  5. border: 1px solid blue;

  6. }

  7. .chatRecord{

  8. width: 100%;

  9. height: 400px;

  10. border-bottom: 1px solid blue;

  11. line-height:20px;

  12. overflow:auto;

  13. overflow-x:hidden;

  14. }

  15. .sendWindow{

  16. width: 100%;

  17. height: 200px;

  18. }

  19. .sendChatValue{

  20.  
  21. width: 90%;

  22. height: 40px;

  23.  
  24. }

另外还需要的3个JS包,jquery.min.js、sockjs.min.js、stomp.js。

如有错漏,请各位大神指教!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值