【实战】SpringBoot整合Websocket、Redis实现Websocket集群负载均衡

文章目录

    • 前言
    • 技术积累
      • 什么是Websocket
      • 什么是Redis发布订阅
      • Redis发布订阅与消息队列的区别
    • 实战演示
      • SpringBoot整合Websoket
      • Websoket集群负载均衡
    • 实战测试
      • IDEA启动两台服务端
      • 配置nginx负载均衡
      • 浏览器访问模拟对话

前言

相信很多同学都用过websocket来实现服务端主动向客户端推送消息吧,基本上所有的管理类系统都会有这个功能。因为有websocket的存在,使得前后的主动交互变得容易和低成本。其实在JAVA领域用SpringBoot框架集成Websoket还是很简单的,今天我们重点不是集成而是通过Redis的发布订阅实现Websocket集群通信,当然有条件的也可以用MQ代替。

技术积累

什么是Websocket

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在这里插入图片描述

什么是Redis发布订阅

Redis 的发布订阅(Pub/Sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。当有新消息通过 PUBLISH 命令发送给频道时,这个消息会被发送给订阅它的所有客户端。

在这里插入图片描述

Redis发布订阅与消息队列的区别

Redis的发布订阅(Pub/Sub)和消息队列是两种不同的消息传递模式,它们的主要区别在于消息的处理方式和使用场景。

消息的处理方式:
在 Redis 的发布订阅模式中,消息是即时的,也就是说,当消息发布后,只有当前在线且订阅了该频道的客户端才能收到这个消息,消息不会被存储,一旦发布,当前没有在线的客户端将无法接收到这个消息。

在消息队列中,消息是持久化的,消息被发送到队列后,会一直在队列中等待被消费,即使没有在线的消费者,消息也不会丢失,消费者下次上线后可以继续从队列中获取到消息。

实战演示

本次演示采用demo形式,仅仅提供演示Websocket集群的实现方式,以及解决消息负载均衡的问题。演示案例重点偏后端实现Websoket集群通讯,不涉及前后端心跳检测,如果应用在生产环境前端需要增加心跳检测与重复创建。

本实战采用原生spring websocket,后续有时间再提供netty版本,以及产线版本。有条件的同学可以自己实现,原理都差不多。

SpringBoot整合Websoket

1、项目结构
在这里插入图片描述

2、springcloud 版本

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.12.RELEASE</version><relativePath/> <!-- lookup parent from repository -->
</parent>
<properties><java.version>8</java.version><spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>

3、maven依赖

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.68</version>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency><!-- 整合thymeleaf前端页面 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

4、配置文件

server:port: 8888
spring:profiles:active: devmvc:pathmatch:# Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatchermatching-strategy: ant_path_matcherthymeleaf:mode: HTMLencoding: UTF-8content-type: text/htmlcache: falseprefix: classpath:/templates/

5、thymeleaf 页面
websocket.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>var socket;function openSocket() {if (typeof (WebSocket) == "undefined") {console.log("您的浏览器不支持WebSocket");} else {console.log("您的浏览器支持WebSocket");//实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");//var socketUrl="${request.contextPath}/im/"+$("#userId").val();var socketUrl = "ws://192.168.1.4:7777/ws/" + $("#userId").val();socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");console.log(socketUrl);if (socket != null) {socket.close();socket = null;}socket = new WebSocket(socketUrl);//打开事件socket.onopen = function () {console.log("websocket已打开");//socket.send("这是来自客户端的消息" + location.href + new Date());};//获得消息事件socket.onmessage = function (msg) {console.log("接收消息为:"+msg.data);};//关闭事件socket.onclose = function () {console.log("websocket已关闭");};//发生了错误事件socket.onerror = function () {console.log("websocket发生了错误");}}}//心跳检测与重复验证自己实现function sendMessage() {if (typeof (WebSocket) == "undefined") {console.log("您的浏览器不支持WebSocket");} else {console.log("您的浏览器支持WebSocket");console.log('发送消息为:{"fromUserId":"' + $("#userId").val() + '","toUserId":"' + $("#toUserId").val() + '","contentText":"' + $("#contentText").val() + '"}');socket.send('{"fromUserId":"' + $("#userId").val() + '","toUserId":"' + $("#toUserId").val() + '","contentText":"' + $("#contentText").val() + '"}');}}</script>
<body>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
<p>【内容】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>【操作】:<div><button onclick="openSocket()">开启socket</button></div>
<p>【操作】:<div><button onclick="sendMessage()">发送消息</button></div>
</body>
</html>

6、消息实体
Message.java

import lombok.Data;/*** Message* @author senfel* @version 1.0* @date 2024/5/17 14:39*/
@Data
public class Message {/*** 消息编码*/private String code;/*** 来自(保证唯一)*/private String fromUserId;/*** 去自(保证唯一)*/private String toUserId;/*** 内容*/private String contentText;}

7、Websocket配置类
WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/*** WebSocketConfig* @author senfel* @version 1.0* @date 2024/5/16 16:51*/
@Component
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {System.out.println("启动websocket支持");return new ServerEndpointExporter();}
}Websocket 服务类
WebSocketServer.java
import com.example.ccedemo.config.SpringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;/*** WebSocketServer* @author senfel* @version 1.0* @date 2024/5/16 16:59*/
@ConditionalOnClass(value = WebSocketConfig.class)
@Component
@ServerEndpoint("/ws/{deviceId}")
public class WebSocketServer {protected Logger logger = LoggerFactory.getLogger(this.getClass());/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 设备ID*/private String deviceId;/**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。注:底下WebSocket是当前类名*/private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();/**用来存在线连接用户信息*/private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam(value="deviceId")String deviceId) {try {if(StringUtils.isEmpty(deviceId)||deviceId.equals("undefined")){return;}this.session = session;this.deviceId = deviceId;webSockets.add(this);sessionPool.put(deviceId, session);logger.info("【websocket消息】有新的连接,总数为:"+webSockets.size());StringBuffer stringBuffer = new StringBuffer();sessionPool.forEach((key, value) -> {stringBuffer.append(key).append(";");});logger.info("当前服务器连接有客户端有:"+stringBuffer.toString());} catch (Exception e) {}}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {try {webSockets.remove(this);sessionPool.remove(this.deviceId);logger.info("【websocket消息】连接断开,总数为:"+webSockets.size());StringBuffer stringBuffer = new StringBuffer();sessionPool.forEach((key, value) -> {stringBuffer.append(key).append(";");});logger.info("当前服务器连接有客户端有:"+stringBuffer.toString());} catch (Exception e) {}}/*** 收到客户端消息后调用的方法* @param message*/@OnMessagepublic void onMessage(String message) {logger.info("【websocket消息】收到客户端消息:"+message);SpringUtil.getBean(StringRedisTemplate.class).convertAndSend("webSocketMsgPush",message);}/** 发送错误时的处理* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error) {logger.error("用户错误,原因:"+error.getMessage());error.printStackTrace();}/*** 广播消息* @author senfel* @date 2024/5/17 17:10* @return void*/public void sendAllMessage(String message) {logger.info("【websocket消息】广播消息:"+message);for(WebSocketServer webSocket : webSockets) {try {if(webSocket.session.isOpen()) {webSocket.session.getAsyncRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}}/*** 单点消息 单人* @param deviceId* @param message* @author senfel* @date 2024/5/17 17:10* @return void*/public void sendOneMessage(String deviceId, String message) {Session session = sessionPool.get(deviceId);if (session != null&&session.isOpen()) {try {logger.info("【websocket消息】 单点消息:"+message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 单点消息* @param deviceId* @param object* @author senfel* @date 2024/5/17 17:10* @return void*/public void sendOneObject(String deviceId, Object object) {Session session = sessionPool.get(deviceId);if (session != null&&session.isOpen()) {try {logger.info("【websocket消息】 单点消息(对象):"+object);session.getAsyncRemote().sendObject(object);} catch (Exception e) {e.printStackTrace();}}}/*** 单点消息(多人)* @param deviceIds* @param message* @author senfel* @date 2024/5/17 17:11* @return void*/public void sendMoreMessage(String[] deviceIds, String message) {for(String deviceId:deviceIds) {Session session = sessionPool.get(deviceId);if (session != null&&session.isOpen()) {try {logger.info("【websocket消息】 单点消息:"+message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}}

8、controller提供接口

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;/*** TestController* @author senfel* @version 1.0* @date 2024/5/17 17:49*/
@RestController
@RequestMapping("/api/websocket")
public class BaseController {@GetMapping("page")public ModelAndView page(Long userId){ModelAndView websocket = new ModelAndView("websocket");websocket.addObject("userId",userId);return websocket;}
}

Websoket集群负载均衡

大家都知道Websoket是一个长链接,在不断开的情况下服务端与客户端是可以自由通讯的,这是因为服务端缓存了会话。

如果我们后端采用集群部署,那么可能多个用户的缓存会话会分散在各个服务器上。在我们给指定用户推送消息时就有可能调用服务器上并没有这个用户的会话。

所以,我们引入Redis发布订阅,将消息进行转发到所有的服务端,只有有会话缓存的服务端才会成功推送消息。讲到这里就比较明显了吧,完美解决Websoket负载均衡的问题。

1、maven引入Redis

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

2、配置文件

spring:redis:host: 127.0.0.1port: 6379

3、Redis配置类
RedisConfig.java

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** RedisConfig * @author senfel* @version 1.0* @date 2024/5/17 14:31*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {@Bean@Primarypublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jacksonSeial.setObjectMapper(om);template.setValueSerializer(jacksonSeial);template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);template.setHashValueSerializer(jacksonSeial);template.afterPropertiesSet();return template;}@BeanRedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,MessageListenerAdapter topicAdapter) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(connectionFactory);//订阅了主题 webSocketMsgPushcontainer.addMessageListener(topicAdapter, new PatternTopic("webSocketMsgPush"));return container;}/*** 消息监听器适配器,绑定消息处理器** @return*/@BeanMessageListenerAdapter topicAdapter() {return new MessageListenerAdapter(new RedisListener());}
}

4、Redis订阅监听
RedisListener.java

import com.alibaba.fastjson.JSONObject;
import com.example.ccedemo.config.SpringUtil;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;/*** RedisListener* @author senfel* @version 1.0* @date 2024/5/17 14:37*/
public class RedisListener implements MessageListener {@Overridepublic void onMessage(Message msg, byte[] bytes) {System.out.println(".监听到需要进行负载转发的消息:" + msg.toString());com.example.ccedemo.redissocket.Message message = JSONObject.parseObject(msg.toString(), com.example.ccedemo.redissocket.Message.class);SpringUtil.getBean(WebSocketServer.class).sendOneMessage(message.getToUserId(), message.getContentText());}
}

实战测试

我们本地启动两个服务,分别开启端口8888、9999,然后用nginx暴露7777端口做一个负载均衡。

IDEA启动两台服务端

在这里插入图片描述

在这里插入图片描述

配置nginx负载均衡

#服务器url变量定义
upstream api_service1 {server 192.168.1.4:8888;server 192.168.1.4:9999;
}#nginx配置websocket
map $http_upgrade $connection_upgrade {default upgrade;'' close;
}
server {listen  7777;large_client_header_buffers 4 16k;client_max_body_size 300m;client_body_buffer_size 128k;proxy_connect_timeout 600;proxy_read_timeout 600;proxy_send_timeout 600;proxy_buffer_size 64k;proxy_buffers   4 32k;proxy_busy_buffers_size 64k;proxy_temp_file_write_size 64k;proxy_http_version 1.1;root  /demo/page/dist;index  index.html;#apilocation /api/ {proxy_pass  http://api_service1/api/;proxy_set_header Host $http_host;}#nginx配置websocketlocation /ws/ { proxy_http_version 1.1; proxy_pass  http://api_service1/ws/;proxy_redirect off;proxy_set_header Host $host;  proxy_set_header X-Real-IP $remote_addr;proxy_read_timeout 3600s;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection $connection_upgrade;}#解决页面刷新404location / {try_files $uri $uri/ @req;index index.html;}location @req {rewrite ^.*$ /index.html last;}
}

浏览器访问模拟对话

1、浏览器开启多个无痕界面
http://192.168.1.4:7777/api/websocket/page
模拟对话:
多个界面的用户ID互补
在这里插入图片描述

2、分别开启soket,由于nginx轮询策略会分别注册在两个服务端上
在这里插入图片描述在这里插入图片描述

3、客户端相互发送消息验证
在这里插入图片描述

由以上图片可知,我们两个客户端相互对话能够接收到对方推送的消息。那么,由此也可以证明我们后端Websocke集群使用Redis发布订阅的方式搭建成功。

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

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

相关文章

【知识蒸馏】deeplabv3 logit-based 知识蒸馏实战,对剪枝的模型进行蒸馏训练

本文将对【模型剪枝】基于DepGraph(依赖图)完成复杂模型的一键剪枝 文章中剪枝的模型进行蒸馏训练 一、逻辑蒸馏步骤 加载教师模型定义蒸馏loss计算蒸馏loss正常训练 二、代码 1、加载教师模型 教师模型使用未进行剪枝&#xff0c;并且已经训练好的原始模型。 teacher_mod…

利用Python去除PDF水印

摘要 本文介绍了如何使用 Python 中的 PyMuPDF 和 OpenCV 库来从 PDF 文件中移除水印&#xff0c;并将每个页面保存为图像文件的方法。我们将深入探讨代码背后的工作原理&#xff0c;并提供一个简单的使用示例。 导言 简介&#xff1a;水印在许多 PDF 文件中都很常见&#x…

全国数据库管理系统设计赛-人大金仓内核实训安排正式发布

作为数据库领域国家队&#xff0c;人大金仓积极响应国家战略&#xff0c;通过赛题设计、内核技术支撑及赛前培训等多方面&#xff0c;大力支持全国大学生计算机系统能力大赛-数据库管理系统设计大赛成功举办。目前第二届全国大赛正在火热报名中&#xff0c;各种奖项等你来拿&am…

《web应用设计》第八次作业

我的小组长是姚若希&#xff0c;我们组课程设计的题目是&#xff1a;学生管理系统 &#xff0c;我认领的功能模块是&#xff1a;课程管理 2.查询并分页

只需三步,即可配置HTTPS跳转

HTTPS&#xff08;全称&#xff1a;Hyper Text Transfer Protocol over Secure Socket Layer&#xff09;&#xff0c;是以安全为目标的HTTP通道&#xff0c;简单讲是HTTP的安全版。通过SSL/TLS协议对数据进行加密&#xff0c;保证了数据传输的安全&#xff0c;防止数据被截获、…

UWB论文:Introduction to Impulse Radio UWB Seamless Access Systems(2):脉冲;超宽带;测距;定位

3) 测距/接收器 像全球定位系统&#xff08;GPS&#xff09;这样的系统依赖于单向测距One Way Ranging&#xff08;OWR&#xff09;&#xff0c;其中多个卫星&#xff08;代表固定节点&#xff0c;称为锚点anchors&#xff09;定期传输同步的无线电数据包集合&#xff0c;这允许…

sh控制台输入文字多行 按“# ꧂ ꧁”结束

如果在Unix shell中输入多行文字&#xff0c;那么这样操作&#xff1a; 1. 打开您的终端&#xff08;Terminal&#xff09;。 2. 输入您的文字&#xff0c;每行文字后按回车键。 3. 当您完成输入所有文字后&#xff0c;输入“# ꧂ ꧁”然后按回车键&#xff0c;表示输入结束。…

将Surface的分辨率减半以省电(二合一本\笔记本电脑适用)

【完全自定义分辨率教程】这篇教程用于将Surface之类的高分屏&#xff08;高分辨率&#xff09;的二合一本或笔记本等的分辨率调整为原来的一半&#xff0c;以实现省电等目的。 下载CRU&#xff08;Custom Resolution Utility&#xff09;解压后&#xff0c;打开CRU.exe选择当…

Java期末复习指南(1):知识点总结+思维导图,考试速成!

&#x1f516;面向对象 &#x1f4d6; Java作为面向对象的编程语言&#xff0c;我们首先必须要了解类和对象的概念&#xff0c;本章的所有内容和知识都是围绕类和对象展开的&#xff01; ▐ 思维导图1 ▐ 类和对象的概念 • 简单来说&#xff0c;类就是对具有相同特征的一类事…

(全面)Nginx格式化插件,Nginx生产工具,Nginx常用命令

目录 &#x1f3ab; 前言 &#x1f389; 开篇福利 &#x1f381; 开篇福利 x2 Double happiness # 介绍 # 地址 # 下载 &#x1f4bb; 命令及解析 # 整个文件系统中搜索名为nginx.conf的文件 # 编辑nginx.conf文件 # 重新加载配置文件 # 快速查找nginx.conf文件并使…

建筑施工突发事故应急处置vr安全培训平台

在不断发展的时代背景下&#xff0c;掌握必要的应急安全知识已成为我们生活中不可或缺的一部分。由央企携手我们华锐推出的3D线上应急宣教虚拟体验馆&#xff0c;标志着民众应急安全教育的全新里程碑&#xff0c;不仅突破了传统学习模式的局限&#xff0c;还让每个人都能在灵活…

防火墙技术基础篇:基于IP地址的转发策略

防火墙技术基础篇&#xff1a;基于IP地址的转发策略的应用场景及实现 什么是基于IP地址的转发策略&#xff1f; 基于IP地址的转发策略是一种网络管理方法&#xff0c;它允许根据目标IP地址来选择数据包的转发路径。这种策略比传统的基于目的地地址的路由更灵活&#xff0c;因…

深度学习之Python+OpenCV+Tensorflow实时人体检测和计数

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习之PythonOpenCVTensorflow实时人体检测和计数项目简介 一、项目背景与意义 随着科技的不断发展&#xff…

Go微服务: 日志系统ELK的应用

概述 基于前文&#xff0c;我们已经了解并搭建完成ELK的所有环境了&#xff0c;现在我们来结合应用程序来使用ELK参考前文&#xff1a;https://active.blog.csdn.net/article/details/138898538 封装日志模块 在通用工具模块: gitee.com/go-micro-services/common 这个包是通…

CTFHUB技能树——SSRF(三)

目录 URL Bypass 数字IP Bypass 302跳转 Bypass DNS重绑定 Bypass SSRF绕过方法&#xff1a; &#xff08;1&#xff09; http://abc.com127.0.0.1 &#xff08;2&#xff09;添加端口号 http://127.0.0.1:8080 &#xff08;3&#xff09;短地址 htt…

限制U盘使用:企业数据安全的软件解决方案

在当今数字化办公环境中&#xff0c;U盘作为一种便捷的数据传输工具&#xff0c;其使用在企业内部非常普遍。然而&#xff0c;U盘的不当使用也给企业数据安全带来了巨大风险。为了防止数据泄露和病毒传播&#xff0c;企业需要采取有效的软件解决方案来限制U盘的使用。本文将探讨…

docker容器安装mysql

linux: centOS-7 hadoop: 3.3.6 前置章节&#xff1a; (图文并茂)基于CentOS-7搭建hadoop3.3.6大数据集群-CSDN博客 可选&#xff1a;zookeeper安装教程-CSDN博客 1.安装docker 1.1 添加docker的repo源 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/…

集合、Collection接口特点和常用方法

1、集合介绍 对于保存多个数据使用的是数组&#xff0c;那么数组有不足的地方。比如&#xff0c; 长度开始时必须指定&#xff0c;而且一旦制定&#xff0c;不能更改。 保存的必须为同一类型的元素。 使用数组进行增加/删除元素的示意代码&#xff0c;也就是比较麻烦。 为…

一种简单实用的ollvm反混淆的方案与源码

我是一名从事反欺诈&风控&设备指纹相关的工作&#xff0c;最近对ollvm的如何逆向的问题进行了学习与思考。 ollvm是一个开源免费的so混淆工具&#xff0c;对于逆向的小白来说简直是灾难性的存在。 这个例子是超简单&#xff0c;我想每个人都可以学会跟掌握&#xff0c;…