Spring5高级编程------Spring-WebSocket实现在客户端和服务器之间发送消息的全双工单套接字连接

本文主要是介绍Spring5高级编程------Spring-WebSocket实现在客户端和服务器之间发送消息的全双工单套接字连接,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Spring-WebSocket

  • 一、WebSocket介绍
  • 二、Spring中使用WebSocket
  • 三、使用WebSocket API
    • 1、下面是WebSocket API的组件Maven引入方式
    • 2、下面是用于测试通信的前端单页面
    • 3、基与java全注解方式配置WebSocket
  • 四、基于stomp协议的WebSocket配置
    • 1、JavaConfig方式的配置
    • 2、使用到的实体类、控制器、拦截器
    • 3、前端测试代码
  • 五、两个配置类的详细介绍
    • 1、WebSocketConfigurer
    • 2、WebSocketMessageBrokerConfigurer

一、WebSocket介绍

WebSocket是作为HTML5计划的一部分而开发的一种规范,可以实现在客户端和服务器之间发送消息的全双工单套接字连接。过去,需要实现更新功能的Web应用程序会通过打开多个连接或是使用长轮询来定期轮询服务器端组件以获取更新数据。WebSocket的单套接字能够避免每个客户端需要多个连接并减少开销。
WebSocket

二、Spring中使用WebSocket

从版本4.0开始,Spring框架开始支持WebSocket样式的消息传递以及STOMP作为应用程序级别的子协议。在框架内,可以在spring-websocket模块中找到对WebSocket的支持,该模块与JSR-356兼容。需要注意的是不是所有浏览器都支持WebSocket协议,为了处理这种情况,Spring通过SockJS协议提供了透明的后被选项(根据浏览器的真实情况自动决定使用匹配的协议)。
Spring-WebSocket

三、使用WebSocket API

1、下面是WebSocket API的组件Maven引入方式

注意:SpringMVC项目依赖的其他组件不再这里列出

<dependency><groupId>org.springframework</groupId><artifactId>spring-websocket</artifactId><version>5.2.6.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-messaging</artifactId><version>5.2.6.RELEASE</version>
</dependency>

2、下面是用于测试通信的前端单页面

<!DOCTYPE html>
<html>
<head>
<title>WebSocket Tester</title>
<script type="text/javascript"src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">var ping;var websocket;jQuery(function($) {function writePing(message) {$('#pingOutput').append(message + '\n');}function writeStatus(message) {$('#statusOutput').val($("#statusOutput").val() + message + '\n');}function writeMessage(message) {$('#messageOutput').append(message + '\n');}$('#connect').click(function doConnect() {websocket = new WebSocket($("#target").val());websocket.onopen = function(evt) {writeStatus("CONNECTED");var ping = setInterval(function() {if (websocket != "undefined") {websocket.send("ping");}}, 3000);};websocket.onclose = function(evt) {writeStatus("DISCONNECTED");};websocket.onmessage = function(evt) {if (evt.data === "ping") {writePing(evt.data);} else {writeMessage('ECHO: ' + evt.data);}};websocket.onerror = function(evt) {onError(writeStatus('ERROR: ' + evt.data))};});$('#disconnect').click(function() {if (typeof websocket != 'undefined') {websocket.close();websocket = undefined;} else {alert("Not connected.");}});$('#send').click(function() {if (typeof websocket != 'undefined') {websocket.send($('#message').val());} else {alert("Not connected.");}});});
</script></head>
<body><h2>WebSocket Tester</h2>Target:<input id="target" size="40"value="ws://127.0.0.1:8081/wholesmart-springmvc-web/echoHandler"><br><button id="connect">Connect</button><button id="disconnect">Disconnect</button><br><br>Message:<input id="message" value=""><button id="send">Send</button><br><p>Status output:</p><pre><textarea id="statusOutput" rows="10" cols="50"></textarea></pre><p>Message output:</p><pre><textarea id="messageOutput" rows="10" cols="50"></textarea></pre><p>Ping output:</p><pre><textarea id="pingOutput" rows="10" cols="50"></textarea></pre>
</body>
</html>

3、基与java全注解方式配置WebSocket

package com.wholesmart.core.config;import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import com.wholesmart.websocket.EchoHandler;/*** WebSocket配置类,因为我的项目是在主配置类中使用@Import()注解引入该配置类,所以这个类上没有使用@Configuration注解。* * @author dyw* @date 2020年6月23日*/
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {/*** 使用HttpSessionHandshakeInterceptor设置setAllowedOrigins,否则会报403*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(echoHandler(), "/echoHandler").addInterceptors(new HttpSessionHandshakeInterceptor()).setAllowedOrigins("*");}@Beanpublic EchoHandler echoHandler() {return new EchoHandler();}
}
package com.wholesmart.websocket;import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;/*** 消息处理器* * @author dyw* @date 2020年6月23日*/
public class EchoHandler extends TextWebSocketHandler {@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {session.sendMessage(new TextMessage(message.getPayload()));System.out.println(new TextMessage(message.getPayload()));}
}

四、基于stomp协议的WebSocket配置

1、JavaConfig方式的配置

package com.wholesmart.config;import java.util.List;import org.springframework.context.annotation.Bean;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.SockJsServiceRegistration;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.StompWebSocketEndpointRegistration;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
import org.springframework.web.socket.messaging.StompSubProtocolErrorHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import com.wholesmart.websocket.UserHandshakeInterceptor;/*** 为WebSocket客户端定义简单的传输协议配置方法,通常需要配合使用@EnableWebSocketMessageBroker注解开启相应功能* * @author dyw* @date 2020年6月30日*/
@EnableWebSocketMessageBroker
public class WebSocketStompConfiguration implements WebSocketMessageBrokerConfigurer {/*** 有关处理来自客户端的消息和发送到客户端的消息的配置选项*/@Overridepublic void configureWebSocketTransport(WebSocketTransportRegistration registry) {}/*** 注册STOMP端点(可以是多个),并将端点映射到特定的URL上,并(可选)启用和配置SockJS回退选项。* 这里的配置主要用于构建WebSocketHandlerMapping* * <pre>* 这里的配置项有两种类型:* 1、{@code StompEndpointRegistry} 接口定义的配置* 2、{@code StompWebSocketEndpointRegistration} 接口定义的配置* 3、{@code SockJsServiceRegistration} 接口定义的配置* </pre>*/@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {/** 从WebMvcStompEndpointRegistry.getHandlerMapping()方法中可以看出,* WebSocketHandlerMapping优先使用我们这里配置的UrlPathHelper,如果我们这里没有配置这个值,* WebSocketHandlerMapping会使用在其父类AbstractHandlerMapping中创建的UrlPathHelper。*/registry.setUrlPathHelper(null);/** 设置STOMP所使用的HandlerMapping的优先级,默认值为1*/registry.setOrder(1);/** 自定义一个处理客户端错误帧的处理器,* 这里的StompSubProtocolErrorHandler会被设置到WebMvcStompEndpointRegistry类持有的StompSubProtocolHandler对象中*/registry.setErrorHandler(stompSubProtocolErrorHandler());/** websocket的端点,客户端需要注册这个端点进行链接*/StompWebSocketEndpointRegistration stompWebSocketEndpointRegistration = registry.addEndpoint("/stomp/ws");/** 配置允许的浏览器Origin Header的值,这个配置主要是针对浏览器设计的。HTTP 协议中的 Origin Header* 存在于请求中,用于指明当前请求来自于哪个站点。默认空表示只支持同源请求,“*”表示支持所有站点请求,其他以“http://”、“https://”* 表示支持特定站点的请求。*/stompWebSocketEndpointRegistration.setAllowedOrigins("*");/** 没有设置时默认使用DefaultHandshakeHandler*/stompWebSocketEndpointRegistration.setHandshakeHandler(null);/** OriginHandshakeInterceptor为默认拦截器,用于验证* OriginHeader,这里的配置只是多添加一个拦截器,不会覆盖掉默认拦截器。*/stompWebSocketEndpointRegistration.addInterceptors(userHandshakeInterceptor());/** SockJsServiceRegistration为返回值,可以配置SockJS*/SockJsServiceRegistration sockJsServiceRegistration = stompWebSocketEndpointRegistration.withSockJS();/** */sockJsServiceRegistration.setHttpMessageCacheSize(100);}@Beanpublic HandshakeInterceptor userHandshakeInterceptor() {return new UserHandshakeInterceptor();}@Beanpublic StompSubProtocolErrorHandler stompSubProtocolErrorHandler() {return new StompSubProtocolErrorHandler();}/*** 消息中介的相关配置*/@Overridepublic void configureMessageBroker(MessageBrokerRegistry config) {// 客户端订阅消息的基础路径config.setApplicationDestinationPrefixes("/app");// 服务器广播消息的基础路径config.enableSimpleBroker("/topic");}/*** 配置消息转换器,以便在从带注解的方法上提取消息和发送消息时使用。返回的boolean值类型用于确定是否还要添加默认转换器。*/@Overridepublic boolean configureMessageConverters(List<MessageConverter> messageConverters) {return true;}/*** 客户端消息传入通道的相关配置。默认情况下,通道由大小为1的线程池支持。建议生产环境设置合适的自定义线程池配置。*/@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {WebSocketMessageBrokerConfigurer.super.configureClientInboundChannel(registration);}/*** 客户端消息传出通道的相关配置。默认情况下,通道由大小为1的线程池支持。建议生产环境设置合适的自定义线程池配置。*/@Overridepublic void configureClientOutboundChannel(ChannelRegistration registration) {WebSocketMessageBrokerConfigurer.super.configureClientOutboundChannel(registration);}/*** 添加解析器以支持自定义控制器方法参数类型。不会修改内置方法参数解析器,如果需要修改内置方法参数解析器,直接配置SimpAnnotationMethodMessageHandler*/@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {WebSocketMessageBrokerConfigurer.super.addArgumentResolvers(argumentResolvers);}/*** 添加处理程序以支持自定义控制器方法返回值类型。内置处理器的修改直接配置SimpAnnotationMethodMessageHandler*/@Overridepublic void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {WebSocketMessageBrokerConfigurer.super.addReturnValueHandlers(returnValueHandlers);}}

2、使用到的实体类、控制器、拦截器

package com.wholesmart.websocket.stomp;import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;public class Stock implements Serializable {private static final long serialVersionUID = 7704093854068084650L;private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";private String code;private double price;private Date date;private String formatDate;public Stock() {}public Stock(String code, double price) {DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);date = new Date();formatDate = dateFormat.format(date);this.code = code;this.price = price;}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public Date getDate() {return date;}public void setDate(Date date) {DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);formatDate = dateFormat.format(date);this.date = date;}public String getFormatDate() {return formatDate;}public void setFormatDate(String formatDate) {this.formatDate = formatDate;}public static String getDateFormat() {return DATE_FORMAT;}@Overridepublic String toString() {return "Stock [code=" + code + ", price=" + price + "]";}}
package com.wholesmart.web;import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Controller;import com.wholesmart.websocket.stomp.Stock;@Controller
public class StockController {private TaskScheduler taskScheduler;private SimpMessagingTemplate simpMessagingTemplate;private List<Stock> stocks = new ArrayList<Stock>();private Random random = new Random(System.currentTimeMillis());public StockController() {stocks.add(new Stock("VMW", 1.00D));stocks.add(new Stock("EMC", 1.00D));stocks.add(new Stock("GOOD", 1.00D));stocks.add(new Stock("IBM", 1.00D));}@MessageMapping("/addStock")public void addStock(Stock stock) {stocks.add(stock);}@Autowiredpublic void setTaskScheduler(TaskScheduler taskScheduler) {this.taskScheduler = taskScheduler;}@Autowiredpublic void setSimpMessagingTemplate(SimpMessagingTemplate simpMessagingTemplate) {this.simpMessagingTemplate = simpMessagingTemplate;}private void broadcastUpdatePrices() {System.out.println("currentThread==" + Thread.currentThread().getName());System.out.println("Stocks  SIZE=======" + stocks.size());for (Stock stock : stocks) {System.out.println("stock===" + stock);stock.setPrice(stock.getPrice() + (getUpdatedStockPrice() * stock.getPrice()));stock.setDate(new Date());}simpMessagingTemplate.convertAndSend("/topic/price", stocks);}private double getUpdatedStockPrice() {double priceChange = random.nextDouble() * 5.0;if (random.nextInt(2) == 1) {priceChange = -priceChange;}return priceChange / 100.0;}@PostConstructprivate void broadcastTimePeriodically() {taskScheduler.scheduleAtFixedRate(() -> {broadcastUpdatePrices();}, 6000);}
}
package com.wholesmart.websocket;import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;/*** 自定义WebSocket握手拦截器* * @author dyw* @data 2020年7月2日*/
public class UserHandshakeInterceptor implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Map<String, Object> attributes) throws Exception {System.out.println("UserHandshakeInterceptor.beforeHandshake()");return true;}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Exception exception) {System.out.println("UserHandshakeInterceptor.afterHandshake()");}}

3、前端测试代码

<!DOCTYPE html>
<html>
<head>
<title>WebSocket Tester</title>
<script type="text/javascript"src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript"src="https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>
<script type="text/javascript"src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.2/stomp.min.js"></script>
<script type="text/javascript">var stomp = Stomp.over(new SockJS("/wholesmart-springmvc-web/stomp/ws"));function displayStockPrice(frame) {var prices = JSON.parse(frame.body);$('#price').empty();console.log(prices);for ( var i in prices) {var price = prices[i];$('#price').append($('<tr>').append($('<td>').html(price.code),$('<td>').html(price.price.toFixed(2)),$('<td>').html(price.formatDate)));}}var connectCallback = function() {stomp.subscribe('/topic/price', displayStockPrice);}var errorCallback = function(error) {alert(error.headers.message);}stomp.connect("guest", "guest", connectCallback, errorCallback);$(document).ready(function() {$('.addStockButton').click(function(e) {e.preventDefault();var jsonstr = JSON.stringify({'code' : $('.code').val(),'price' : Number($('.price').val())});stomp.send("/app/addStock", {}, jsonstr);return false;});});
</script></head>
<body><h1><b>Stock Ticker</b></h1><table border="1"><thead><tr><th>Code</th><th>Price</th><th>Time</th></tr></thead><tbody id="price"></tbody></table><p class="addStock">Code:<input class="code" /><br> Price:<input class="price" /><br><button class="addStockButton">And Stock</button></p>
</body>
</html>

五、两个配置类的详细介绍

1、WebSocketConfigurer

2、WebSocketMessageBrokerConfigurer

大家可以试图做各种理性分析,但最终回避不了这样的事实:人是一个个愚昧的、情绪化的个体,我们在有生之涯不知所措,用有限的视野辨识光明,凭着本能在惊恐中寻找猎物和栖息处。

这篇关于Spring5高级编程------Spring-WebSocket实现在客户端和服务器之间发送消息的全双工单套接字连接的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/305241

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2