Netty学习——源码篇2 客户端Bootstrap(一) 备份

 1 Channel简介

        在Netty中,Channel相当于一个Socket的抽象,它为用户提供了关于Socket状态(是连接还是断开)以及对Socket的读写等操作。每当Netty建立了一个连接,都创建一个与其对应的Channel实例。

        除了TCP,Netty还支持很多其他的协议,并且每种协议还有NIO和OIO(传统的阻塞IO)版本的区别。不同协议不同阻塞类型的连接都有不同的Channel类型与之对应,下表对一些常用的Channel做了简单介绍。

 

        来看一下Channel的总体类图,如下图:

 

2 NioSocketChannel的创建

        Bootstrap是Netty提供的一个便利的工厂类,可以通过它来完成客户端或者服务端的Netty的初始化。

        首先,从客户端开始分析。 

public class ChatClient {
    public ChatClient connect(int port,String host,final String name){
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            System.out.println("初始化channel:" + socketChannel);
                        }
                    });
            //发起同步连接操作
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            channelFuture.channel().closeFuture().sync();
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            //关闭,释放线程资源
            group.shutdownGracefully();
        }
        return this;

    }

    public static void main(String[] args) {
        new ChatClient().connect(8080,"127.0.0.1","jay");
    }
}

         分析如下:

        1、EventLoopGroup:不论是服务端还是客户端,都必须指定EventLoopGroup。在本例中,指定了NioEventLoopGroup,表示一个NIO的EventLoopGroup。

        2、ChannelType:指定Channel的类型。因为是客户端,所以使用了NioSocketChannel。

        3、Handler:设置处理数据的Handler。

        客户端启动Bootstrap后都做了哪些工作?看一下NioSocketChannel的类层次结构图,如下:

        回到在客户端连接代码的初始化Bootstrap中,该方法调用了一个channel方法,传入的参数是NioSocketChannel.class,在这个方法中其实就是初始化了一个ReflectiveChannelFactory的对象,代码实现如下:

public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }

         而ReflectiveChannelFactory实现了ChannelFactory接口,它提供了唯一的方法,即newChannel方法。顾名思义,ChannelFactory就是创建Channel的工厂类。进入ReflectiveChannelFactory的newChannel方法,其实现代码如下:

public T newChannel() {
        try {
            return (Channel)this.clazz.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
        }
    }

        根据上面的代码,可以得出以下结论。

        1、Bootstrap中的ChannelFactory实现类是ReflectiveChannelFactory。

        2、通过channel方法创建的Channel具体类型是NioSocketChannel。

        Channel的实例化过程其实就是调用ChannelFactory的newChannel方法,而实例化的Channel具体类型又和初始化Bootstrap时传入的channel方法的参数有关。因此对于客户端的Bootstrap而言,创建的Channel实例就是NioSocketChannel。

3 客户端Channel初始化

        上面提到了如何设置一个Channel的类型,并且了解到Channel是通过ChannelFactory的newChannel方法来实例化的,那么ChannelFactory的newChannel方法在哪里调用呢?其调用链路如下图:

        在AbstractBootstrap的initAndRegister方法中,调用ChannelFactory的newChannel方法来创建一个NioSocketChannel的实例,代码如下:

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
            }

            return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

        在newChannel方法中,利用反射机制调用类对象newInstance()方法来创建一个新的Channel实例,相当于调用NioSocketChannel的默认构造方法。 NioSocketChannel默认的构造方法代码如下:

public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

        这里的代码比较关键,可以看到,在这个构造器中首先会调用newSocket()方法来打开一个新的Java NIO的SocketChannel对象。

private static java.nio.channels.SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException var2) {
            throw new ChannelException("Failed to open a socket.", var2);
        }
    }

        然后调用父类,即AbstractNioByteChannel构造器.

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
        super(parent, ch, 1);
    }

        同时,传入参数,parent的值默认为null,ch为之前调用newSocket()方法创建的Java NIO的SocketChannel对象,因此新创建的NioSocketChannel对象中的parent暂时是null。接着会调用父类的AbstractNioChannel构造器,并传入实际参数readInterestOp=SelectionKey.OP_READ。

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;

        try {
            ch.configureBlocking(false);
        } catch (IOException var7) {
            try {
                ch.close();
            } catch (IOException var6) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to close a partially initialized socket.", var6);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", var7);
        }
    }

        最后会调用父类AbstractNioChannel的构造器。

        至此,NioSocketChannel就完成了初始化,总结一下NioSocketChannel初始化所做的流程:

        1、调用NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个新的Java NioSocketChannel。

        2、初始化AbstractChannel对象并给属性赋值,具体赋值的属性如下:

                (1)id:每个Channel都会被分配一个唯一的id。

                (2)parent:属性值默认为null。

                (3)unsafe:通过调用newUnsafe()方法实例化一个Unsafe对象,它的类型是AbsractNioByteChannel.NioByteUnsafe内部类。

                (4)pipeline:是通过调用new DefaultChannelPipeline(this)新创建的实例。

        3、AbstractNIOChannel中被赋值的属性如下:

                (1)ch:被赋值为Java 原生SocketChannel,即NioSocketChannel的newSocket()方法返回的Java NIO SocketChannel。

                (2)readInterestOp:被赋值为SelectionKey.OP_READ。

                (3)ch:被配置为非阻塞,即调用ch.configureBlocking(false)方法

        4、NioSocketChannel中被赋值的属性:config = new NioSocketChannelConfig(this,socket.socket())。

4 ChannelPipeline的初始化

        上面在分析NioSocketChannel的初始化过程中,漏掉了一个关键的部分,即ChannelPipeline的初始化。在实例化一个Channel时,必须要实例化一个ChannelPipeline。在AbstractChannel的构造器中看到了Pipeline属性被初始化为DefaultChannelPipeline的实例。DefaultChannelPipeline构造器的代码如下:

protected DefaultChannelPipeline(Channel channel) {
        this.channel = (Channel)ObjectUtil.checkNotNull(channel, "channel");
        this.succeededFuture = new SucceededChannelFuture(channel, (EventExecutor)null);
        this.voidPromise = new VoidChannelPromise(channel, true);
        this.tail = new DefaultChannelPipeline.TailContext(this);
        this.head = new DefaultChannelPipeline.HeadContext(this);
        this.head.next = this.tail;
        this.tail.prev = this.head;
    }

        DefaultChannelPipeline 的构造器需要传入一个Channel,而这个Channel其实就是实例化的NioSocketChannel对象,DefaultChannelPipeline会将这个  NioSocketChannel对象保存在Channel属性中。DefaultChannelPipeline中还有两个属性是双向链表的头和尾,即Head和Tail。其实在DefaultChannelPipeline中维护了一个以AbstractChannelHandlerContext为节点元素的双向链表,这个链表是NEtty实现Pipeline机制的关键。先看HeadContext的继承层次结构,如下图所示:

        TailContext的继承层次结构图如下:

         可以看到,链表中Head是一个ChannelOutBoundHandler,而Tail是一个ChannelInBoundHandler。接着看HeadContext的构造器代码:

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, (EventExecutor)null, DefaultChannelPipeline.HEAD_NAME, false, true);
            this.unsafe = pipeline.channel().unsafe();
            this.setAddComplete();
        }

        它调用了父类AbstractChannelHandlerContext的构造器,并传入擦承诺书inbound=false,outbound=true。而TailContext的构造器与HeadContext刚好相反。

5 EventLoop的初始化

        回到最开始ChatClient用户代码中,一开始就实例化了一个NioEventLoopGrouop的对象,因此就从它的构造器中追踪EventLoop的初始化过程。首先来看NioEventLoopGrouop的类继承层次结构图:

        NioEventLoop中有几个重载的构造器,不过内容都没有太大的区别,最终都调用父类MultithreadEventLoopGroup的构造器,代码如下:

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

        如果传入的线程数nThreads是0,那么Netty会设置默认的线程数DEFAULT_EVENT_LOOP_THREADS,而这个默认的线程数怎么确定的呢?首先确定 DEFAULT_EVENT_LOOP_THREADS的值

private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

        Netty首先从系统属性中获取“io.netty.eventLoopThreads”的值,如果没有设置,就返回默认值,即CPU核数*2。回到MultithreadEventLoopGroup构造器中会继续调用父类MultithreadEventExecutorGroup的构造器。

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
        this.terminatedChildren = new AtomicInteger();
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        } else {
            if (executor == null) {
                executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
            }

            this.children = new EventExecutor[nThreads];

            int j;
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var18 = false;

                try {
                    var18 = true;
                    this.children[i] = this.newChild((Executor)executor, args);
                    success = true;
                    var18 = false;
                } catch (Exception var19) {
                    throw new IllegalStateException("failed to create a child event loop", var19);
                } finally {
                    if (var18) {
                        if (!success) {
                            int j;
                            for(j = 0; j < i; ++j) {
                                this.children[j].shutdownGracefully();
                            }

                            for(j = 0; j < i; ++j) {
                                EventExecutor e = this.children[j];

                                try {
                                    while(!e.isTerminated()) {
                                        e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                                    }
                                } catch (InterruptedException var20) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }

                    }
                }

                if (!success) {
                    for(j = 0; j < i; ++j) {
                        this.children[j].shutdownGracefully();
                    }

                    for(j = 0; j < i; ++j) {
                        EventExecutor e = this.children[j];

                        try {
                            while(!e.isTerminated()) {
                                e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException var22) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }

            this.chooser = chooserFactory.newChooser(this.children);
            FutureListener<Object> terminationListener = new FutureListener<Object>() {
                public void operationComplete(Future<Object> future) throws Exception {
                    if (MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
                        MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
                    }

                }
            };
            EventExecutor[] arr$ = this.children;
            j = arr$.length;

            for(int i$ = 0; i$ < j; ++i$) {
                EventExecutor e = arr$[i$];
                e.terminationFuture().addListener(terminationListener);
            }

            Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length);
            Collections.addAll(childrenSet, this.children);
            this.readonlyChildren = Collections.unmodifiableSet(childrenSet);
        }
    }

        继续进入newChooser方法查看实现逻辑,代码如下DefaultEventExecutorChooserFactory:

public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
    public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();

    private DefaultEventExecutorChooserFactory() {
    }

    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        return (EventExecutorChooser)(isPowerOfTwo(executors.length) ? new DefaultEventExecutorChooserFactory.PowerOfTowEventExecutorChooser(executors) : new DefaultEventExecutorChooserFactory.GenericEventExecutorChooser(executors));
    }

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        public EventExecutor next() {
            return this.executors[Math.abs(this.idx.getAndIncrement() % this.executors.length)];
        }
    }

    private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTowEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        public EventExecutor next() {
            return this.executors[this.idx.getAndIncrement() & this.executors.length - 1];
        }
    }
}

        上面代码主要表达的意思是:如果nThreads是2的平方,则使用PowerOfTowEventExecutorChooser,否则使用GenericEventExecutorChooser。这两个Chooser都重写next()方法。next()方法的主要功能就是将数组索引循环位移,如下图所示:

        当索引移动到最后一个位置时,再调用next方法就会将索引位置重新指向0,如下图所示:

        分析到这里,已经非常清楚 MultithreadEventLoopGroup中的处理逻辑,简单总结如下:

        1、创建一个大小为nThreads的SingleThreadEventExecutor数组。

        2、根据nThreads的大小,创建不同的Chooser,如果nThreads是2的平方,则使用PowerOfTowEventExecutorChooser,否则使用GenericEventExecutorChooser。不论使用哪个Chooser,它们的功能都是一样的,即从children数组中选出一个合适的EventExecutor实例。

        3、调用newChild()方法初始化children数组。

        根据上面代码,知道了MultithreadEventLoopGroup内部维护了一个EventExecutor数组,而Netty的EventLoopGroup的实现机制其实就建立在MultithreadEventLoopGroup之上。每当Netty需要一个EventLoop时,都会调用next方法获取一个可用的EventLoop。

6 将Channel注册到Selector

        前面提到Channel会在Bootstrap的initAndRegister中进行初始化,但是这个方法还会将初始化好的channel注册到NioEventLoop的Selector中。

        当Channel初始化后,紧接着会调用group().register()方法来向Selector注册Channel。继续跟踪,会发现其调用链路如下图所示:

        通过跟踪链路,最终发现在AbstractBootstrap的initAndRegister方法中调用的是Unsafe的register方法,接下来看一下AbstractChannel代码:

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            } else if (AbstractChannel.this.isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
            } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
                promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
            } else {
                AbstractChannel.this.eventLoop = eventLoop;
                if (eventLoop.inEventLoop()) {
                    this.register0(promise);
                } else {
                    try {
                        eventLoop.execute(new Runnable() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);
                            }
                        });
                    } catch (Throwable var4) {
                        AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
                        this.closeForcibly();
                        AbstractChannel.this.closeFuture.setClosed();
                        this.safeSetFailure(promise, var4);
                    }
                }

            }
        }

         首先,将EventLoop赋值给Channel的eventLoop属性,我们直到EventLoop对象其实是通过MultithreadEventLoopGroup的next方法获取的,根据前面的分析,可以确定next方法返回的eventLoop对象是NioEventLoop实例。register方法接着调用了register0方法,代码如下:

private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !this.ensureOpen(promise)) {
                    return;
                }

                boolean firstRegistration = this.neverRegistered;
                AbstractChannel.this.doRegister();
                this.neverRegistered = false;
                AbstractChannel.this.registered = true;
                AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
                this.safeSetSuccess(promise);
                AbstractChannel.this.pipeline.fireChannelRegistered();
                if (AbstractChannel.this.isActive()) {
                    if (firstRegistration) {
                        AbstractChannel.this.pipeline.fireChannelActive();
                    } else if (AbstractChannel.this.config().isAutoRead()) {
                        this.beginRead();
                    }
                }
            } catch (Throwable var3) {
                this.closeForcibly();
                AbstractChannel.this.closeFuture.setClosed();
                this.safeSetFailure(promise, var3);
            }

        }

        register0方法又调用了AbsractNioChannel的doRegister方法,代码如下:

protected void doRegister() throws Exception {
        boolean selected = false;

        while(true) {
            try {
                this.selectionKey = this.javaChannel().register(this.eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException var3) {
                if (selected) {
                    throw var3;
                }

                this.eventLoop().selectNow();
                selected = true;
            }
        }
    }

        看到javaChannel()这个方法,我们在前面就知道,它返回的是一个java NIO的SocketChannel对象。到了这里,就将SocketChannel注册到与eventLoop关联的Selector上了。

        总结一下Channel的注册过程,具体如下:

        1、在AbstractBootstrap的initAndRegister()方法中,通过group().register(channel)调用MultithreadEventLoopGroup的register()方法。

        2、在MultithreadEventLoopGroup的register()方法中,调用next方法获取一个可用的SingleThreadEventLoop,然后调用它的register方法。

        3、在SingleThreadEventLoop的register方法中,调用channel.unsafe().register(this,promise)方法获取Channel的unsafe()底层操作对象,然后调用Unsafe的register方法。

        4、在AbstractUnsafe的register方法中,调用register0方法注册到Channel对象。

        5、在AbstractUnsafe的register0方法中,调用AbstractNioChannel的doRegister方法。

        6、AbstractNioChannel的doRegiter方法通过javaChannel.register(eventLoop().selector,0,this)将Channel对应的Java NIO的SocketChannel注册到一个eventLoop的Selector中,并且将当前Channel作为Attachment与SocketChannel关联。

        总的来说,Channel的注册过程所做的工作就是将Channel与对应的EventLoop进行关联。因此,在Netty中,每个Channel都会关联一个特定的EventLoop,并且这个Channel中的所有I/O操作都是在这个EventLoop中执行的;当关联好Channel和EventLoop后,会继续调用底层的Java NIO的SocketChannel对象的register方法,将底层Java NIO的SocketChannel注册到指定的Selector中。通过这两步,就完成了Netty对Channel的注册过程。

     

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

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

相关文章

考研数学|跟武忠祥,刷什么习题集效果最好?

选择听哪位老师的课程并不是硬性规定。我个人觉得&#xff0c;关键在于根据自己的学习需求和情况来选择合适的学习方式。比如如果听武忠祥老师的课程可能更适合你&#xff0c;你可以选择武忠祥老师&#xff1b;而如果你希望通过大量的题目练习来提高解题能力&#xff0c;那么选…

IntelliJ IDEA 设置运行时环境变量

背景 博主要测试langchain4j&#xff0c;运行时需要OPENAI_BASE_URL和OPENAI_API_KEY这两个环境变量的值。 临时设置 Run -> Edit Configurations -> Edit Environmental Variables 永久设置 在系统环境变量中设置&#xff0c;教程无数。 注意&#xff1a;windows在…

进程地址空间的进一步认识

进程地址空间 地址空间的大小取决于系统的架构和操作系统的实现。在32位系统中&#xff0c;地址空间大小为2的32次方&#xff08;约为4GB&#xff09;。而在64位系统中&#xff0c;地址空间大小为2的64次方。 进程地址空间的划分使得不同的数据和代码可以在不同的区域中进行管…

IM项目题

消息协议 消息的可靠性 前言 IM系统的可靠性指的是端到端的可靠性&#xff0c;并不是tcp的可靠性&#xff0c;它是指客户端A&#xff0c;客户端B以及服务端三端通信之间的可靠性&#xff0c;并不是客户端A到服务端这么一个上行消息的可靠&#xff0c;这个tcp就可以保证了&#…

洛谷 P1378 油滴扩展

本道题可以理解成一个平面直角坐标系&#xff0c;在坐标系上标出整个矩形和油滴的坐标&#xff0c;计算两个油滴的面积和直径&#xff0c;判断点是否在圆内&#xff08;点与圆的位置关系&#xff09;&#xff0c;利用使用坐标求两点间距离的公式取解。 代码如下&#xff1a; …

定位及解决OOM

一、定义 内存溢出&#xff1a;OutOfMemoryError&#xff0c;是指因内存不够&#xff0c;导致操作新对象没有剩余空间。会导致频繁fullgc出现STW从而导致性能下降。 内存泄漏&#xff1a;指用malloc或new申请了一块内存&#xff0c;但是没有通过free或delete将内存释放&#…

一维坐标的移动(bfs)

在一个长度为n的坐标轴上&#xff0c;小S想从A点移动B点。 他的移动规则如下&#xff1a; 向前一步&#xff0c;坐标增加1。 向后一步&#xff0c;坐标减少1。 跳跃一步&#xff0c;使得坐标乘2。 小S不能移动到坐标小于0或大于n的位置。 小S想知道从A点移动到B点的最少步数是多…

四.排序(冒泡/选择)

目录 11-排序介绍 常见排序算法: 12-冒泡排序介绍 代码要求: 思路: 13-冒泡排序 代码: 14-选择排序 简单写法: 好的写法: 11-排序介绍 排序&#xff1a;将一组“无序”的记录序列调整为“有序”的记录序列。 列表排序&#xff1a;将无序列表变为有序列表 输入&#…

LeetCode 2312.卖木头块:动态规划(DP)

【LetMeFly】2312.卖木头块&#xff1a;动态规划(DP) 力扣题目链接&#xff1a;https://leetcode.cn/problems/selling-pieces-of-wood/ 给你两个整数 m 和 n &#xff0c;分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices &#xff0c;其中 prices[i] [hi, …

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现RIME-TCN-BiGRU-Attention霜冰算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述程…

面试经典150题(114-118)

leetcode 150道题 计划花两个月时候刷完之未完成后转&#xff0c;今天完成了5道(114-118)150 gap 了一周&#xff0c;以后就不记录时间了。。 114.(70. 爬楼梯) 题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不…

【CTF笔记】 CTF web方向笔记分享 免费 附预览图

个人不怎么记东西&#xff0c;笔记不多&#xff0c;师傅们凑合看… 百度网盘&#xff1a;https://pan.baidu.com/s/1PspihUX28Y_AOQZPurHqKA 麻烦各位师傅帮忙填写一下问卷&#xff0c;提取码在问卷填写结束后显示~ 【https://www.wjx.cn/vm/mBBTTKm.aspx# 】 &#xff08;…

6大赚钱平台大揭秘,正规靠谱,电脑手机均可操作增收

找到一个真正靠谱的赚钱平台&#xff0c;无疑是你开启创收之旅的绝佳起点&#xff01;接下来&#xff0c;我将为你提供一些建议&#xff0c;帮助你在这浩瀚的互联网世界中&#xff0c;稳稳地迈出赚取第一桶金的第一步。 参与调查问卷&#xff1a;像Swagbucks和YouGov这样的调查…

信号量——生产消费者模型

前文 在这一篇博客&#xff08;信号量博客&#xff09;中我曾经提及过信号量的知识&#xff0c;而当对信号量进行提炼总结时&#xff0c;大致是以下三点&#xff1a; 1. 信号量本质是一个计数器&#xff08;代表资源的数量&#xff09; 2. 申请信号量本质就是对资源的一种预定机…

AI大模型额外学习一:斯坦福AI西部世界小镇笔记(包括部署和源码分析)

文章目录 一、简单介绍1&#xff09;项目代码介绍2&#xff09;重新播放模拟3&#xff09;适当修改分叉模拟 二、部署斯坦福小镇Demo1&#xff09;准备工作2&#xff09;解决遇到的bug3&#xff09;启动服务器和前端 三、源码剖析1&#xff09;主题顺序 github链接 一、简单介…

luceda ipkiss教程 62:等长波导布线(二)

教程 27介绍了两段波导等长布线的例子&#xff0c;下面同样是通过控制偏移量实现三段波导的等长布线&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class demo(i3.Circuit):mmi i3.ChildCellProperty(doc"mmi in…

【面经八股】搜广推方向:面试记录(九)

【面经&八股】搜广推方向:面试记录(九) 文章目录 【面经&八股】搜广推方向:面试记录(九)1. 自我介绍2. 科研-项目经历问答3. 实习经历问答4. 八股5. 编程题6. 反问1. 自我介绍 。。。。。。 2. 科研-项目经历问答 挑了我的论文,一直揪着问,建议一定要熟悉自…

mysql主从复制/主从备份搭建

mysql主从复制/主从备份搭建 前言一、主从复制1&#xff09;为什么使用主从复制、读写分离&#xff1f;2&#xff09;主从复制原理 二、如何实现主从复制&#xff1f;1&#xff09;主库配置1、修改配置文件2、登录mysql&#xff1a; 2&#xff09;从库配置1、修改配置文件2、登…

函数-Python

师从黑马程序员 函数初体验 str1"asdf" str2"qewrew" str3"rtyuio" def my_len(data):count0for i in data:count1print(f"字符串{data}的长度是{count}")my_len(str1) my_len(str2) my_len(str3) 函数的定义 函数的调用 函数名&a…

爱恩斯坦棋小游戏使用C语言+ege/easyx实现

目录 1、游戏介绍和规则 2、需要用到的头文件 3、这里我也配上一个ege和easyx的下载链接吧&#xff0c;应该下一个就可以 4、运行结果部分展示 5、需要用到的图片要放在代码同一文件夹下 6、代码地址&#xff08;里面有需要用到的图片&#xff09; 1、游戏介绍和规则 规则如…