Springboot-接入WebSocket服务

1、依赖引入 

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、启动类添加bean

public class Application {/*** 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint* 要注意,如果使用独立的servlet容器,* 而不是直接使用springboot的内置容器,* 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。*/@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}    

3、websocket服务创建

1、注解@ServerEndpoint("/client/websocket/{deviceId}")

2、地址参数与restful 风格一致

3、方法上通过获取地址参数 @PathParam( value = "deviceId")

4、方法getRemoteAddress() 可以获取客户端IP,如果是本机请求 则返回0.0.0.0.0.1

5、只能通过本地缓存对象sessionMap 存储session信息。

6、如果需要集群、分布式,则使用Nginx 做负载均衡(IP hash)

7、如果需要bean注入其他对象,必须使用构造函数手动申明SpringUtils.getBean(RedisCache.class);


import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;/*** 与客户端进行socket通信服务* @author xuancg*/
@ServerEndpoint("/client/websocket/{deviceId}")
@Component
@Slf4j
public class ClientSocketService {private RedisCache redisCache;private ClientProperites clientProperites; /** 用于存储当前服务器连接的socket  deviceId-session */private static Map<Long, Session> sessionMap = new ConcurrentHashMap<>(32);/*** 必须通过构造函数引入bean*/public ClientSocketService(){this.redisCache = SpringUtils.getBean(RedisCache.class);this.clientProperites = SpringUtils.getBean(ClientProperites.class); log.info("websocket准备完成");} /*** 连接事件,加入注解* @param deviceId* @param session*/@OnOpenpublic void onOpen(@PathParam( value = "deviceId") Long deviceId, Session session ) {// 设置消息体最大大小及session空闲时间int MAX_MESSAGE_SIZE 2 * 1024 * 1024;session.setMaxTextMessageBufferSize(MAX_MESSAGE_SIZE);session.setMaxBinaryMessageBufferSize(MAX_MESSAGE_SIZE);session.setMaxIdleTimeout(1 * 1000 * 60);log.info("客户端发起连接deviceId={}", deviceId);}/*** 连接事件,加入注解* 用户断开链接* 此处不允许执行删除sessionMap操作。由于deviceId 可能是恶意构造,需要做其他参数,或者请求token 验证,或者通过接收消息关闭onMessage* @param deviceId* @param session*/@OnClosepublic void onClose(@PathParam ( value = "deviceId") Long deviceId, Session session ) {log.info("客户端关闭连接deviceId={}", deviceId);close(session);}/*** 当接收到用户上传的消息* @param deviceId* @param session*/@OnMessagepublic void onMessage(@PathParam ( value = "deviceId") Long deviceId, Session session ,String message) {log.info("接收客户端请求 deviceId=,message={}", deviceId, message);}/*** 给单个用户推送消息* @param session* @param message*/private void sendMessage(Session session, ClientNotifyResp message){if(session == null){return;}// 同步RemoteEndpoint.Async async = session.getAsyncRemote();async.sendText(JSONUtil.toJsonStr(message));}/*** 处理用户活连接异常* @param session* @param throwable*/@OnErrorpublic void onError(Session session, Throwable throwable) {try {session.close();} catch (IOException e) {e.printStackTrace();}throwable.printStackTrace();}private void close(Session session){try {session.close();} catch (IOException e) {e.printStackTrace();}}private static String getRemoteAddress(Session session) {if (session == null) {return null;}RemoteEndpoint.Async async = session.getAsyncRemote();//在Tomcat 8.0.x版本有效//InetSocketAddress addr0 = (InetSocketAddress) getFieldInstance(async,"base#sos#socketWrapper#socket#sc#remoteAddress");//System.out.println("clientIP0" + addr0);//在Tomcat 8.5以上版本有效Object obj = getFieldInstance(async, "base#socketWrapper#socket#sc#remoteAddress");if(null == obj){return "127.0.0.1";}InetSocketAddress addr = (InetSocketAddress) obj;String ip = addr.toString().replace("/", "");int idx = ip.lastIndexOf(":");if(idx > 0){return ip.substring(0, idx);}return ip;}private static Object getFieldInstance(Object obj, String fieldPath) {String fields[] = fieldPath.split("#");for (String field : fields) {obj = getField(obj, obj.getClass(), field);if (obj == null) {return null;}}return obj;}private static Object getField(Object obj, Class<?> clazz, String fieldName) {for (; clazz != Object.class; clazz = clazz.getSuperclass()) {try {Field field;field = clazz.getDeclaredField(fieldName);field.setAccessible(true);return field.get(obj);} catch (Exception e) {}}return null;}}

4、拦截器放行

或者添加自定义拦截器

httpSecurity
// CSRF禁用,因为不使用session TODO
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/client/websocket/**").permitAll()

5、客户端调用demo

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Java后端WebSocket的Tomcat实现</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">发送消息</button>
<hr/>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
<div id="message"></div>
</body><script type="text/javascript">var websocket = null;//判断当前浏览器是否支持WebSocketif ('WebSocket' in window) {websocket = new WebSocket('ws://localhost:8080/mood-service/client/websocket/200013');}else {alert('当前浏览器 Not support websocket')}//连接发生错误的回调方法websocket.onerror = function () {setMessageInnerHTML("WebSocket连接发生错误");};//连接成功建立的回调方法websocket.onopen = function () {setMessageInnerHTML("WebSocket连接成功");}//接收到消息的回调方法websocket.onmessage = function (event) {setMessageInnerHTML(event.data);}//连接关闭的回调方法websocket.onclose = function () {setMessageInnerHTML("WebSocket连接关闭");}//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function () {closeWebSocket();}//将消息显示在网页上function setMessageInnerHTML(innerHTML) {document.getElementById('message').innerHTML += innerHTML + '<br/>';}//关闭WebSocket连接function closeWebSocket() {websocket.close();}//发送消息function send() {var message = document.getElementById('text').value;websocket.send(message);}
</script>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/685190.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMwareWorkstation17.0虚拟机安装Windows2.03完整详细步骤图文教程

VMwareWorkstation17.0虚拟机安装Windows2.03完整详细步骤图文教程 第一篇 下载Windows2.03第二篇 配置Windows2.03虚拟机机器环境第三篇 启动Windows2.03系统 第一篇 下载Windows2.03 1.Windows2.0原版软盘下载地址是 暂不提供&#xff0c;后续更新 2.Windows2.03虚拟机镜像下…

重案组第一季的观后感

继续研究美剧&#xff0c;重案组这部剧的人员组成&#xff0c;个性&#xff0c;特长&#xff0c;很有意思。 年龄&#xff0c;老中青三代。种族&#xff0c;白&#xff0c;黄&#xff0c;黑&#xff0c;有色。颜值&#xff0c;大众脸&#xff0c;很普通。对白&#xff0c;围绕…

鸿蒙开发-HarmonyOS UI架构

初步布局Index 当我们新建一个工程之后&#xff0c;首先会进入Index页。我们先简单的做一个文章列表的显示 class Article {title?: stringdesc?: stringlink?: string }Entry Component struct Index {State articles: Article[] []build() {Row() {Scroll() {Column() …

GiantPandaCV | 视觉类表面缺陷检测项目相关技术总结

本文来源公众号“GiantPandaCV”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;视觉类表面缺陷检测项目相关技术总结 本文由海滨撰写&#xff0c;首发于GaintPandaCV。 零、前言 做这个方向的项目也有一段时间了&#xff0c;作为…

Deep learning学习笔记

lec 1&#xff1a;Regression 1.5 Linear neural networks for regression线性神经网络的回归 I parameterizing output layer, I handling data, I specifying loss function, I training model. 浅层网络包括线性模型&#xff0c;其中包含了许多经典的统计预测方法&…

C++中的拷贝构造函数

一、拷贝构造函数的概念 拷贝构造函数用于创建一个与已有对象相同的对象&#xff0c;本质上也是构造函数的重载 拷贝构造函数只有一个类型为 const 类类型引用的形参&#xff0c;当我们要创建一个与已存在对象相同的对象时&#xff0c;由编译器自动调用拷贝构造函数。 clas…

简单的edge浏览器插件开发记录

今天在浏览某些网页的时候&#xff0c;我想要屏蔽掉某些信息或者修改网页中的文本的颜色、背景等等。于是在浏览器的控制台中直接输入JavaScript操作dom完成了我想要的功能。但是每次在网页之间跳转该功能都会消失&#xff0c;我需要反复复制粘贴js脚本&#xff0c;无法实现自动…

MATLAB知识点:exprnd函数(★★☆☆☆)生成指数分布的随机数

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章&#xff1a;课后习题讲解中拓展的函数 在讲解第三…

【革新你的社交形象】用AI创意头像应用,让你的头像独一无二!

在这个数字化时代&#xff0c;社交媒体已经成为我们生活中不可或缺的一部分。你是否曾经为了找到一个既能表达自己个性&#xff0c;又足够吸引眼球的头像而苦恼&#xff1f;现在&#xff0c;有了我们全新推出的AI创意头像应用&#xff0c;你的这一困扰将成为过去&#xff01; …

React入门到精通:掌握前端开发的必备技能!

介绍&#xff1a;React是一个由Facebook开发和维护的JavaScript库&#xff0c;用于构建用户界面&#xff0c;特别是用于构建单页应用程序和移动应用程序的用户界面。以下是对React的详细介绍&#xff1a; 虚拟DOM&#xff1a;React通过使用虚拟DOM&#xff08;Document Object …

C++的进阶泛型编程学习(2):类模板的机制

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、类模板二、类模板和函数模板的区别1.类模板没有自动类型推导的方式2.类模板在模板参数列表中可以有默认参数 三、类模板对象做函数参数四、类模板与继承4.1…

【Deep Learning 2】神经网络的优化

&#x1f31e;欢迎来到PyTorch的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年2月16日&a…

平滑几何名词

G0: Position (touching) G1: Tangent (angle) G2: Curvature (radius) G3&#xff1a; A G3 path is characterized by a continuously differentiable curvature profile。Acceleration/Torsion (rate of change of curvature 、 sharpness) clothoid: An Euler spi…

2.16C语言学习

P1296 奶牛的耳语 一开始以为是普通的二重循环&#xff0c;结果做出来答案是错的&#xff0c;但是思路不可能有问题&#xff0c;于是抱着试一试的想法加了一个排序&#xff0c;这样就过了&#xff0c;每次在第二重循环里遇到大于最小能听到的距离就跳出循环&#xff0c;进入下…

java8-重构、测试、调试

8.1.1 改善代码的可读性 改善代码的可读性到底意味着什么?我们很难定义什么是好的可读性&#xff0c;因为这可能非常主观。通常的理解是&#xff0c;“别人理解这段代码的难易程度”。改善可读性意味着你要确保你的代码能非常容易地被包括自己在内的所有人理解和维护。为了确保…

在Spring中事务失效的场景

在Spring框架中&#xff0c;事务管理是通过AOP&#xff08;面向切面编程&#xff09;实现的&#xff0c;主要依赖于Transactional注解。然而&#xff0c;在某些情况下&#xff0c;事务可能会失效。以下是一些可能导致Spring事务失效的常见场景&#xff1a; 非public方法&#…

2月16日,每日信息差

第一、2024春节档总观影人次进入影史前二&#xff0c;截至2月16日16时31分&#xff0c;2024年春节档总观影人次达1.46亿&#xff08;含预售&#xff09;&#xff0c;超2018年春节档总观影人次1.45亿&#xff0c;成为中国影史春节档总观影人次第二名。《热辣滚烫》《飞驰人生2》…

阿里云“BGP(多线)”和“BGP(多线)_精品”区别价格对比

阿里云香港等地域服务器的网络线路类型可以选择BGP&#xff08;多线&#xff09;和 BGP&#xff08;多线&#xff09;精品&#xff0c;普通的BGP多线和精品有什么区别&#xff1f;BGP&#xff08;多线&#xff09;适用于香港本地、香港和海外之间的互联网访问。使用BGP&#xf…

悦纳自己:拥抱个人局限,开启成长之旅

悦纳自己&#xff1a;拥抱个人局限&#xff0c;开启成长之旅 在人生的旅途中&#xff0c;我们每个人都会面临无数的挑战和选择。有时我们会因为这些挑战而感到焦虑和不安&#xff0c;因为我们害怕失败&#xff0c;害怕无法达到预期的目标。然而&#xff0c;真正重要的是我们如何…

Selenium实战教程系列(三)--- Selenium中的动作

Selenium中针对元素进行的动作在代码中可以分为两类&#xff1a; Selenium::WebDriver::ActionBuilder类中的动作方法Selenium::WebDriver::Element类中的动作方法 其中ActionBuilder类中的动作方法比较丰富&#xff0c;基本涵盖了所有可以进行的操作。 而Element类的动作比较…