• 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。
    

      

  • 相关阅读:
    iOS开发 贝塞尔曲线UIBezierPath
    iOS开发 解决使用AVAudioRecorder录制后转mp3解决音量小的问题
    比JDK高效的array equals
    高性能web架构原则
    基于内存映射的千万级数据处理框架
    LesenRPC-基于netty/protobuffer的高性能RPC框架
    java垃圾回收机制详解
    MVC架构详解
    用抽象实现代码解耦
    python按年份统计文件数量
  • 原文地址:https://www.cnblogs.com/leigepython/p/10511290.html
Copyright © 2020-2023  润新知