Dubbo源码解析第一期:如何使用Netty4构建RPC

一、背景

        早期学习和使用Dubbo的时候(那时候Dubbo还没成为Apache顶级项目),写过一些源码解读,但随着Dubbo发生了翻天覆地的变化,那些文章早已过时,所以现在计划针对最新的Apache Dubbo源码来进行“阅读理解”,希望和大家一起再探Dubbo的实现。由于能力有限,如果文章有错误的地方,欢迎大家留言指正。

        本期的主题是Dubbo如何使用Netty4构建RPC来通讯

二、Server端视角

        我们看看作为服务提供方,Apache Dubbo是如何使用Netty4的。

2.1 Netty的线程组

        线程组作为Netty的Reactor设计核心组件,在这里自然少不了,我们看到org.apache.dubbo.remoting.transport.netty4.NettyServer中有如下两个方法:

    int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);String IO_THREADS_KEY = "iothreads";String EVENT_LOOP_BOSS_POOL_NAME = "NettyServerBoss";String EVENT_LOOP_WORKER_POOL_NAME = "NettyServerWorker";protected EventLoopGroup createBossGroup() {return NettyEventLoopFactory.eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME);}protected EventLoopGroup createWorkerGroup() {return NettyEventLoopFactory.eventLoopGroup(getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),EVENT_LOOP_WORKER_POOL_NAME);}

分别用来构建Boss线程组和Worker线程组,Boss线程组负责创建连接,连接创建成功后由Worker线程组来负责处理和发送请求。注意Boss线程组只设置了1个线程,而且没有设置修改接口,通常1个也是够用的,如果设置成可配的会更好。而Worker线程组默认值是当前可用核数和32中取最小值,注意,是最小值,不是最大值,意味着如果你单个Provider实例上可用的核超过32,那么一定要设置IO_THREADS_KEY,否则可能无法达到最大吞吐量(特别是IO密集型应用)。上面代码也给出了这两个线程组的线程池的名称,如果不清楚现在跑的是多少,可以jstack命令看下。注意,为了阐述方便,上面的四个属性定义来自org.apache.dubbo.remoting.Constants。

2.1 Netty的ChannelHandler

        Netty的可扩展性主要来自其核心组件之一ChannelHandler,ChannelHandler能帮助我们解决拆包&粘包、协议编解码、权限校验等RPC常遇到的问题。那我们来看看现在Dubbo的Provider端用到了哪些ChannelHandler,同样,我们看NettyServer#initServerBootstrap方法:

    protected void initServerBootstrap(NettyServerHandler nettyServerHandler) {boolean keepalive = getUrl().getParameter(KEEP_ALIVE_KEY, Boolean.FALSE);bootstrap.group(bossGroup, workerGroup)// 根据是否支持EPoll来返回EpollServerSocketChannel或者NioServerSocketChannel.channel(NettyEventLoopFactory.serverSocketChannelClass())// SO_REUSEADDR设置为true表示允许重用本地地址和端口。如果服务器意外关闭后再次启动,可以立即绑定到之前使用的地址和端口。.option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)// 表示禁用Nagle算法。Nagle算法会将小的网络包合并成较大的包来提高网络效率,但会增加数据传输的延迟。一般对延迟敏感的场景都会禁用Nagle算法。.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)// 开启TCP的心跳机制,用于检测连接是否还活着.childOption(ChannelOption.SO_KEEPALIVE, keepalive)// PooledByteBufAllocator是Netty提供的一种内存分配器实现,它可以重用ByteBuf对象的内存,提高内存的利用率和性能。.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {int closeTimeout = UrlUtils.getCloseTimeout(getUrl());NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);// 处理HTTPSch.pipeline().addLast("negotiation", new SslServerTlsHandler(getUrl()));ch.pipeline()// 解码,读数据的时候用.addLast("decoder", adapter.getDecoder())// 编码,写数据的时候用.addLast("encoder", adapter.getEncoder())// 注意,这是Netty提供的ChannelHandler.addLast("server-idle-handler", new IdleStateHandler(0, 0, closeTimeout, MILLISECONDS))// NettyServerHandler可是核心.addLast("handler", nettyServerHandler);}});}

我们重点关注下childHandler中添加的ChannelHandler。在这之前,我们先对Channel中的ChannelPipeline和ChanelHandler进行简单介绍,我们知道每个连接对应一个Channel,而每个Channel对应一个ChannelPipeline,用来组织和管理连接上面的处理器ChannelHandler(责任链的方式)。ChannelHandler分为ChannelInboundHandler和ChannelOutboundHandler两大类,分别管理入站(读)和出站(写)的流程定制,当然,为了使用方便,Netty提供了很多开箱即用的ChannelHandler,便于我们轻松实现HTTP、WebSocket等协议。

SslServerTlsHandler

        这是上面通过childHandler添加的第一个ChannelHandler(它属于Netty中ByteToMessageDecoder的子类,ByteToMessageDecoder的主要用途是将接收到的字节数据解码为消息对象,并将解码后的消息对象传递给下一个处理器进行后续的业务处理。继承它可以方便的处理多种数据格式,例如二进制数据、文本数据等),看这Handler的名称就知道是做什么的,用来进行SSL|TLS加解密。在其实现的decode方法中,如果发现ByteBuf是支持SSL|TLS,那么会立马在当前Channel的ChannelPipeline中加入SslHandler(Netty自带的支持SSL|TLS握手的ChannelHandler),注意,是加载当前SslServerTlsHandler之后。为了能在SSL|TLS解码后能继续执行SslServerTlsHandler的逻辑,在SslHandler后面又新加了一个SslServerTlsHandler(其中sslDetected=true,说明数据包到这里已经解包完成),用于校验协议。

        当然,如果SslServerTlsHandler发现你没有使用SSL|TLS(Netty中的SslHandler#isEncrypted能判断ByteBuf是否进行过加密),那么会直接把自己从ChannelPipeline中删除。这里给出SslServerTlsHandler#decode代码:

    @Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list)throws Exception {// Will use the first five bytes to detect a protocol.if (byteBuf.readableBytes() < 5) {return;}// SSL|TLS 校验完成后会创建一个新的SslServerTlsHandler,其中sslDetected会设置成trueif (sslDetected) {return;}CertManager certManager =url.getOrDefaultFrameworkModel().getBeanFactory().getBean(CertManager.class);ProviderCert providerConnectionConfig = certManager.getProviderConnectionConfig(url, channelHandlerContext.channel().remoteAddress());// 如果没启用SSL|TLS,那么获取到的providerConnectionConfig就会是null,从而删除并退出该Handlerif (providerConnectionConfig == null) {ChannelPipeline p = channelHandlerContext.pipeline();p.remove(this);return;}if (isSsl(byteBuf)) {SslContext sslContext = SslContexts.buildServerSslContext(providerConnectionConfig);enableSsl(channelHandlerContext, sslContext);return;}if (providerConnectionConfig.getAuthPolicy() == AuthPolicy.NONE) {ChannelPipeline p = channelHandlerContext.pipeline();p.remove(this);return;}logger.error(INTERNAL_ERROR, "", "", "TLS negotiation failed when trying to accept new connection.");channelHandlerContext.close();}

NettyCodecAdapter.InternalDecoder

        之前已经给了NettyServer#initServerBootstrap方法,其中可以发现这个内部解码器InternalDecoder是继SslServerTlsHandler的下一个ChannelHandler,由于它和SslServerTlsHandler一样,是ByteToMessageDecoder的子类,所以我们直接看其decode方法:

    private class InternalDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {// ChannelBuffer是Dubbo对通道缓冲区的抽象,这里的NettyBackedChannelBuffer就是其Netty缓冲区第一个实现,NettyBackedChannelBuffer内部直接使用的是Netty的ByteBufChannelBuffer message = new NettyBackedChannelBuffer(input);// Dubbo中的NettyChannel是衔接Netty Channel和Dubbo内部通道抽象的桥梁,其中NettyChannel就有Dubbo的解码器NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);do {int saveReaderIndex = message.readerIndex();// 这里的codec默认是DubboCountCodec实例,支持MultiMessage解码,这里返回的msg已经是org.apache.dubbo.remoting.exchange.Request对象Object msg = codec.decode(channel, message);if (msg == Codec2.DecodeResult.NEED_MORE_INPUT) {message.readerIndex(saveReaderIndex);break;} else {// is it possible to go here ?if (saveReaderIndex == message.readerIndex()) {throw new IOException("Decode without read data.");}if (msg != null) {out.add(msg);}}} while (message.readable());}}

1. DubboCountCodec(implements Codec2)用来处理MultiMessage类型的消息,Dubbo Consumer可以通过MultiMessage来一次性发送多个请求,但一般没人这么用,大家更愿意传递一个List来让下游Dubbo Provider来批量处理,上述InternalDecoder#decode方法内部调用了其decode方法,decode方法会继续触发其他org.apache.dubbo.remoting.Codec2实现类的调用。

2. DubboCodec(extends ExchangeCodec):DubboCountCodec#decode内部会调用其父类ExchangeCodec#decode方法,主要用于将Dubbo协议中的请求和响应消息进行序列化和反序列化。看到这里,难道Dubbo没用LengthFieldBasedFrameDecoder?这可是Netty中解决TCP拆包、粘包的神器!没有,Dubbo自己在ExchangeCodec#decode方法中自己解决了粘包、拆包问题,通过返回DecodeResult.NEED_MORE_INPUT来表示需要更多数据。ExchangeCodec#decode方法最后又将调用DubboCodec#decodeBody来解码body部分,这里给出Dubbo的协议图:

我们看看DubboCodec解码body部分的代码(注意DubboCodec可以解码也可以编码,这里为了简单起见,只展示解码部分):

    @Overrideprotected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);// get request id.long id = Bytes.bytes2long(header, 4);// decode request.Request req;Object data;req = new Request(id);req.setVersion(Version.getProtocolVersion());req.setTwoWay((flag & FLAG_TWOWAY) != 0);// get data length.int len = Bytes.bytes2int(header, 12);req.setPayload(len);DecodeableRpcInvocation inv;// 如果你实现了自定义解析器,那么就使用自定义的if (customByteAccessor != null) {inv = customByteAccessor.getRpcInvocation(channel, req, new UnsafeByteArrayInputStream(readMessageData(is)), proto);} else {// 否则用Dubbo内置的inv = new DecodeableRpcInvocation(frameworkModel,channel,req,new UnsafeByteArrayInputStream(readMessageData(is)),proto);}// DecodeableRpcInvocation来通过协议对应的序列化器来反序列化inv.decode();data = inv;req.setData(data);return req;}

可以看到,DubboCodec将使用DecodeableRpcInvocation#decode来完成最终解码过程。需要注意的是,我们可以通过设置DECODE_IN_IO_THREAD_KEY("decode.in.io.thread")参数来让Dubbo决定整个decode操作是否在IO线程中执行,默认是在IO线程执行。

2. DecodeableRpcInvocation(extends RpcInvocation):

前面我们说真正的反序列化是在该类中实现的,我们看下代码:

    @Overridepublic Object decode(Channel channel, InputStream input) throws IOException {int contentLength = input.available();this.put(Constants.CONTENT_LENGTH_KEY, contentLength);// 通过请求中的SerializationId来选择对应的序列化对象进行数据的反序列化ObjectInput in = CodecSupport.getSerialization(serializationType).deserialize(channel.getUrl(), input);this.put(SERIALIZATION_ID_KEY, serializationType);// 设置Dubbo版本号String dubboVersion = in.readUTF();request.setVersion(dubboVersion);setAttachment(DUBBO_VERSION_KEY, dubboVersion);// 设置需要调用的接口标识String path = in.readUTF();setAttachment(PATH_KEY, path);String version = in.readUTF();setAttachment(VERSION_KEY, version);// Do provider-level payload checks.String keyWithoutGroup = keyWithoutGroup(path, version);// 为了防止数据过载,在Dubbo Consumer端可以设置payLoad,这里检查数据是否超过payLoad限制checkPayload(keyWithoutGroup);// 设置需要调用的方法标识setMethodName(in.readUTF());// 参数类型的全限定名,如果有多个参数,通过;分隔String desc = in.readUTF();setParameterTypesDesc(desc);ClassLoader originClassLoader = Thread.currentThread().getContextClassLoader();try {Object[] args = DubboCodec.EMPTY_OBJECT_ARRAY;Class<?>[] pts = DubboCodec.EMPTY_CLASS_ARRAY;if (desc.length() > 0) {// 根据参数描述来拿到对应的Class对象pts = drawPts(path, version, desc, pts);// 根据参数的Class和值,解析到真正的参数对象args = drawArgs(in, pts);}setParameterTypes(pts);Map<String, Object> map = in.readAttachments();if (CollectionUtils.isNotEmptyMap(map)) {addObjectAttachments(map);}decodeArgument(channel, pts, args);} catch (ClassNotFoundException e) {throw new IOException(StringUtils.toString("Read invocation data failed.", e));} finally {Thread.currentThread().setContextClassLoader(originClassLoader);if (in instanceof Cleanable) {((Cleanable) in).cleanup();}}return this;}

我们可以看到,先是通过CodecSupport.getSerialization来获取对应的序列化对象,然后通过它来反序列化数据(我们可以通过看org.apache.dubbo.common.serialize.Serialization有哪些实现类来了解Dubbo默认支持的协议类型)。

NettyServerHandler

        当轮到NettyServerHandler执行的时候,由于前面的ChannelHandler处理,请求数据已经反序列化成了org.apache.dubbo.remoting.exchange.Request对象,到了NettyServerHandler#received内部,就轮到一些列 org.apache.dubbo.remoting.ChannelHandler 的子类来处理了:

MultiMessageHandler

        如果是MultiMessage类型的消息,那么for循环去调用内部handler去处理,否则只调用handler处理一次。

HeartbeatHandler

        在Channel上设置读的时间戳,便于后续判断该Channel是否存活,同时判断请求是不是HeartBeatRequest类型,如果是,则直接应答。否则继续执行下一个handler。

AllChannelHandler

        根据请求的url来获取处理的线程池,然后将请求和DecodeHandler实例构造成ChannelEventRunnable对象来扔进线程池中执行。

    @Overridepublic void received(Channel channel, Object message) throws RemotingException {ExecutorService executor = getPreferredExecutorService(message);try {// 这里的handler是DecodeHandler对象executor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));} catch (Throwable t) {if (message instanceof Request && t instanceof RejectedExecutionException) {sendFeedback(channel, (Request) message, t);return;}throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);}}

DecodeHandler

        前面说了AllChannelHandler之后的逻辑是在另一个线程中执行的,毫无疑问,在这个新的线程中,主要是用来调用DecodeHandler的来进行解码,在Dubbo Provider接收客户端请求的场景下,调用的是received方法:

    @Overridepublic void received(Channel channel, Object message) throws RemotingException {// 如果之前设置的不是在IO线程中解码,那么就在这个线程中解码if (message instanceof Decodeable) {decode(message);}// 如果是Request对象,那么得对消息负载(真正的请求对象)进行解码if (message instanceof Request) {decode(((Request) message).getData());}if (message instanceof Response) {decode(((Response) message).getResult());}// handler是HeaderExchangeHandler对象handler.received(channel, message);}

HeaderExchangeHandler

        该Handler根据请求的交互方式来决定如何处理请求(例如有的交互方式是不需要应答的),处理真正调用,比如会拿Request.data(RpcInvocation),最终会去调用DubboProtocol#ExchangeHandler的reply方法来拿到一个异步结果对象,并设置完成时候res的状态码和结果的赋值:

    void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {Response res = new Response(req.getId(), req.getVersion());// find handler by message class.Object msg = req.getData();try {// handler是DubboProtocol#ExchangeHandler对象CompletionStage<Object> future = handler.reply(channel, msg);future.whenComplete((appResult, t) -> {try {if (t == null) {res.setStatus(Response.OK);res.setResult(appResult);} else {res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(t));}channel.send(res);} catch (RemotingException e) {logger.warn(TRANSPORT_FAILED_RESPONSE,"","","Send result to consumer failed, channel is " + channel + ", msg is " + e);}});} catch (Throwable e) {res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(e));channel.send(res);}}

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

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

相关文章

XHCMS靶场小记(熊海)

文件包含漏洞 template下的header.php中存在文件包含漏洞&#xff08;该文件被file文件夹下的多数文件进行包含&#xff09; f参数可以包含任意文件通过php格式解析&#xff08;这是文件包含点&#xff09; 代码分析 根目录下的index.php文件&#xff1b;r参数用于获取包含文…

安捷伦AgilentE8363B网络分析仪

安捷伦AgilentE8363B网络分析仪 E8363B 是 Agilent 的 40 GHz 网络分析仪。网络分析仪是一种功能强大的仪器&#xff0c;可以以无与伦比的精度测量射频设备的线性特性。许多行业使用网络分析仪来测试设备、测量材料和监控信号的完整性 附加功能&#xff1a; 104 dB 的动态范围…

大数据导论(2)---大数据与云计算、物联网、人工智能

文章目录 1. 云计算1.1 云计算概念1.2 云计算的服务模式和类型1.3 云计算的数据中心与应用 2. 物联网2.1 物联网的概念和关键技术2.2 物联网的应用和产业2.3 大数据与云计算、物联网的关系 1. 云计算 1.1 云计算概念 1. 首先从商业角度给云计算下一个定义&#xff1a;通过网络…

升级8.0:民生手机银行的“内容解法”

数字化浪潮&#xff0c;滚滚来袭。 随着数字中国建设的持续推进&#xff0c;数字经济正在蓬勃发展。中商产业研究院分析师预测&#xff0c;2023年中国数字经济市场规模将增长至56.7万亿元&#xff0c;占GDP的比重将达到43.5%。 在此浪潮下&#xff0c;数字化的触角蔓延到各行…

洛谷NOIP2002 普及组 选数 +NOIP1999普及组 回文数

两道日常的练习题&#xff0c;废话不多说&#xff0c;直接上题上代码&#xff1a; 这道题目的难点在于怎样去根据一个不同的k值&#xff0c;通过代码来实现将所有符合题目要求的数字相加并且不重复的功能。下面请看代码&#xff0c;会有详细的讲解&#xff1a; #include<io…

用通俗易懂的方式讲解:选择最佳的 Embedding 和重排序模型,提升大模型 RAG 效果特别明显!

在构建检索增强生成&#xff08;RAG&#xff09;Pipeline时&#xff0c;一个关键组件是Retriever。我们有多种embedding模型可供选择&#xff0c;包括OpenAI、CohereAI和开源sentence transformers。此外&#xff0c;CohereAI和sentence transformers还提供了几个重排序器。 但…

OpenSource - 工具管理器easy-manager-tool

文章目录 功能说明运行配置环境配置启动docker部署 项目安全UI展示 Easy-Manager-Tool 打造软件行业首款集成工具&#xff0c;不管你是程序员&#xff0c;测试&#xff0c;运维等都可以使用该软件来提升自己的工作效率。 Easy-Manager-Tool 的诞生是为了解决软件行业众多参与者…

PGSQL安装PostGIS扩展模块

一、PostGIS简介 1、PostGIS介绍 PostGIS是一个空间数据库&#xff0c;空间数据库像存储和操作数据库中其他任何对象一样去存储和操作空间对象。 空间数据与数据库关联起来的三个要素&#xff1a;数据类型、索引和函数。 空间数据类型&#xff1a;用于指定图形为点&#xff0…

HBase学习五:运维排障

1、负载均衡 1.1 Rgion迁移 在当前的HBase版本中,Region迁移虽然是一个轻量级操作,但实现逻辑依然比较复杂,≈复杂性主要表现在两个方面:其一,Region迁移过程涉及多种状态的改变;其二,迁移过程中涉及Master、ZooKeeper(ZK)以及RegionServer等多个组件的相互协调。 …

11.什么档次的原型模式和我写的一样

在《生化危机》系列电影中&#xff0c;克隆人是个频频出现的话题。保护伞公司为了需求复制出另一个战力相当的战士Alice&#xff0c;不惜克隆成百上千个Alice&#xff0c;然而直到最后&#xff0c;非但没有真正克隆出另一个完美的Alice&#xff0c;就连Alice自己也被证实是保护…

SpringBoot(三层框架Controller,Mapper,Service)中遇到的一些注解整理

本文主要从Controller层,Service层,Mapper层这三层架构中记录用到的各种注解 还有一些MyBatis用到的注解 持续更新到本人的毕设做完为止,太多了太多了根本学不完哈哈哈 1.Controller层 1.1GetMapping/PostMapping/DeleteMapping/PutMapping 用于建立HTTP请求与处理方法之间的…

Go 爬虫之 colly 从入门到不放弃指南

文章目录 概要介绍如何学习官方文档如何安装快速开始如何配置调试分布式代理层面执行层面存储层面存储多收集器配置优化持久化存储启用异步加快任务执行禁止或限制 KeepAlive 连接扩展总结如果想用 GO 实现爬虫能力,该如何做呢?抽时间研究了 Go 的一款爬虫框架 colly。 概要…

Qt文件和目录相关操作

1.相关说明 QCoreApplication类、QFile类、QDir、QTemporaryDir类、QTemporaryFile类、QFileSystemWatcher类的相关函数 2.相关界面 3.相关代码 #include "dialog.h" #include "ui_dialog.h" #include <QFileDialog> #include <QTemporaryDir>…

Ardupilot开源飞控之VTOL之旅:打印件清单

Ardupilot开源飞控之VTOL之旅&#xff1a;打印件清单 1. 源由2. 清单2.1 模拟VTX打印件2.2 摄像头打印件2.3 GPS & RC天线打印件2.4 飞控 & 电调打印件 3. 总结4. 参考资料 1. 源由 VTOL一直仍在角落吃灰&#xff0c;主要还是手头缺点经费&#xff0c;搞台3D打印机基本…

Kotlin 移动端多平台

支持多平台编程是 Kotlin 的主要优势之一。它减少了为不同平台编写和维护相同代码所花费的时间&#xff0c;同时保留了本机编程的灵活性和优势。 1. 基本概念 KMM&#xff1a;Kotlin Multiplatform for mobile&#xff08;移动设备的 Kotlin 多平台&#xff09; KMM 多平台的主…

16k+ start 一个开源的的监控系统部署教程

安装条件 Linux或macOS系统 4GB内存 开放 33014、33174、3183端口 1.安装 1、下载源码 首先使用 git 克隆源码到本地 git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/ 方式1&#xff1a;运行 install.sh 脚本一键安装 ./install.s…

luffy商城项目(一)

企业项目类型 # 1 面向互联网用户&#xff1a;商城类项目 -微信小程序商城 # 2 面向互联网用户&#xff1a;二手交易类的 -咸鱼 -转转 # 3 公司内部项目&#xff1a;python写的重点 -oa系统 -打卡系统工资核算系统 -第三方公司做的&#xff1a…

leetCode-42.接雨水

&#x1f4d1;前言 本文主要是【算法】——算法模拟的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&#xff…

git提权

实验环境——vulnhub-dc2靶场 git提权 前提&#xff1a;用户可以使用sudo中git权限 查看sudo权限 sudo -l可以发现git命令存在sudo提权 基于此进行权限提升 方式&#xff1a; sudo git help config #在末行命令模式输入 !/bin/bash 或 !sh #完成提权 sudo git -p help…

直接发文!1D-2D-MTF-CNN-GRU-AT多通道图像时序融合的分类/故障识别程序!Excel导入,直接运行

​适用平台&#xff1a;Matlab2023版本及以上 本程序同时结合两篇国内顶级EI的方法&#xff1a;提出1D-2D-MTF-CNN-GRU-AT多通道图像时序融合的分类/故障识别程序&#xff01; ①中文EI期刊《电力自动化设备》12月29号网络首发文献&#xff1a;《基于格拉姆角场与并行CNN的并…