【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(附通信协议和代码)(上)

基于Netty的分布式通信框架实现

  • 前提介绍
    • 回顾Dubbo
    • 分布式通信框架
      • 组成元素
      • 程序执行流程
      • 消息协议设计
      • 实现机制
          • ChannelInboundHandlerAdapter
            • 自定义事件处理
          • ChannelOutboundHandlerAdapter
      • 编(解)码处理器
        • 编码过程阶段
          • ChannelOutboundHandlerAdapter序列化实现
          • ChannelOutboundHandlerAdapter压缩实现
          • LengthBasedEncoder编码器
        • 解码过程阶段
      • 处理器链的建立
        • 创建`ChannelPipeline`对象
        • `ChannelPipeline`中添加处理器
        • 添加的顺序形成处理器链
    • 未完待续

前提介绍

今天,我要向大家实现一个基于Netty实现的高性能远程通信框架!这个框架利用了 Netty 的强大功能,提供了快速、可靠的远程通信能力。

无论是构建大规模微服务架构还是实现分布式计算,这个分布式通信框架都是一个不可或缺的利器。

回顾Dubbo

相信大家都指导Dubbo(Dubbo3)这个非常著名的RPC框架对吧,如果你忘记了,那么我给您先垫垫底,可以看到下面就是Dubbo的借本架构图,当然Dubbo3会更加复杂,我们先按照基础的Dubbo架构进行回顾
在这里插入图片描述
无论是在分布式系统、微服务架构还是其他需要跨网络进行通信的场景下,这个框架都能够帮助你实现高效的数据传输和通信。它具备出色的性能和可扩展性,能够满足各种复杂的通信需求。
在这里插入图片描述
但是无论是从层次化和结构化而言,Dubbo/Dubbo3都过于的复杂了,我们起始未必会用到那么复杂以及扩展性那么强的功能,因此我们来实现一个属于我们自己的一个可靠且高性能的远程通信解决方案。

分布式通信框架

分布式通信框架是一种卓越的高性能远程通信解决方案,它基于 Netty 实现了 TCP 通信的底层细节,并对上层进行了封装,以提供简单易用和高度可扩展的能力。在这里插入图片描述
这个框架能够帮助开发者轻松构建分布式系统,并实现可靠的跨网络通信。通过利用 Netty 的强大功能,该框架能够提供出色的性能和可靠性,同时还具备灵活的扩展性,可以满足各种复杂的通信需求。

组成元素

先介绍一下网络通信的两个最基本的元素和属性,如下所示。
在这里插入图片描述

  • Channel:可以理解为一个通道,即一条连接线路的概念。它承载着数据、信息或者信号的传输功能。

  • ChannelGroup:由多个通道组合而成的一个概念。它将多条通道有机地集合在一起,形成一个整体,以便更高效地进行数据、信息或者信号的传输。

程序执行流程

下图自上而下分别为boss接受连接、channel、dispatcher、event listener和service。
在这里插入图片描述
这五个部分各自承载着独特的任务,又彼此协作,形成了一个系统化、高效化的运行流程。
在这里插入图片描述

  • Boss线程:接受连接流程,主要负责接受外部请求,这些请求可能是来自用户的操作或是其他服务的调用。一旦接收到请求,boss会进行必要的处理,然后将请求分发给下面的线程池worker进行处理。

  • Worker线程:系统中的工作执行者,负责接收boss分发的任务,然后执行具体的业务逻辑。这些任务可能涉及到数据的处理、服务的调用等。线程池worker通过channel与boss进行通信,确保任务能够准确无误地传递。

  • dispatcher机制:在worker执行任务的过程中,需要有一个机制来调度和分配任务。这就是dispatcher的作用。

dispatcher根据一定的策略和规则,将任务分配给合适的worker线程进行处理。这一环节保证了系统的负载均衡和高效运行。

  • EventListener:基于在每个worker线程内部,eventListener发挥着关键作用。它负责监听和处理线程中的事件,比如任务的完成、异常等。通过eventListener,系统能够及时响应各种事件,进行必要的处理和反馈。

  • Service业务逻辑实现:它代表了整个系统的核心业务逻辑。service接收并处理来自worker线程的任务,完成具体的业务操作。这些操作可能涉及到数据的处理、服务的调用等。

消息协议设计

消息协议这里是指对消息编码和解码的规范的一种定义,通信内置的消息协议采用如下结构:其中包含了三个部分:ID、Length 和 Content。
在这里插入图片描述

  1. ID:

    • 长度:1 字节
    • 用途:表示 Content 部分是否被压缩,其中 1 表示 Content 部分被压缩,0 表示未被压缩。
  2. Length:

    • 长度:4 字节
    • 用途:表示 ID 和 Content 的总长度。这通常用于消息分片或分批传输,确保接收方可以正确地重新组装消息。
  3. Content:

    • 长度:不定(由 Length 字段决定)
    • 用途:真实的消息内容。根据 ID 的值,它可能是压缩的或未压缩的。

如果 ID 为 1,则 Content 部分可能会被某种算法(如gzip)压缩,以减少存储或传输的空间需求。Length 字段确保了数据的完整性,因为接收方可以根据这个长度字段正确地读取和重组数据。

在实际应用中,这种结构通常用于网络通信、文件存储或数据库存储等场景,其中需要对数据进行有效且紧凑的表示。

实现机制

Netty框架原生提供了一个处理器链,该链用于对事件进行处理。每个处理器都实现了 ChannelHandler 接口。ChannelHandler 接口是一个空接口,其中:ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter
在这里插入图片描述
我们主要关注这两个接口,因为它们被用于处理读取输入和写入输出的消息。

ChannelInboundHandlerAdapter

ChannelInboundHandlerAdapterNetty框架中用于处理从网络到应用程序的事件的组件。它是一种特殊的ChannelHandler,主要负责处理读取操作。
在这里插入图片描述

当网络通道接收到数据时,ChannelInboundHandlerAdapter会被触发,然后开发者可以通过重写其中的方法来执行需要的操作。常见的操作包括数据的解码、解压或反序列化等。

自定义事件处理

ChannelInboundHandlerAdapterNetty中实现业务逻辑的关键组件,它提供了丰富的方法来处理不同的事件,例如通道激活、数据读取和异常处理等,下面是对应的源码:

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

    /**
     * Calls {@link ChannelHandlerContext#fireChannelRegistered()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelRegistered();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelUnregistered()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelUnregistered();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelActive()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelInactive()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelInactive();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelRead(Object)} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ctx.fireChannelRead(msg);
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelReadComplete()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        ctx.fireUserEventTriggered(evt);
    }

    /**
     * Calls {@link ChannelHandlerContext#fireChannelWritabilityChanged()} to forward
     * to the next {@link ChannelInboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelWritabilityChanged();
    }

    /**
     * Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward
     * to the next {@link ChannelHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    @SuppressWarnings("deprecation")
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.fireExceptionCaught(cause);
    }
}

通过自定义ChannelInboundHandlerAdapter,开发者可以灵活地处理从网络到应用程序的数据传输过程,稍后回进行分析介绍。

ChannelOutboundHandlerAdapter

ChannelOutboundHandlerAdapter也是一种特殊的ChannelHandler,用于处理从应用程序到网络的事件,主要包括写出操作。它是Netty框架中的一个关键组件,负责将应用程序的数据写入网络通道中,以便发送给对应的接收端。
在这里插入图片描述
使用ChannelOutboundHandlerAdapter可以实现对写出事件的定制化处理,例如数据的编码、压缩或序列化等操作,以满足具体业务需求。它可以直接扩展ChannelOutboundHandlerAdapter类,并重写其中的方法来实现特定的功能。

package io.netty.channel;
import io.netty.channel.ChannelHandlerMask.Skip;
import java.net.SocketAddress;
/**
 * Skeleton implementation of a {@link ChannelOutboundHandler}. This implementation just forwards each method call via
 * the {@link ChannelHandlerContext}.
 */
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {

    /**
     * Calls {@link ChannelHandlerContext#bind(SocketAddress, ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
            ChannelPromise promise) throws Exception {
        ctx.bind(localAddress, promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception {
        ctx.connect(remoteAddress, localAddress, promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#disconnect(ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
        ctx.disconnect(promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#close(ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise)
            throws Exception {
        ctx.close(promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#deregister(ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        ctx.deregister(promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#read()} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void read(ChannelHandlerContext ctx) throws Exception {
        ctx.read();
    }

    /**
     * Calls {@link ChannelHandlerContext#write(Object, ChannelPromise)} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ctx.write(msg, promise);
    }

    /**
     * Calls {@link ChannelHandlerContext#flush()} to forward
     * to the next {@link ChannelOutboundHandler} in the {@link ChannelPipeline}.
     *
     * Sub-classes may override this method to change behavior.
     */
    @Skip
    @Override
    public void flush(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

编(解)码处理器

编码(解码)处理器、压缩(解压)处理器以及序列化(反序列化)处理器等都是直接或间接用于实现ChannelHandler的组件。

编码过程阶段

编码过程由三个Handler组合完成,分别为序列化,压缩数据以及编码处理。
在这里插入图片描述

ChannelOutboundHandlerAdapter序列化实现

当你需要实现序列化数据的发送时,可以基于ChannelOutboundHandlerAdapter接口进行实现。下面是一个简单的示例代码,展示了如何使用write方法将序列化后的数据发送到网络:

public class SerializationHandler extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        // 进行数据序列化操作,这里假设使用Java内置的序列化方式
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(msg);
        oos.flush();
        byte[] serializedData = bos.toByteArray();
        // 将序列化后的数据写入网络通道
        ByteBuf byteBuf = ctx.alloc().buffer();
        byteBuf.writeBytes(serializedData);
        ctx.write(byteBuf, promise);
    }
}

重写了write方法,在该方法中进行了数据的序列化操作。具体来说,我们使用Java内置的序列化方式将msg对象序列化为字节数组serializedData,然后将序列化后的数据写入网络通道。

ChannelOutboundHandlerAdapter压缩实现

要通过数据压缩进行处理,基于ChannelOutboundHandlerAdapter接口实现一个压缩处理器。使用DeflaterOutputStream进行数据压缩并发送到网络:

public class CompressionHandler extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        // 创建输出流,使用DeflaterOutputStream进行数据压缩
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DeflaterOutputStream dos = new DeflaterOutputStream(bos);
        
        // 压缩数据
        dos.write((byte[]) msg);
        dos.finish();
        
        // 获取压缩后的数据
        byte[] compressedData = bos.toByteArray();
        
        // 将压缩后的数据写入网络通道
        ByteBuf byteBuf = ctx.alloc().buffer();
        byteBuf.writeBytes(compressedData);
        ctx.write(byteBuf, promise);
    }
}

同样也是重写了write方法,在该方法中进行了数据的压缩操作。我们使用DeflaterOutputStream将原始数据(byte[]) msg进行压缩,然后将压缩后的数据写入网络通道。

LengthBasedEncoder编码器

要实现Netty中的编码器,你可以自定义一个类并实现MessageToByteEncoder接口。展示了如何编写一个基于字符串的编码器:

public class LengthBasedEncoder extends MessageToByteEncoder<String> {

    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        byte[] data = msg.getBytes(StandardCharsets.UTF_8);
        int length = data.length;
        out.writeInt(length);
        out.writeBytes(data);
    }
}

encode方法中,我们首先将字符串转换为字节数组,使用UTF-8字符集进行编码。然后,我们获取字节数组的长度,并将其写入输出ByteBuf。最后,我们将字节数组写入输出缓冲区。

注意,上述示例中使用的是字符串编码器,你可以根据实际需求替换成其他类型的编码器。同时,也请确保在创建ByteBuf对象时使用适当的Allocator,以获取更高效的内存分配和释放。

通过将自定义的编码器StringEncoder添加到NettyChannelPipeline中,作为ChannelOutboundHandler使用,你就可以在数据发送前将字符串编码为字节并写入网络通道中了。

解码过程阶段

解码的代码和编码的代码就是一个镜像操作和处理,在这里就进行赘余了,相信小伙伴都可以实现,如果真的有不会实现的,可以评论区留言告诉我,我把完整代码给你们。
在这里插入图片描述
对于 TCP 通信而言,粘包是很正常的现象,因此 decoder 必须处理粘包问题。LengthFrameDecoder 是一个支持粘包处理的decoder 类抽象,可基于基于长度的解码器的实现方式进行控制。

处理器链的建立

通过处理器链,Netty框架可以非常灵活地处理不同类型的事件,在Netty中,我们可以通过ChannelPipeline来建立处理器链。ChannelPipeline是一个用于管理和执行处理器的容器,它负责处理入站和出站的事件,并将这些事件传递给适当的处理器。

创建ChannelPipeline对象
ChannelPipeline pipeline = channel.pipeline();
ChannelPipeline中添加处理器
pipeline.addLast("handler1", new Handler1());
pipeline.addLast("handler2", new Handler2());

这里的 "handler1""handler2" 是处理器的名称,可以根据需要进行命名。

添加的顺序形成处理器链

数据将按照顺序在处理器之间传递。最后一个添加的处理器将是数据的出站处理器,第一个添加的处理器将是数据的入站处理器。

ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new MyHandler());

通过建立处理器链,可以根据需要按照一定的顺序和逻辑处理数据。

未完待续

由于篇幅过长,本文就到这里为止。下一篇文章将继续介绍《【分布式技术专题】「探索高性能远程通信」基于Netty的分布式通信框架实现(Dispatcher和EventListener)(下)》,并详细说明剩下的内容。敬请期待!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

NLP自然语言处理的发展:从初创到人工智能的里程碑

自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;人工智能领域中备受关注的重要分支之一。它使得计算机能够理解、解释和使用人类语言。随着技术的不断发展&#xff0c;NLP经历了从初创时期到深度学习时代的巨大演变&#xff0c;推动了互联网产…

【教学类-XX -XX 】20240128名字字卡1.0(15CM正方形手工纸、黑体,说明是某个孩子的第几个名字)

作品展示&#xff1a; 15CM手工纸上一个名字&#xff0c;页眉有这个字是哪一位孩子的第X个名字的说明 背景需求&#xff1a; 去年我制作了中6班孩子的姓名卡片&#xff0c;一张A4纸上6个字&#xff0c;每张卡片大约10CM&#xff09; 【教学类-25-01】20230320 名字卡片绘画游…

使用机器学习算法检测交易中的异常行为

交易中的异常检测意味着识别交易或相关活动中的异常或意外模式。这些模式被称为异常或异常值&#xff0c;明显偏离预期规范&#xff0c;可能表明存在不规则或欺诈行为。 异常检测在各种业务中发挥着至关重要的作用&#xff0c;尤其是那些涉及金融交易、在线活动和安全敏感操作…

IMX6ULL驱动学习——通过总线设备驱动模型点亮野火开发板小灯【参考韦东山老师教程】

参考&#xff1a;【IMX6ULL驱动开发学习】11.驱动设计之面向对象_分层思想&#xff08;学习设备树过渡部分&#xff09;-CSDN博客 韦东山课程&#xff1a;LED模板驱动程序的改造_总线设备驱动模型 我使用的开发板&#xff1a;野火imx6ull pro 欢迎大家一起讨论学习 实现了总线设…

ChatGPT与文心一言:智能回复与语言准确性的较量

在当今数字化时代&#xff0c;随着人们对智能化技术的需求不断增长&#xff0c;智能回复工具也成为了日常生活中不可或缺的一部分。ChatGPT和文心一言作为两个备受瞩目的智能回复工具&#xff0c;在智能回复、语言准确性以及知识库丰富度等方面各有卓越之处。 本文将对这两者进…

JAVA编程语言单词汇总

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发&#xff0c;并在 1995 年正式推出。后来 Sun 公司被 Oracle &#xff08;甲骨文&#xff09;公司收购&#xff0c;Java 也随之成为 Ora…

算法每日一题: 边权重均等查询 | 公共子祖先

大家好&#xff0c;我是星恒&#xff0c;今天给大家带来的是一道图里面有关公共子祖先的题目&#xff0c;理解起来简单&#xff0c;大家 题目&#xff1a;leetcode 2846 现有一棵由 n 个节点组成的无向树&#xff0c;节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n …

【Linux C | 网络编程】详细介绍 “三次握手(建立连接)、四次挥手(终止连接)、TCP状态”

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

强化合作!浪潮信息携手业界伙伴筑牢算力底座

以太平金融科技服务&#xff08;上海&#xff09;有限公司&#xff08;以下简称“太平金科”&#xff09;为例&#xff0c;在算力新型基础设施建设方面&#xff0c;该公司一直不遗余力。近日&#xff0c;该公司更携手全球领先的IT基础设施供应商浪潮信息&#xff0c;优化算力基…

静态代理IP该如何助力Facebook多账号注册运营?

在Facebook运营中&#xff0c;充分利用静态代理IP是多账号运营的关键一环。通过合理运用静态代理IP&#xff0c;不仅可以提高账号安全性&#xff0c;还能有效应对Facebook的算法和限制。以下是这些关键点&#xff0c;可以帮助你了解如何运用静态代理IP进行Facebook多账号运营&a…

如何通俗解释Docker是什么?

要想弄懂Docker&#xff0c;咱们得先从“容器化”讲起。 一、容器化技术及Docker的出现 容器化&#xff0c;它是一种轻量级、可移植的软件打包方式&#xff0c;你就想象成一个快递箱子&#xff0c;里面装着你的应用和所有需要运行的环境&#xff0c;这个箱子能在任何支持容器…

PDF标准详解(一)——PDF文档结构

已经很久没有写博客记录自己学到的一些东西了。但是在过去一年的时间中自己确实又学到了一些东西。一直攒着没有系统化成一篇篇的文章&#xff0c;所以今年的博客打算也是以去年学到的一系列内容为主。通过之前Vim系列教程的启发&#xff0c;我发现还是写一些系列文章对自己的帮…

go学习之air库的使用

首先下载air库 go install github.com/cosmtrek/air之后你需要去找到库下载的地方&#xff0c;若使用的是go mod可以使用命令 go env GOPATH找到下载库的位置 进入后&#xff0c;有bin&#xff0c;pkg目录&#xff0c;进入bin目录&#xff0c;你能看到air.exe文件 这时候将此…

NSSCTF Round#17 RE snake WP

控制流劫持可以非常快&#xff0c;当时困在中间的循环里了&#xff0c;其实一直跳到最后就行…… 运行一下发现是个贪吃蛇 联系到朝雾老师教的打飞机hit-plane那一题&#xff0c;应该通过控制流劫持直接跳转到打印flag的地方 第一个cmp分支处&#xff0c;判断轮数&#xff0c…

dataGrip连接数据库mysql和intersystems的iris

文章目录 前言创建新项目选择对应的数据库产品类型新建数据库资源连接sql命令窗体手动配置本地驱动 前言 intersystems公司的产品iris是cache的升级版本&#xff0c;目前绝大多数数据库工具都没法连接这个数据库 datagrip下载地址 https://download-cdn.jetbrains.com.cn/da…

vue3前端开发,如何引入element-plus前端框架及配置参数

vue3前端开发,如何引入element-plus前端框架及配置参数&#xff01;这是一个简单的教程&#xff0c;帮助大家快速在自己的项目中引入element-plus框架。 主要是介绍的引入流程和参数的配置情况。 如图&#xff0c;这个就是elment-plus前端框架里面的一个主按钮展示。表示我们配…

Tonka Finance 测试网活动,开启新铭文时代财富之门

Tonka Finance 是铭文赛道首个借贷市场&#xff0c;通过搭建一套铭文资产借贷质押方案&#xff0c;为铭文资产以借贷的形式释放价值、捕获流动性等方面提供了基础。作为铭文赛道最重要的基建设施之一&#xff0c;Tonka Finance 在面向市场后备受关注&#xff0c;并迅速作为铭文…

206. 反转链表(力扣LeetCode)

文章目录 206. 反转链表题目描述双指针递归 206. 反转链表 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 示例 2&#xff1a; 输入&am…

GUN/Linux时间同步服务之chrony配置管理

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;相关配置操作是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

嵌入式——直接存储器存取(DMA)补充

目录 一、认识 DMA 二、DMA结构 1. DMA请求 2. 通道DMA 补&#xff1a;通道配置过程。 3. 仲裁器 三、DMA数据配置 1. 从哪里来&#xff0c;到哪里去 &#xff08;1&#xff09;从外设到存储器 &#xff08;2&#xff09;从存储器到外设 &#xff08;3&#xff09;从…
最新文章