Netty核心组件创建源码浅析

news2024/11/25 13:30:18

pipeline,Handler, HandlerContext创建源码剖析

源码解析目标
  • Netty中的ChannelPipeline,ChannelHandler和ChannelHandlerContext是核心组件,从源码解析来分析
    • Netty是如何设计三个核心组件
    • 分析Netty是如何创建和协调三个组件
    • 三个组件的结构关系是什么样的
Pipeline 接口设计
  • 如下源码,类结构
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {}

在这里插入图片描述

在这里插入图片描述

  • 可以从源码中看到,ChannelPipeline是一个接口并且继承了inBound,outBound,Iterable接口,它提供了各种addFirst,addLast等类似操作链表的方法,能够多类似链表的结构进行插入,追加,删除,替换操作,同时他的返回值包括ChannelPipeline,或者ChannelHandler或者ChannelHandlerContext
  • 我们看如下某个方法
 /**
     * Inserts a {@link ChannelHandler} at the first position of this pipeline.
     *
     * @param name     the name of the handler to insert first
     * @param handler  the handler to insert first
     *
     * @throws IllegalArgumentException
     *         if there's an entry with the same name already in the pipeline
     * @throws NullPointerException
     *         if the specified handler is {@code null}
     */
    ChannelPipeline addFirst(String name, ChannelHandler handler);
  • 以上方法是增加链表头节点方法,参数是一个那么,和关键的参数ChannelHandler,说明ChannelPipeline类似链表的结构中封装是一个一个的Handler,继续找源码中的注释查看如下
  • 一下是ChannelPipeline 源码中的一个说明图
  * <pre>
 *                                                 I/O Request
 *                                            via {@link Channel} or
 *                                        {@link ChannelHandlerContext}
 *                                                      |
 *  +---------------------------------------------------+---------------+
 *  |                           ChannelPipeline         |               |
 *  |                                                  \|/              |
 *  |    +---------------------+            +-----------+----------+    |
 *  |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  |               |                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  .               |
 *  |               .                                   .               |
 *  | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
 *  |        [ method call]                       [method call]         |
 *  |               .                                   .               |
 *  |               .                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  |               |                                  \|/              |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
 *  |    +----------+----------+            +-----------+----------+    |
 *  |              /|\                                  |               |
 *  +---------------+-----------------------------------+---------------+
 *                  |                                  \|/
 *  +---------------+-----------------------------------+---------------+
 *  |               |                                   |               |
 *  |       [ Socket.read() ]                    [ Socket.write() ]     |
 *  |                                                                   |
 *  |  Netty Internal I/O Threads (Transport Implementation)            |
 *  +-------------------------------------------------------------------+
 * </pre>
  • 入上图所示,表示的是一个handler的list,handler用于处理入站或者出站的事件,其中inBound是入站,方向是从Socket到ChannelPipeline。 outBound是出站方向是从ChannelPipeline到Socket,ChannelPipeline实现了一个过滤器模式,方便用户控制事件的处理,以及控制handler在pipeline中的交互
  • 接着看如下源码
 * Converts this pipeline into an ordered {@link Map} whose keys are
     * handler names and whose values are handlers.
     */
    Map<String, ChannelHandler> toMap();
	......
    @Override
    ChannelPipeline fireChannelRead(Object msg);
    ......

  • 以上源码中注释意思:将pipeline中的Handler 利用name:Handler的方式放入有序的Map中,并且提供以下方法进行处理
  • 因此ChannelPipeline会通过fireChannelRead等类似fireXXX的方法来控制ChannelPipeline中的Handler来处理入站和出站事件
  • 我们也知道,一个Pipeline中通常会有多个Handler,比如,一个典型的服务器在每个通道的管道中都有以下几个步骤处理
    • 处理程序协议解码器–将二进制数据转换为java对象
    • 协议编码器–将java对象转换为二进制数据
    • 业务逻辑处理器–执行具体的业务逻辑(安具体需求类似入库等)
  • 以上第三步骤中的业务逻辑处理程序我们尽量做到不会将线程阻塞,因为此处阻塞必然引起IO速度,进而影响整个Netty程序性能。如果业务程序很快,可以放IO线程中,反之需要异步执行,ChannelHandler对象中给我们提供了线程池用来异步执行
  • 接下来我们先来分析ChannelPipeline初始化的源码,依此来理解ChannelPipeline的实现以及对应的数据结构
  • 用以下案例来分析源码
//Netty cient端
/**
 * Created by jiamin5 on 2022/9/20.
 */
public class NettyClient {

    public static void main(String[] args) throws Exception {

            EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventExecutors)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("client is ready ....");

            //start client to connection server
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            eventExecutors.shutdownGracefully();
        }
    }
}

//Netty Server端口
/**
 * Created by jiamin5 on 2022/9/19.
 */
public class NettyService {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("客户端 SocketChannel hashCode: "+ socketChannel.hashCode());
                            socketChannel.pipeline().addLast(new NettyServerSchedulerReadHandler());
                        }
                    });
            System.out.println(" ..... Service is ready");
            ChannelFuture cf = bootstrap.bind(6668).sync();
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(cf.isSuccess()){
                        System.out.println(" listener 6668 success!!");
                    }else {
                        System.out.println(" listener 6668 failed!!");
                    }
                }
            });
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

//两个不同的ChannelHandler实现
/**
 * 优化耗时读取测试
 * Created by jiamin5 on 2022/9/19.
 */
public class NettyServerOptLongTimeReadHandler extends ChannelInboundHandlerAdapter {
    /**
     *读取数据事件,可以读取客户端发送来的信息
     * */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
        // NIOEventLoop 的 taskQueue中,

        // 解决方案1 用户程序自定义的普通任务

        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵2", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });

        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        });

        System.out.println("go on ...");
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("hellow, client~ miaomiao", CharsetUtil.UTF_8));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

/**
 * 优化耗时读取测试
 * Created by jiamin5 on 2022/9/19.
 */
public class NettyServerSchedulerReadHandler extends ChannelInboundHandlerAdapter {
    /**
     *读取数据事件,可以读取客户端发送来的信息
     * */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 比如这里我们有一个非常耗时长的业务-> 异步执行 -> 提交该channel 对应的
        // NIOEventLoop 的 taskQueue中,

        // 解决方案2 定时任务
        ctx.channel().eventLoop().schedule(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(5 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵3", CharsetUtil.UTF_8));
                    System.out.println("channel code=" + ctx.channel().hashCode());
                } catch (Exception ex) {
                    System.out.println("发生异常" + ex.getMessage());
                }
            }
        }, 10, TimeUnit.SECONDS);

        System.out.println("go on ...");
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("hellow, client~ miaomiao", CharsetUtil.UTF_8));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
  • 我们在上一篇Netty启动流程源码剖析中对ServerBootStrap进行过分析,其中说明ServerBootstrap 对象,他是一个引导类,用于启动服务器和引导整个程序的初始化。此处我们需要看Pipeline的创建过程,那么我们从此处来进行切入点。
  • 同时要看ChannelPipeline的初始化代码我们将入口锁定在ChannelPipeline的操作上,在NettyServer的以下语句上加上断点
  bootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class) //此处加上断点
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //给pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("客户端 SocketChannel hashCode: "+ socketChannel.hashCode());
                            socketChannel.pipeline().addLast(new NettyServerSchedulerReadHandler()); //此处关键节点加上断点
                        }
                    });
  • 启动NettyServer进行Debug,进入到 channel(NioServerSocketChannel.class) ,此处会进入到AbstractBootstrap的channel方法如下
/**
     * The {@link Class} which is used to create {@link Channel} instances from.
     * You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
     * {@link Channel} implementation has no no-args constructor.
     */
    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }
  • 之前文章中分析过,这个一个通过反射获取channelClass的方法,我们传入的参数是NioServerSockerChannel。我们直接进入到NioServerSockerChannel的构造方法中。
public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
//一直从super进去
protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }
  • 以上重点看newChannelPipeline方法,此处是创建ChannelPipeline的关键方法,进入后来到AbstractChannel的NewChannelPipeline方法中
protected DefaultChannelPipeline newChannelPipeline() {
        return new DefaultChannelPipeline(this);
    }
//最终的ChannelPipeline构造方法
protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }
  • 如上构造方法中是DefaultChannelPipeline,他是ChannelPipeline的一个抽象子类如下结构图

在这里插入图片描述

  • 如上代码中参数是我们传入的NioServerSockerChannel
  • 方法首先对channel参数进行初始化 this.channel = ObjectUtil.checkNotNull(channel, “channel”);用于pipeline操作channel
  • 创建一个future和promise用于异步回调
  • 创建一个inBound的tailContext,创建一个即是inbound又是outbound类型的headContext
  • 接着讲两个Context相互连接形成双向链表
ChannelHandler 作用以及设计
public interface ChannelHandler {

    /**
     * 当把 ChannelHandler 添加到 pipeline 时被调用
     */
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;

    /**
     * 当从 pipeline 中移除时调用
     */
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

    /**
     * 当处理过程中在 pipeline 发生异常时调用
     */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}

  • ChannelHandler是一个接口,从提供的方法中可以看出,作用就是处理IO事件或者拦截IO事件,Handler处理事件时候分为入站和出站,两个方向的操作都是不同的,因此Netty定义了两个子接口继承ChannelHandler
  • ChannelInboundHandler 入站事件接口

在这里插入图片描述

  • 其中ChannelActive用户当Channel处于活动状态时候被调用

  • channelRead当从channel读取数据时候被调用,这两个方法是我们使用Netty编码的时候经常用到的

  • ChannelOutboundHandler 出站事件

在这里插入图片描述

  • bind方法,当请求将channel绑定到本地的时候调用

  • close方法,当请求关闭Channel时候调用

  • ChannelDuplexHandler处理入站和出站事件
    在这里插入图片描述

  • ChannelDuplexHandler间接实现了入站接口并直接实现了出站接口

  • 是一个通用的能够同时处理入站事件和出站事件的类,类结构如下
    在这里插入图片描述

ChannelHandlerContext 作用以及设计
  • 如下类结构图

在这里插入图片描述

  • ChannelHandlerContext基础了出站方法,调用接口和入站方法调用接口,部分方法列表如下:

在这里插入图片描述
在这里插入图片描述

  • ChannelHandlerContext不仅仅基础了inboud和outbound方法,同时也定义了一些自己的方法,channel,executor,handler,pipeline,分配内存器,管理handler是否被删除
  • context是包装了handler相关的一切,方便Context可以在pipeline下进行操作handler
    在这里插入图片描述
ChannelPipeline | ChannelHandler | ChannelHandlerContext
  • 以上分析了三个核心对象的结构,接下来我们来看下三者的联系
  • 我们知道,每个ChannelSocker的创建同时都有一个管道pipeline,我们调用pipeline的add方法添加handler,如下代码
 socketChannel.pipeline().addLast(new NettyServerSchedulerReadHandler());
  • 刚才解析ChannelPipeline的时候分析了pipeline方法,接着分析DefaultChannelPipeline中的构造方法,
protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise = new VoidChannelPromise(channel, true);
    tail = new TailContext(this);
    head = new HeadContext(this);
    head.next = tail;
    tail.prev = head;
}
  • 在初始化tail和 head 链表节点的时候,他创建的是一个TailContext, HeadContext,继续进去看Context的初始化
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                                  boolean inbound, boolean outbound) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.inbound = inbound;
        this.outbound = outbound;
        // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }
  • 如上可以看到他的初始化过程中包含 DefaultChannelPipeline, executor 线程池初始化,就是我们创建的EventLoopGroup,包括inbound,outbound两个boolean类型,标识此处的Handler是处理出站还是处理入站

  • 接下来我们在到demo中,追Pipeline初始化后的addLast方法,如下

 socketChannel.pipeline().addLast(new NettyServerSchedulerReadHandler());
//进入addLast方法

 @Override
    public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
        if (handlers == null) {
            throw new NullPointerException("handlers");
        }
        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, null, h);
        }
        return this;
    }
    //继续追addlast
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);
            newCtx = newContext(group, filterName(name, handler), handler);  
            addLast0(newCtx);
			......
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }
  • 如上方法,中我们addLast可以一次添加n个Handler,DefaultChannelPipeline实现的addLast方法会将Handler循环添加到Pipeline中,
  • 在以上addLast中,参数是线程池,name此处是null,handler我们系统传入,Netty同时做了同步synchronized
  • checkMultiplicity(handler); 检测是否共享,如果不是,并且已经被别的Pipeline使用则抛出异常
  • 调用 newContext(group, filterName(name, handler), handler); ,将Handler包装成Context,如下,创建一个Context
DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
        super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        this.handler = handler;
    }
  • 从此处我们可以看出,每次添加一个Handler都会管理一个Context

  • 接着最后调用addLast将包装后的context添加到Pipeline双向链表中,如下

private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }
  • 更具如上分析我们可以得出以下一个三个核心对象的一个组织结构

请添加图片描述

Pipeline,Handler,HandlerContext创建过程

  • 每单创建一个ChannelSocket的时候都会创建一个绑定的pipeline,一对一的关系,创建Pipeline的时候也会创建tail节点,head节点,形成最初的双向链表
  • 调用pipeline的addLast方法,更具给定的handler创建Context,然后将这个Context插入到链表的尾端
  • Context包装Handler,多个Context在Pipeline中形成双向链表
  • 入站方向inbound从head节点开始,出站方法outbound从tail开始
分析Pipeline中Handler的使用

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

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

相关文章

LQB05 数码管动态扫描,显示字符串

1、蓝桥杯51单片机开发板的数码管是共阳数码管&#xff1b; 需要注意段码表的推导。 掌握推导段码表。 2、stcisp软件的数码管代码&#xff0c;是共阴的模式&#xff0c;注意取反的话&#xff0c;如何实现&#xff1f; 3、定时器动态扫描的思路&#xff1b; 4、注意动态扫描的时…

golang入门笔记——测试

测试类型&#xff1a; 单元测试&#xff1a; 规则&#xff1a; 1.所有测试文件以_test.go结尾 2.func Testxxx&#xff08;*testing.T&#xff09; 3.初始化逻辑放到TestMain中 运行&#xff1a; go test [flags][packages]Go语言中的测试依赖go test命令。 go test命令是一…

(考研湖科大教书匠计算机网络)第六章应用层-第四节:域名系统DNS

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;DNS概述二&#xff1a;层次域名结构&#xff08;1&#xff09;概述&#xff08;2&#xff09;顶级域名分类&#xff08;3&#xff09;因特网命名空…

「SAP」ABAP模块学习需要了解什么?快收下这份ABAP技术栈指南【附技能树】

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计专业大二本科在读&#xff0c;阿里云社区专家博主&#xff0c;华为云社区云享专家&#xff0c;CSDN SAP应用技术领域新兴创作者。   在学习工…

1. MacOs Dart环境安装

前置材料&#xff1a;需要安装dart的Mac设备, 一颗会用搜索引擎的聪明大脑一步步讲一下homebrew的安装流程我个人安装时遇到的情况 大家做个参考 如果你遇到的问题和我的不一样可以来这里 homebrew快速安装指引 可入群咨询首先, 我其实是安装过homebrew的网上常见的dart安装命令…

2003 -Cant connect to MySql server on IP地址 (10060)----在docker安装的MySQL连接阿里云服务器

MySQL配置 这个问题是因为在数据库服务器中的mysql数据库中的user的表中没有权限(也可以说没有用户)&#xff0c;下面将记录我遇到问题的过程及解决的方法。 在搭建完LNMP环境后用Navicate连接出错 遇到这个问题首先到mysql所在的服务器上用连接进行处理 0、docker exec -it m…

界面控件DevExpress WinForm——轻松构建类Visual Studio UI(二)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…

嵌入式常用知识

12、并发和并行的区别&#xff1f; 最本质的区别就是&#xff1a;并发是轮流处理多个任务&#xff0c;并行是同时处理多个任务。 你吃饭吃到一半&#xff0c;电话来了&#xff0c;你一直到吃完了以后才去接&#xff0c;这就说明你不支持并发也不支持并行。 你吃饭吃到一半&…

推荐5款实用小工具,第五款更是小白最爱

作为一个黑科技软件爱好者&#xff0c;电脑里肯定是不会缺少这方面的东西&#xff0c;今天的5款实用小工具闪亮登场了。 1.磁盘空间分析——SpcaeSniffer SpcaeSniffer是一款可视化硬盘空间占用布局大小的查询工具&#xff0c;软件体积小巧&#xff0c;使用简单。软件可对所需…

Android Studio翻译插件推介(Translation)

前言 Android Studio翻译插件适合英语水平不太好的程序员&#xff08;比如&#xff1a;我&#xff09;&#xff0c;最常用的翻译插件Translation和AndroidLocalize&#xff0c;本文主要讲解Translation&#xff0c;亲测可用。 先看看效果&#xff1a;这里是Android的API,任意选…

apache、iis设置301教程(适用虚拟主机)

当前提供教程是通过重写规则实现301,目前西部数码主机面板已经开发"301转向"功能可快捷设置&#xff1a; 如果部署了https访问&#xff0c;请忽略此教程&#xff0c;部署https的网站请参考&#xff1a;https://www.west.cn/faq/list.asp?unid1419 进入业务管理-虚…

单通道说话人语音分离——Conv-TasNet(Convolutional Time-domain audio separation Network)

单通道说话人语音分离——Conv-TasNet模型(Convolutional Time-domain audio separation Network) 参考文献&#xff1a;《Conv-TasNet: Surpassing Ideal Time-FrequencyMagnitude Masking for Speech Separation》 1.背景 在真实的声学环境中&#xff0c;鲁棒的语音处理通常…

【蓝桥杯每日一题】差分算法

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; 蓝桥杯 &#x1f319;我与杀戮之中绽放&#xff0c;亦如黎明的花…

银河麒麟V10桌面版系统将用户开发Qt界面程序添加为开机自启动

银河麒麟V10桌面版系统将用户开发Qt界面程序添加为开机自启动 银河麒麟V10桌面版系统允许用户开发自己的qt界面程序并将其添加为开机自启动。这样&#xff0c;每次开机后&#xff0c;用户开发的qt界面程序会自动启动&#xff0c;无需手动打开。 要将用户开发的qt界面程序添加…

走进chatGPT新一代机器人

chatGPT这款新一代对话式人工智能便在全球范围狂揽1亿名用户&#xff0c;并成功从科技界破圈&#xff0c;成为街头巷尾的谈资。chatGPT能干什么&#xff1f;打开官网https://openai.com/blog/chatgpt/ &#xff0c;完了&#xff0c;芭比Q了试下其他家的接口讲笑话写代码写解决方…

格雷码应用意义及编解码

文章目录1. 格雷码的应用意义2. 由自然数编码获得格雷码2.1 对称法实现2.2 公式法实现3. 由格雷码解码获得自然数1. 格雷码的应用意义 学过晶体管知识的朋友们都知道&#xff0c;数据位跳变就相当于硬件电路中的晶体管翻转。许多位同时跳变就相当于多个晶体管同时翻转&#xf…

【C++】STL之空间配置器 | STL总结

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;什么是空…

全球首个云渗透测试认证专家课程发布!腾讯安全领衔编制

2月20日&#xff0c;国际云安全联盟CSA发布了“云渗透测试认证专家CCPTP”课程体系&#xff0c;这是全球首个云渗透测试能力培养课程及人才认证项目&#xff0c;有效地弥补了云渗透测试认知的差距和技能人才培养的空白。腾讯安全在该项目中担任核心课程编撰单位。CSA是全球中立…

【双指针问题】LeetCode344、345、 844、283问题详解及代码实现

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…

曼恩斯特在创业板注册生效:拟募资约5亿元,彭建林夫妇为实控人

2月21日&#xff0c;深圳证券交易所披露的信息显示&#xff0c;深圳市曼恩斯特科技股份有限公司&#xff08;下称“曼恩斯特”&#xff09;的注册生效。据贝多财经了解&#xff0c;曼恩斯特于2021年6月30日在创业板递交招股书&#xff0c;2022年6月15日获得上市委会议通过&…