zoukankan      html  css  js  c++  java
  • springboot+websocket+sockjs进行消息推送【基于STOMP协议】

    springboot+websocket+sockjs进行消息推送【基于STOMP协议】
    
    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的代码
    
    package com.cheng.sbjm.boot;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Controller;
    import com.cheng.sbjm.domain.User;
     
    @Controller
    public class WebSocketController {
    	
        @Autowired
        private SimpMessagingTemplate template;
    	
        //广播推送消息
        @Scheduled(fixedRate = 10000)
        public void sendTopicMessage() {
    	System.out.println("后台广播推送!");
    	User user=new User();
    	user.setUserName("oyzc");
    	user.setAge(10);
        	this.template.convertAndSend("/topic/getResponse",user);
        }
    }
    简单介绍一下
    
    1.SimpMessagingTemplate:SpringBoot提供操作WebSocket的对象
    
    2.@Scheduled(fixedRate = 10000):为了测试,定时10S执行这个方法,向客户端推送
    
    3.template.convertAndSend("/topic/getResponse",new AricResponse("后台实时推送:,Oyzc!")); :直接向前端推送消息。
    
    3.1参数一:客户端监听指定通道时,设定的访问服务器的URL
    
    3.2参数二:发送的消息(可以是对象、字符串等等)
    
     
    
    在上客户端的代码(PC现代浏览器)
    
    html页面:
    
    <!DOCTYPE html>
    <html>
      <head>
        <title>websocket.html</title>	
        <meta name="keywords" content="keyword1,keyword2,keyword3">
        <meta name="description" content="this is my page">
        <meta name="content-type" content="text/html" charset="UTF-8">
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->	
      </head>  
      <body>
    	<div>  
    	    <p id="response"></p>
    	</div>
    	
    	<!-- 独立JS -->
    	<script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>
    	<script type="text/javascript" src="webSocket.js" charset="utf-8"></script>
    	<script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>
    	<script type="text/javascript" src="stomp.js" charset="utf-8"></script>
      </body>
    </html>
    JS代码[webSocket.js]
    
    var stompClient = null;	
        //加载完浏览器后  调用connect(),打开双通道
        $(function(){	
    	//打开双通道
    	connect()
        })
        //强制关闭浏览器  调用websocket.close(),进行正常关闭
        window.onunload = function() {
        	disconnect()
        }
        function connect(){
            var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"
            stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
            stompClient.connect({},function(frame){//连接WebSocket服务端     
                console.log('Connected:' + frame);
                //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息
                stompClient.subscribe('/topic/getResponse',function(response){
                    showResponse(JSON.parse(response.body));
                });
            });
        }
     
        //关闭双通道
        function disconnect(){
            if(stompClient != null) {
                stompClient.disconnect();
            }
            console.log("Disconnected");
        }
        function showResponse(message){
            var response = $("#response");
            response.append("<p>"+message.userName+"</p>");
        }
    值得注意的是,只需要在连接服务器注册端点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的代码
    
    package com.cheng.sbjm.boot;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Controller;
    import com.cheng.sbjm.domain.User;
     
     
    @Controller
    public class WebSocketController {
    	
        @Autowired
        private SimpMessagingTemplate template;
    	
        //一对一推送消息
        @Scheduled(fixedRate = 10000)
        public void sendQueueMessage() {
    	System.out.println("后台一对一推送!");
    	User user=new User();
    	user.setUserId(1);
    	user.setUserName("oyzc");
    	user.setAge(10);
    	this.template.convertAndSendToUser(user.getUserId()+"","/queue/getResponse",user);
        }
    }
    简单介绍一下:
    
    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页面:
    
    <!DOCTYPE html>
    <html>
      <head>
        <title>websocket.html</title>
    	
        <meta name="keywords" content="keyword1,keyword2,keyword3">
        <meta name="description" content="this is my page">
        <meta name="content-type" content="text/html" charset="UTF-8">
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
    	<!-- 独立css -->
      </head>  
      <body>
    	<div>  
    	    <p id="response"></p>
    	</div>	
    	<!-- 独立JS -->
    	<script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>
    	<script type="text/javascript" src="webSocket.js" charset="utf-8"></script>
    	<script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>
    	<script type="text/javascript" src="stomp.js" charset="utf-8"></script>
      </body>
    </html>
    JS代码[webSocket.js]
    
     var stompClient = null;	
        //加载完浏览器后  调用connect(),打开双通道
        $(function(){	
    	//打开双通道
    	connect()
        })	
        //强制关闭浏览器  调用websocket.close(),进行正常关闭
        window.onunload = function() {
        	disconnect()
        }
        function connect(){
        	var userId=1;
            var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"
            stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
            stompClient.connect({},function(frame){//连接WebSocket服务端         
                console.log('Connected:' + frame);
                //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息
                stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){
                	var code=JSON.parse(response.body);         	            		
                	showResponse(code)              	
                });
            });
        }
        //关闭双通道
        function disconnect(){
            if(stompClient != null) {
                stompClient.disconnect();
            }
            console.log("Disconnected");
        }
        function showResponse(message){
            var response = $("#response");
            response.append("<p>只有userID为"+message.userId+"的人才能收到</p>");
        }
    与广播不同的是,在指定通道的URL加个用户标识:
    
     stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){
                var code=JSON.parse(response.body);                      
                showResponse(code)              
    
                });
    
    该标识userId必须与服务器推送消息时设置的用户标识一致
    
    
    
     
    
    
    
    以上就是实现服务器实时向客户端推送消息,各位可以按照各自的需求进行配合使用。
    
     
    
    ③实现客户端与服务器之间的直接交互,聊天室demo[在②的基础上添加了一些代码]
    
    1.在webSocket配置中,增加2个WebSocket的代理
    
     
    package com.cheng.sbjm.configure;
     
    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消息代理,群发(mass),单独聊天(alone)
            registry.enableSimpleBroker("/topic","/user","/mass","/alone");
            //点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
            registry.setUserDestinationPrefix("/user");
     
        }
     
    }
    "/mass"用以代理群发消息
    
    "/alone"用以代码一对一聊天
    
    2.java后台实现
    
    package com.cheng.sbjm.boot;
     
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.messaging.handler.annotation.MessageMapping;
    import org.springframework.messaging.handler.annotation.SendTo;
    import org.springframework.messaging.simp.SimpMessagingTemplate;
    import org.springframework.stereotype.Controller;
    import com.cheng.sbjm.onput.ChatRoomRequest;
    import com.cheng.sbjm.onput.ChatRoomResponse;
     
     
    @Controller
    public class WebSocketController {
    	
        @Autowired
        private SimpMessagingTemplate template;
        //客户端主动发送消息到服务端,服务端马上回应指定的客户端消息
        //类似http无状态请求,但是有质的区别
        //websocket可以从服务器指定发送哪个客户端,而不像http只能响应请求端
        
        //群发
        @MessageMapping("/massRequest")
        //SendTo 发送至 Broker 下的指定订阅路径
        @SendTo("/mass/getResponse")
        public ChatRoomResponse mass(ChatRoomRequest chatRoomRequest){
            //方法用于群发测试
            System.out.println("name = " + chatRoomRequest.getName());
            System.out.println("chatValue = " + chatRoomRequest.getChatValue());
            ChatRoomResponse response=new ChatRoomResponse();
            response.setName(chatRoomRequest.getName());
            response.setChatValue(chatRoomRequest.getChatValue());
            return response;
        }
    		
        //单独聊天
        @MessageMapping("/aloneRequest")	
        public ChatRoomResponse alone(ChatRoomRequest chatRoomRequest){
            //方法用于一对一测试
    	System.out.println("userId = " + chatRoomRequest.getUserId());
            System.out.println("name = " + chatRoomRequest.getName());
            System.out.println("chatValue = " + chatRoomRequest.getChatValue());	       
            ChatRoomResponse response=new ChatRoomResponse();
            response.setName(chatRoomRequest.getName());       
            response.setChatValue(chatRoomRequest.getChatValue());
            this.template.convertAndSendToUser(chatRoomRequest.getUserId()+"","/alone/getResponse",response);
            return response;
        }
    }
    简单介绍新的注解一下:
    
    一.@MessageMapping("/massRequest"):类似与@RequestMapping,客户端请求服务器的URL,前提是双方端点已经打开
    
    二.@SendTo("/mass/getResponse"):作用跟convertAndSend类似,广播发给与该通道相连的客户端
    
    其他已经在前面解释过了。
    
    3.html代码
    
    <!DOCTYPE html>
    <html>
      <head>
        <title>login.html</title>
    	
        <meta name="keywords" content="keyword1,keyword2,keyword3">
        <meta name="description" content="this is my page">
        <meta name="content-type" content="text/html" charset="UTF-8">
        <!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
    	<!-- 独立css -->
    	<link rel="stylesheet" type="text/css" href="chatroom.css">
      </head>
      
      <body>
    <div>
    	<div style="float:left;40%">
     	<p>请选择你是谁:</p>
      	<select id="selectName" onchange="sendAloneUser();">
      	<option value="1">请选择</option>
      	<option value="ALong">ALong</option>
      	<option value="AKan">AKan</option>
      	<option value="AYuan">AYuan</option>
      	<option value="ALai">ALai</option>
      	<option value="ASheng">ASheng</option>
      	</select>
    	<div class="chatWindow">
    	<p style="color:darkgrey">群聊:</p>
    	<section id="chatRecord" class="chatRecord">
    	<p id="titleval" style="color:#CD2626;"></p>
    	</section>
    	<section class="sendWindow">
    	<textarea name="sendChatValue" id="sendChatValue" class="sendChatValue"></textarea>
    	<input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendMassMessage()" value="发送">
    	</section>
    	</div>
    	</div>
    	
    	
    	<div style="float:right; 40%">
    	<p>请选择你要发给谁:</p>
      	<select id="selectName2">
      	<option value="1">请选择</option>
      	<option value="ALong">ALong</option>
      	<option value="AKan">AKan</option>
      	<option value="AYuan">AYuan</option>
      	<option value="ALai">ALai</option>
      	<option value="ASheng">ASheng</option>
      	</select>
    	<div class="chatWindow">
    	<p style="color:darkgrey">单独聊:</p>
    	<section id="chatRecord2" class="chatRecord">
    	<p id="titleval" style="color:#CD2626;"></p>
    	</section>
    	<section class="sendWindow">
    	<textarea name="sendChatValue2" id="sendChatValue2" class="sendChatValue"></textarea>
    	<input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendAloneMessage()" value="发送">
    	</section>
    	</div>
    	</div>
    </div>	
        <!-- 独立JS -->
    	<script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>	
    	<script type="text/javascript" src="chatroom.js" charset="utf-8"></script>
    	<script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script>
    	<script type="text/javascript" src="stomp.js" charset="utf-8"></script>
      </body>
    </html>
    JS代码[chatroom.js]:
    
    var stompClient = null;
    	
    	//加载完浏览器后  调用connect(),打开双通道
    	$(function(){	
    		//打开双通道
    		connect()
    	})
    	
    	//强制关闭浏览器  调用websocket.close(),进行正常关闭
        window.onunload = function() {
        	disconnect()
        }
     
    	//打开双通道
        function connect(){
            var socket = new SockJS('http://172.16.0.56:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointAric"
            stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
            stompClient.connect({},function(frame){//连接WebSocket服务端
             
                console.log('Connected:' + frame);           
                //广播接收信息
                stompTopic();
                
            });
        }
     
        //关闭双通道
        function disconnect(){
            if(stompClient != null) {
                stompClient.disconnect();
            }
            console.log("Disconnected");
        }
     
        //广播(一对多)
        function stompTopic(){
            //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(广播接收信息)
            stompClient.subscribe('/mass/getResponse',function(response){  
            	var message=JSON.parse(response.body);          	
            	//展示广播的接收的内容接收
            	 var response = $("#chatRecord");
                 response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");              	
            });
        } 
        
        //列队(一对一)
        function stompQueue(){
        
        	var userId=$("#selectName").val();
        	alert("监听:"+userId)
            //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(队列接收信息)
        	stompClient.subscribe('/user/' + userId + '/alone/getResponse',function(response){
            	var message=JSON.parse(response.body); 
            	//展示一对一的接收的内容接收
            	 var response = $("#chatRecord2");
                 response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");                 	
            });
        } 
        
        //选择发送给谁的时候触发连接服务器
        function sendAloneUser(){
        	stompQueue();
        }
        
        //群发
        function sendMassMessage(){
        	var postValue={};
        	var chatValue=$("#sendChatValue");
        	var userName=$("#selectName").val();
        	postValue.name=userName;
        	postValue.chatValue=chatValue.val();
        	if(userName==1||userName==null){
        		alert("请选择你是谁!");
        		return;
        	}
        	if(chatValue==""||userName==null){
        		alert("不能发送空消息!");
        		return;
        	}
        	stompClient.send("/massRequest",{},JSON.stringify(postValue));  
        	chatValue.val("");
        }
        //单独发
        function sendAloneMessage(){
        	var postValue={};
        	var chatValue=$("#sendChatValue2");
        	var userName=$("#selectName").val();
        	var sendToId=$("#selectName2").val();
        	var response = $("#chatRecord2");
        	postValue.name=userName;
        	postValue.chatValue=chatValue.val();
        	postValue.userId=sendToId;
        	if(userName==1||userName==null){
        		alert("请选择你是谁!");
        		return;
        	}
        	if(sendToId==1||sendToId==null){
        		alert("请选择你要发给谁!");
        		return;
        	}
        	if(chatValue==""||userName==null){
        		alert("不能发送空消息!");
        		return;
        	}
        	stompClient.send("/aloneRequest",{},JSON.stringify(postValue));  
        	response.append("<p><span>"+userName+":</span><span>"+chatValue.val()+"</span></p>");
        	chatValue.val("");
        }
    chatroom.css
    
    .chatWindow{
     
    	 100%;
    	height: 500px;
    	border: 1px solid blue;
    }
    .chatRecord{
    	 100%;
    	height: 400px;
    	border-bottom: 1px solid blue;
    	line-height:20px;
    	overflow:auto;
    	overflow-x:hidden;
    }
    .sendWindow{
    	 100%;
    	height: 200px;
    }
    .sendChatValue{
    	
    	 90%;
    	height: 40px;
    	
    }
    另外还需要的3个JS包,jquery.min.js、sockjs.min.js、stomp.js。
    

      

  • 相关阅读:
    PAT B1045 快速排序 (25 分)
    PAT B1042 字符统计 (20 分)
    PAT B1040 有几个PAT (25 分)
    PAT B1035 插入与归并 (25 分)
    PAT B1034 有理数四则运算 (20 分)
    PAT B1033 旧键盘打字 (20 分)
    HDU 1231 最大连续子序列
    HDU 1166 敌兵布阵
    HDU 1715 大菲波数
    HDU 1016 Prime Ring Problem
  • 原文地址:https://www.cnblogs.com/leigepython/p/10511290.html
Copyright © 2011-2022 走看看