[Netty实践] 请求响应同步实现
目录
一、介绍
二、依赖引入
三、公共部分实现
四、server端实现
五、client端实现
六、测试
一、介绍
本片文章将实现请求响应同步,什么是请求响应同步呢?就是当我们发起一个请求时,希望能够在一定时间内同步(线程阻塞)等待响应结果。
我们通过netty实现rpc调用时,由于客户端和服务端保持连接,在此期间客户端会有无数的接口调用(并发),而此时,每次发送的请求需要能够及时响应获取调用结果,服务端一次次返回调用结果,客户端在处理响应结果时,需要与请求建立联系,确保每一次的请求能够正确获取到对应的调用结果。
由于在一个应用中,客户端与服务端的channel只有一条,所有线程都通过该channel进行rpc调用,所以,在接下来客户端设计中,每个线程发送的请求将会分配一个id,当请求发送完毕之后,该线程会进行阻塞状态,等待channel收到请求id对应返回的响应消息时唤醒或超时唤醒。在接下来服务端设计中,服务端收到客户端的rpc调用请求,对该请求进行处理,将该请求的id和处理结果写入响应类中进行返回。
二、依赖引入
<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.101.Final</version></dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.8.0</version></dependency><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.8.0</version></dependency<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency></dependencies>
三、公共部分实现
1、结构
2、Message类,所有Request和Response类的父类,最关键的字段就是messageType,子类继承之后进行赋值,该值与类的类型进行绑定,用于byte字节数组反序列化时能够获取到需要反序列化的类型。
@Data
public abstract class Message {protected Byte messageType;}
RpcRequest,用于客户端向服务端发起调用的消息通信类
@Data
@ToString
public class RpcRequest extends Message{private String id;private String param;public RpcRequest() {this.id = UUID.randomUUID().toString();super.messageType = MessageConstant.rpcRequest;}}
RpcResponse,用于服务端向客户端返回结构的消息通信类
@Data
@ToString
public class RpcResponse extends Message{private String id;private String result;public RpcResponse() {super.messageType = MessageConstant.rpcResponse;}}
3、MessageConstant,通过数值常量messageType绑定消息类型,在序列化对象时,会在数据中记录对象的messageType,在反序列化对象时,会从数据包中拿到messageType,将其转化为对应的消息类型进行处理
public class MessageConstant {public final static Byte rpcRequest = 1;public final static Byte rpcResponse = 2;public static Map<Byte, Class<? extends Message>> messageTypeMap = new ConcurrentHashMap<>();static {messageTypeMap.put(rpcRequest, RpcRequest.class);messageTypeMap.put(rpcResponse, RpcResponse.class);}public static Class<? extends Message> getMessageClass(Byte messageType){return messageTypeMap.get(messageType);}}
4、序列化工具,用于将类对象序列化为字节数组,以及将字节数组反序列化为对象
public class SerializationUtil {private final static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<>();/*** 序列化*/public static <T> byte[] serialize(T object){LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);try {Class<T> cls = (Class<T>) object.getClass();Schema<T> schema = getSchema(cls);return ProtostuffIOUtil.toByteArray(object, schema, buffer);} catch (Exception e) {throw e;} finally {buffer.clear();}}/*** 反序列化*/public static <T> T deserialize(Class<T> cls, byte[] data) {Schema<T> schema = getSchema(cls);T message = schema.newMessage();ProtostuffIOUtil.mergeFrom(data, message, schema);return message;}public static <T> Schema<T> getSchema(Class<T> cls) {Schema<T> schema = (Schema<T>) schemaCache.get(cls);if(schema == null) {schema = RuntimeSchema.getSchema(cls);schemaCache.put(cls, schema);}return schema;}}
5、MesasgeEncode和MessageDecode实现
MessageEncode,用于将消息对象序列化为字节数组
字节数组主要包括三部分:
·有效数组长度,占4个字节,长度不包括自己,用于半包黏包判断
·消息的类型,占1个字节,用于反序列选择类型使用
·消息对象,占n个字节
public class MessageEncode extends MessageToByteEncoder<Message> {@Overrideprotected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf byteBuf) throws Exception {// 将对象进行序列化byte[] data = SerializationUtil.serialize(message);// 写数据长度,前4个字节用于记录数据总长度(对象 + 类型(1个字节))byteBuf.writeInt(data.length + 1);// 写记录消息类型,用于反序列选择类的类型byteBuf.writeByte(message.getMessageType());// 写对象byteBuf.writeBytes(data);}}
MesageDecode,用于将字节数组反序列化为消息对象
反序列时会进行判断数据是否足够读取,足够的话就会读取到符合长度的字节数组进行序列化,否则的话等到下一个数据包到来再进行重新判断处理(解决半包黏包方案)
public class MessageDecode extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {// 由于数据包的前4个字节用于记录总数据大小,如果数据不够4个字节,不进行读if(byteBuf.readableBytes() < 4) {return;}// 标记开始读的位置byteBuf.markReaderIndex();// 前四个字节记录了数据大小int dataSize = byteBuf.readInt();// 查看剩余可读字节是否足够,如果不是,重置读取位置,等待下一次解析if(byteBuf.readableBytes() < dataSize) {byteBuf.resetReaderIndex();return;}// 读取消息类型byte messageType = byteBuf.readByte();// 读取数据, 数组大小需要剔除1个字节的消息类型byte[] data = new byte[dataSize -1];byteBuf.readBytes(data);Message message = SerializationUtil.deserialize(MessageConstant.getMessageClass(messageType), data);list.add(message);}}
四、server端实现
1、结构
2、RpcRequestHandler,用于处理客户端rpc请求
public class RpcRequestHandler extends SimpleChannelInboundHandler<RpcRequest> {private final static EventLoopGroup worker = new DefaultEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1);@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) throws Exception {// 为避免占用网络io,此处异步进行处理worker.submit(() -> {System.out.println("[RpcRequestHandler] "+ Thread.currentThread().getName() +" 处理请求,msg: " + msg);// 模拟处理耗时try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}RpcResponse rpcResponse = new RpcResponse();rpcResponse.setId(msg.getId());rpcResponse.setResult("处理" + msg.getParam());ctx.writeAndFlush(rpcResponse);});}}
3、ServerChannelInitializer,该类用于初始化Server与Client通信的Channel,需要将我们前面写的编解码器以及RequestHandler添加进pipeline
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();pipeline.addLast(new MessageEncode());pipeline.addLast(new MessageDecode());pipeline.addLast(new RpcRequestHandler());}}
4、RpcServer,用于启动一个Netty Server服务
public class RpcServer {public void bind(Integer port) {EventLoopGroup parent = new NioEventLoopGroup();EventLoopGroup child = new NioEventLoopGroup();Channel channel = null;try{ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(parent, child).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ServerChannelInitializer());ChannelFuture channelFuture = serverBootstrap.bind(port).sync();System.out.println("server启动");// 非阻塞等待关闭channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {System.out.println("server关闭");parent.shutdownGracefully();child.shutdownGracefully();}});channel = channelFuture.channel();} catch (Exception e) {e.printStackTrace();if(channel == null || !channel.isActive()) {System.out.println("server关闭");parent.shutdownGracefully();child.shutdownGracefully();} else {channel.close();}}}}
五、client端实现
1、结构
2、SyncPromise,用于Netty客户端的工作线程与外部发起RpcRequest的线程通信的类,通过该类可以阻塞与唤醒外部发起RpcRequest的线程,以及设置线程之间通信的内容(功能有点像Netty提供的Promise,不过此处我加了超时机制)
此处使用CountDownLatch来阻塞与唤醒线程有以下好处:
1、能够通过await(long timeout, TimeUnit unit)返回值true/false进行判断线程等待返回结果是否超时。因为线程进入阻塞时,CountDownLatch的值为1,当netty客户端的工作线程调用countDown()唤醒线程时,CountDownLatch值减为0,await(long timeout, TimeUnit unit)返回true,意味着线程等待响应结果时,没有超时。当netty客户端的工作线程没有来得及调用countDown()唤醒线程时。也就是说服务端返回结果超时,CountDownLatch值为1,线程超时唤醒,await(long timeout, TimeUnit unit)返回false。
综上所述,以await(long timeout, TimeUnit unit)返回值进行判断线程是否超时唤醒。此处给一个对比,就是有人认为为什么不使用LockSupport进行线程的阻塞与唤醒,原因如下:虽然LockSupport提供了超时唤醒的方法,但是该方法既没有返回值,也没有抛出异常,线程唤醒时,我们没有办法判断该线程是否超时了。
2、在我们实现的流程中,我们先发送了请求,才进行线程阻塞。那么存在一种情况,如果结果在我们线程阻塞之前就返回了,那么当线程进入阻塞时,就再也没有唤醒线程的时机了,导致线程每次调用接口都是超时的。
CountDownLatch的await(long timeout, TimeUnit unit)方法很好的规避了上诉问题,如果netty客户端的工作线程调用countDown()唤醒线程,那么此时CountDownLatch值减为0,线程需要调用await()进入阻塞,此时由于CountDownLatch为0,线程将不会进入阻塞,方法返回true,我们线程也能够正常的拿到请求的响应结果。
具体妙处需要大家仔细感受,一开始可能不太能理解,但把流程仔细梳理一下,就能够有更好的体验。
public class SyncPromise {// 用于接收结果private RpcResponse rpcResponse;private final CountDownLatch countDownLatch = new CountDownLatch(1);// 用于判断是否超时private boolean isTimeout = false;/*** 同步等待返回结果*/public RpcResponse get(long timeout, TimeUnit unit) throws InterruptedException {// 等待阻塞,超时时间内countDownLatch减到0,将提前唤醒,以此作为是否超时判断boolean earlyWakeUp = countDownLatch.await(timeout, unit);if(earlyWakeUp) {// 超时时间内countDownLatch减到0,提前唤醒,说明已有结果return rpcResponse;} else {// 超时时间内countDownLatch没有减到0,自动唤醒,说明超时时间内没有等到结果isTimeout = true;return null;}}public void wake() {countDownLatch.countDown();}public RpcResponse getRpcResponse() {return rpcResponse;}public void setRpcResponse(RpcResponse rpcResponse) {this.rpcResponse = rpcResponse;}public boolean isTimeout() {return isTimeout;}}
3、RpcUtil,封装的请求发送工具类,需要调用rpc发送的请求的线程,将通过该工具的send方法进行远程调用,不能简单的通过channel.writeAndFlush()进行客户端与服务端的通信
syncPromiseMap的作用:记录请求对应的SyncPromise对象(一次请求对应一个SyncPromise对象),由于外部线程与netty客户端的工作线程是通过SyncPromise进行通信的,我们需要通过请求的id与SyncPromise建立关系,确保netty客户端在处理RpcResopnse时,能够根据其中的请求id属性值,找到对应SyncPromise对象,为其设置响应值,以及唤醒等待结果的线程。
public class RpcUtil {private final static Map<String, SyncPromise> syncPromiseMap = new ConcurrentHashMap<>();private final static Channel channel;static{channel = new RpcClient().connect("127.0.0.1", 8888);}public static RpcResponse send(RpcRequest rpcRequest, long timeout, TimeUnit unit) throws Exception{if(channel == null) {throw new NullPointerException("channel");}if(rpcRequest == null) {throw new NullPointerException("rpcRequest");}if(timeout <= 0) {throw new IllegalArgumentException("timeout must greater than 0");}// 创造一个容器,用于存放当前线程与rpcClient中的线程交互SyncPromise syncPromise = new SyncPromise();syncPromiseMap.put(rpcRequest.getId(), syncPromise);// 发送消息,此处如果发送玩消息并且在get之前返回了结果,下一行的get将不会进入阻塞,也可以顺利拿到结果channel.writeAndFlush(rpcRequest);// 等待获取结果RpcResponse rpcResponse = syncPromise.get(timeout, unit);if(rpcResponse == null) {if(syncPromise.isTimeout()) {throw new TimeoutException("等待响应结果超时");} else{throw new Exception("其他异常");}}// 移除容器syncPromiseMap.remove(rpcRequest.getId());return rpcResponse;}public static Map<String, SyncPromise> getSyncPromiseMap(){return syncPromiseMap;}}
4、RpcResponseHandler,处理返回的调用结果,在该处理器中,将唤醒等待返回结果的线程
public class RpcResponseHandler extends SimpleChannelInboundHandler<RpcResponse> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, RpcResponse msg) throws Exception {// 根据请求id,在集合中找到与外部线程通信的SyncPromise对象SyncPromise syncPromise = RpcUtil.getSyncPromiseMap().get(msg.getId());if(syncPromise != null) {// 设置响应结果syncPromise.setRpcResponse(msg);// 唤醒外部线程syncPromise.wake();}}}
5、ClientChannelInitializer,该类用于初始化Server与Client通信的Channel,需要将我们前面写的编解码器以及ResponseHandler添加进pipeline
public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();pipeline.addLast(new MessageEncode());pipeline.addLast(new MessageDecode());pipeline.addLast(new RpcResponseHandler());}}
6、RpcClient实现,用于启动客户端
public class RpcClient {public Channel connect(String host, Integer port) {EventLoopGroup worker = new NioEventLoopGroup();Channel channel = null;try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(worker).channel(NioSocketChannel.class).option(ChannelOption.AUTO_READ, true).handler(new ClientChannelInitializer());ChannelFuture channelFuture = bootstrap.connect(host, port).sync();System.out.println("客户端启动");channel = channelFuture.channel();// 添加关闭监听器channel.closeFuture().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {System.out.println("关闭客户端");worker.shutdownGracefully();}});} catch (Exception e) {e.printStackTrace();if(channel == null || !channel.isActive()) {worker.shutdownGracefully();} else {channel.close();}}return channel;}}
六、测试
1、启动服务端
public static void main(String[] args) {new RpcServer().bind(8888);
}
启动结果如下:
server启动
2、启动客户端,并且通过两个异步线程发送请求
public static void main(String[] args) throws Exception{//Channel channel = new RpcClient().connect("127.0.0.1", 8888);Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {RpcRequest rpcRequest = new RpcRequest();rpcRequest.setParam("参数1");try {System.out.println("thread1发送请求");RpcResponse rpcResponse = RpcUtil.send(rpcRequest, 5, TimeUnit.SECONDS);System.out.println("thread1处理结果:" + rpcResponse);} catch (Exception e) {throw new RuntimeException(e);}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {RpcRequest rpcRequest2 = new RpcRequest();rpcRequest2.setParam("参数2");try {System.out.println("thread2发送请求");RpcResponse rpcResponse = RpcUtil.send(rpcRequest2, 5, TimeUnit.SECONDS);System.out.println("thread2处理结果:" + rpcResponse);} catch (Exception e) {throw new RuntimeException(e);}}});// 休眠一下,等待客户端与服务端进行连接Thread.sleep(1000);thread1.start();thread2.start();}
服务端结果:
[RpcRequestHandler] defaultEventLoopGroup-4-3 处理请求,msg: RpcRequest(id=ade6af01-2bcf-4a4c-a42a-381731010027, param=参数1)
[RpcRequestHandler] defaultEventLoopGroup-4-4 处理请求,msg: RpcRequest(id=db57bf9a-3220-44ca-8e4f-d74237a3d5b2, param=参数2)
客户端结果
thread1发送请求
thread2发送请求
thread1处理结果:RpcResponse(id=ade6af01-2bcf-4a4c-a42a-381731010027, result=处理参数1)
thread2处理结果:RpcResponse(id=db57bf9a-3220-44ca-8e4f-d74237a3d5b2, result=处理参数2)
以上由于我们在RpcRequestHandler中模拟处理请求为3秒,而线程等待结果超时为5秒,所以接下来将线程调用rpc请求的的超时时间设置为2秒,重启客户端,客户端结果如下:
thread1发送请求
thread2发送请求
Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.RuntimeException: java.util.concurrent.TimeoutException: 等待响应结果超时at org.ricardo.sync.client.RpcClientTest$1.run(RpcClientTest.java:32)at java.lang.Thread.run(Thread.java:748)
Caused by: java.util.concurrent.TimeoutException: 等待响应结果超时at org.ricardo.sync.client.rpc.RpcUtil.send(RpcUtil.java:56)at org.ricardo.sync.client.RpcClientTest$1.run(RpcClientTest.java:29)... 1 more
java.lang.RuntimeException: java.util.concurrent.TimeoutException: 等待响应结果超时at org.ricardo.sync.client.RpcClientTest$2.run(RpcClientTest.java:48)at java.lang.Thread.run(Thread.java:748)
Caused by: java.util.concurrent.TimeoutException: 等待响应结果超时at org.ricardo.sync.client.rpc.RpcUtil.send(RpcUtil.java:56)at org.ricardo.sync.client.RpcClientTest$2.run(RpcClientTest.java:45)... 1 more
相关文章:

[Netty实践] 请求响应同步实现
目录 一、介绍 二、依赖引入 三、公共部分实现 四、server端实现 五、client端实现 六、测试 一、介绍 本片文章将实现请求响应同步,什么是请求响应同步呢?就是当我们发起一个请求时,希望能够在一定时间内同步(线程阻塞&am…...
Java进阶—哈希冲突的解决
1. 什么是哈希冲突 哈希函数:哈希函数是一种将输入数据(键)映射到固定大小范围的输出值(哈希值)的函数。哈希函数通常用于存储 数据存储和检索领域,例如哈希表中。 哈希表:哈希表(Hash Table),也成为哈希映射(Hash Map)或字典&…...
css的border详解
CSS的border属性是一个简写属性,用于设置以下四个边框属性: border-width:定义边框的宽度。可以使用具体的像素值,或者使用预定义的关键字如thin、medium和thick。border-width不支持百分比值。默认情况下,边框的宽度是…...

如何保障消息一定能发送到RabbitMQ?
我们知道,RabbitMQ的消息最终是存储在Queue上的,而在Queue之前还要经过Exchange,那么这个过程中就有两个地方可能导致消息丢失。第一个是Producer到Exchange的过程,第二个是Exchange到Queue的过程。 为了解决这个问题,…...

【web前端】CSS语法
CSS语法 1. CSS语法格式 通常情况下语法格式如下: 选择器{属性名:属性值;属性名:属性值;属性名:属性值;... }2. CSS添加方式 2.1 行内样式 直接将样式写在本行的标签内。 <h1><p style"font-size: 48px; color:red;";>行内样式测试</p></…...

JS+CSS3点击粒子烟花动画js特效
JSCSS3点击粒子烟花动画js特效 JSCSS3点击粒子烟花动画js特效...

docker镜像复制与常见命令
一、前言 最近通过阿里的镜像仓库远程拉取镜像,发现以前的版本不见了,拉取了最新的镜像,有发现版本不配问题。那么想使用老版本的镜像那就要从别的环境获取。于是就需要进行离线镜像复制,打包,上传,重新导入…...

如何在linux环境上部署单机ES(以8.12.2版本为例)
ES安装(以8.12.2版本为例) 首先创建好对应的文件夹然后在对应的文件夹下执行依次这些命令 1.wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.12.2-linux-x86_64.tar.gz 2.wget https://artifacts.elastic.co/downloads/…...

如何利用人工智能技术实现企业营销效率提升10倍(下)
01. AI在私域运营中可扮演重要角色 私域用户体验历程中的不满,对企业来说,无疑是一记沉重的打击。这些不满不仅会让用户感到失望和沮丧,更会在无形中侵蚀企业的各个环节,给业务带来不可估量的损失。 在私域环境中,每…...

【PHP + 代码审计】数组函数
🍬 博主介绍👨🎓 博主介绍:大家好,我是 hacker-routing ,很高兴认识大家~ ✨主攻领域:【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 🎉点赞➕评论➕收…...

Keepalive与idle监测及性能优化
Keepalive 与 idle监测 Keepalive(保活): Keepalive 是一种机制,通常用于TCP/IP网络。它的目的是确保连接双方都知道对方仍然存在并且连接是活动的。这是通过定期发送控制消息(称为keepalive消息)实现的。如果在预定时…...

DS-红黑树(RBTree)
一.红黑树 1.1 红黑树的起源 当对对AVL树做一些结构修改的操作时候,性能较为低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。 因此1972年Rudolf…...

ubuntu 如何使用阿里云盘
你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…...

sqlite3 交叉编译
#1.下载源码并解压 源码路径如下,下载autoconf版本 SQLite Download Page 解压 tar -zxvf sqlite-autoconf-3450200.tar.gz cd sqlite-autoconf-3450200 mkdir build # 2. 配置源代码 # 假设你已经安装了交叉编译工具链,如gcc-arm-linux-gnueabih…...
【AI生成文章】flutter ChangeNotifierProvider 实用场景举例
内容由Ai 大模型生成,不能完全保障真实 ChangeNotifierProvider 是 Flutter 中一个非常实用的工具,用于在应用程序中管理和传递状态。以下是一些实用的场景举例: 1. 用户信息管理 在应用程序中,用户信息(如用户名、…...
【0274】从shared init file或local init file加载relation cache(2 - 1)
上一篇: 【0273】深入分析 relcache(relation descriptor cache)初始化第一阶段(1) 【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段(2) 1. 前言 本文内容是作为《【0264】深入分析relcache(relation descriptor cache)缓存初始化第2阶段…...

蓝桥杯-02-2023蓝桥杯c/c++省赛B组题目
参考 2023 年第十四届蓝桥杯 C/C B组省赛题解 2023蓝桥杯c/c省赛B组题目(最全版): A:日期统计 这题方法应该很多,没有和别人讨论想法。我的解法思路是:先 load 函数生成所有这一年的合法日期,然后枚举所有可以从数据…...
欧拉筛+并查集
集合 - 洛谷 std::vector<int> minp, primes,primes1;void sieve(int n,int p) {minp.assign(n 1, 0);primes.clear();for (int i 2; i < n; i) {if (minp[i] 0) {minp[i] i;primes.push_back(i);}for (auto p : primes) {if (i * p > n) {break;}minp[i * p]…...
《桥接模式(极简c++)》
本文章属于专栏《设计模式(极简c版)》 继续上一篇《原型模式(极简c)》。本章简要说明桥接模式。本文分为模式说明、本质思想、实践建议、代码示例四个部分。 模式说明 方案: 将抽象部分与它的实现部分分离,…...

jconsole的使用
前提 已安装jdk 使用步骤 1、命令行输入jconsole...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...