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

相关文章

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

将Java项目提交到云服务器的流程步骤

《将Java项目提交到云服务器的流程步骤》所谓将项目提交到云服务器即将你的项目打成一个jar包然后提交到云服务器即可,因此我们需要准备服务器环境为:Linux+JDK+MariDB(MySQL)+Gi... 目录1. 安装 jdk1.1 查看 jdk 版本1.2 下载 jdk2. 安装 mariadb(my

使用Python实现一个优雅的异步定时器

《使用Python实现一个优雅的异步定时器》在Python中实现定时器功能是一个常见需求,尤其是在需要周期性执行任务的场景下,本文给大家介绍了基于asyncio和threading模块,可扩展的异步定... 目录需求背景代码1. 单例事件循环的实现2. 事件循环的运行与关闭3. 定时器核心逻辑4. 启动与停

基于Python实现读取嵌套压缩包下文件的方法

《基于Python实现读取嵌套压缩包下文件的方法》工作中遇到的问题,需要用Python实现嵌套压缩包下文件读取,本文给大家介绍了详细的解决方法,并有相关的代码示例供大家参考,需要的朋友可以参考下... 目录思路完整代码代码优化思路打开外层zip压缩包并遍历文件:使用with zipfile.ZipFil

Python实现word文档内容智能提取以及合成

《Python实现word文档内容智能提取以及合成》这篇文章主要为大家详细介绍了如何使用Python实现从10个左右的docx文档中抽取内容,再调整语言风格后生成新的文档,感兴趣的小伙伴可以了解一下... 目录核心思路技术路径实现步骤阶段一:准备工作阶段二:内容提取 (python 脚本)阶段三:语言风格调

SpringBoot中配置Redis连接池的完整指南

《SpringBoot中配置Redis连接池的完整指南》这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以... 目录一、添加依赖二、配置 Redis 连接池三、测试 Redis 操作四、完整示例代码(一)pom.

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

C#实现将Excel表格转换为图片(JPG/ PNG)

《C#实现将Excel表格转换为图片(JPG/PNG)》Excel表格可能会因为不同设备或字体缺失等问题,导致格式错乱或数据显示异常,转换为图片后,能确保数据的排版等保持一致,下面我们看看如何使用C... 目录通过C# 转换Excel工作表到图片通过C# 转换指定单元格区域到图片知识扩展C# 将 Excel