Netty Review - 服务端channel注册流程源码解析

文章目录

  • Pre
  • Netty主从Reactor线程模型
  • 服务端channel注册流程
  • 源码解读
    • 入口 serverBootstrap.bind(port)
    • 执行队列中的任务 : AbstractUnsafe#register0
    • 注册 doRegister()
  • 源码流程图

在这里插入图片描述

在这里插入图片描述


Pre

Netty Review - ServerBootstrap源码解析

Netty Review - NioServerSocketChannel源码分析


Netty主从Reactor线程模型

Netty 使用主从 Reactor 线程模型来处理并发连接和网络事件。

在 Netty 中,通常有两种类型的线程池:

  1. Boss 线程池:用于接受客户端连接请求,并将接受到的连接注册到 Worker 线程池的 EventLoop 中。Boss 线程池中的线程负责监听 ServerSocketChannel,并将接受到的连接分配给 Worker 线程池中的某个 EventLoop 处理。

  2. Worker 线程池:每个 Worker 线程池包含多个 EventLoop,每个 EventLoop 负责处理一组连接的读写和事件处理。当一个连接被注册到某个 Worker 线程池的 EventLoop 中时,该 EventLoop 将负责处理这个连接的所有事件,包括读取数据、写入数据、处理网络事件等。


主从 Reactor 线程模型的工作流程如下:

  1. 主线程池(Boss 线程池)负责监听 ServerSocketChannel 上的连接请求,并将接受到的连接请求分配给 Worker 线程池中的某个 EventLoop。

  2. Worker 线程池中的每个 EventLoop 都独立负责一组连接的读写和事件处理。当一个连接被注册到某个 EventLoop 上时,该 EventLoop 将会不断地轮询连接上是否有可读事件或可写事件,并在事件发生时进行相应的处理。

  3. 当有读写事件发生时,EventLoop 将调用对应的 ChannelHandler 进行处理。这些 ChannelHandler 可以进行数据解析、业务逻辑处理等操作。

  4. 处理完事件后,EventLoop 可能会将结果写回到连接中,或者关闭连接等。

通过主从 Reactor 线程模型,Netty 可以高效地处理大量的并发连接和网络事件,提高了网络应用程序的性能和可扩展性。


服务端channel注册流程

在Netty中,服务端Channel注册流程涉及以下几个关键步骤:

  1. 创建ServerBootstrap实例: 首先,需要创建一个ServerBootstrap实例,它是Netty提供的用于启动服务端的引导类。

  2. 配置ServerBootstrap: 使用ServerBootstrap实例,设置一系列参数,包括线程模型、Channel类型、处理器等。

  3. 绑定端口并启动服务: 调用ServerBootstrap的bind方法,指定端口并启动服务端。在bind方法内部,会进行以下操作:

    • 创建NioServerSocketChannel实例:用于表示服务端的Channel,内部封装了Java NIO中的ServerSocketChannel。

    • 初始化ChannelPipeline:为NioServerSocketChannel实例创建一个ChannelPipeline对象,用于管理ChannelHandler链。

    • 创建ChannelInitializer并添加到ChannelPipeline:ChannelInitializer是一个特殊的ChannelHandler,它用于在Channel注册到EventLoop之后初始化ChannelPipeline。在ChannelInitializer的initChannel方法中,可以向ChannelPipeline中添加自定义的ChannelHandler。

    • 获取EventLoopGroup并注册Channel:从ServerBootstrap中获取Boss EventLoopGroup,然后调用其register方法注册NioServerSocketChannel到EventLoop上。

  4. 注册Channel到EventLoop: 在调用register方法时,会将NioServerSocketChannel注册到Boss EventLoop上。在注册过程中,会执行以下操作:

    • 获取EventLoop:根据配置,从Boss EventLoopGroup中选择一个EventLoop。

    • 调用EventLoop的register方法:将NioServerSocketChannel注册到选定的EventLoop上。注册过程中,会创建一个NioServerSocketChannelUnsafe实例来处理注册过程,其中会调用底层的Java NIO方法将ServerSocketChannel注册到Selector上,并监听ACCEPT事件。

  5. 事件处理: 一旦NioServerSocketChannel注册到了EventLoop上,就会开始监听ACCEPT事件。当有新的连接接入时,会触发ACCEPT事件,EventLoop会调用相关的ChannelHandler进行处理,如调用ChannelInitializer的initChannel方法,添加用户自定义的ChannelHandler到新的连接的ChannelPipeline中。接着,新的连接就可以接受和处理客户端的请求了。

通过以上流程,服务端Channel在Netty中的注册过程就完成了,它可以接受客户端的连接,并将连接注册到EventLoop上进行事件处理。


源码解读

当我们梳理完

Netty Review - ServerBootstrap源码解析

Netty Review - NioServerSocketChannel源码分析

接下来让我们从下面这一行代码开始

ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
channelFuture.channel().closeFuture().sync();

这段代码用于启动服务端并阻塞当前线程直到服务端关闭。

  1. serverBootstrap.bind(9000):调用serverBootstrapbind()方法绑定端口9000,并返回一个ChannelFuture对象,表示绑定操作的异步结果。

  2. .sync():调用sync()方法阻塞当前线程,直到绑定操作完成。这样做是为了确保服务端在端口绑定完成后再继续执行后续代码。

  3. channelFuture.channel().closeFuture().sync():获取channelFuture中的channel(),然后调用其closeFuture()方法获取一个表示关闭操作的ChannelFuture对象。接着,再次调用sync()方法阻塞当前线程,直到关闭操作完成。这样做是为了让当前线程一直等待直到服务端关闭。

入口 serverBootstrap.bind(port)

这段代码是bind(int inetPort)方法的实现。

/**
 * Create a new {@link Channel} and bind it.
 */
public ChannelFuture bind(int inetPort) {
    // 调用bind方法,传入一个InetSocketAddress对象,该对象使用指定的端口号创建
    return bind(new InetSocketAddress(inetPort));
}

创建一个新的Channel并绑定到指定的端口。


doBind(final SocketAddress localAddress)

这段代码是doBind(final SocketAddress localAddress)方法的实现。

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 初始化并注册Channel,并返回一个ChannelFuture
    final ChannelFuture regFuture = initAndRegister();
    // 获取注册完成的Channel
    final Channel channel = regFuture.channel();
    // 如果注册过程中发生异常,则直接返回注册的ChannelFuture
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // 如果注册已经完成,则创建一个新的ChannelPromise,并执行绑定操作
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // 如果注册尚未完成,则创建一个PendingRegistrationPromise,并添加一个监听器等待注册完成后再执行绑定操作
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    // 如果注册过程中发生异常,则直接设置失败状态
                    promise.setFailure(cause);
                } else {
                    // 注册成功后执行绑定操作
                    promise.registered();
                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

这段代码的作用是执行绑定操作,并返回一个与绑定操作相关的ChannelFuture。


initAndRegister()

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 使用channelFactory创建一个新的Channel实例
        channel = channelFactory.newChannel();
        // 对新创建的Channel进行初始化
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            // 如果初始化过程中发生异常,关闭Channel
            channel.unsafe().closeForcibly();
            // 创建一个新的ChannelPromise,并设置失败状态
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // 如果channel为null,则创建一个FailedChannel实例,并设置失败状态
        return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    // 使用ChannelConfig的EventLoopGroup进行注册
    ChannelFuture regFuture = config().group().register(channel);
    // 如果注册过程中发生异常,则关闭Channel
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }

    // 返回注册的ChannelFuture
    return regFuture;
}

创建一个新的Channel实例并对其进行初始化,然后使用EventLoopGroup将其注册到事件循环中。最后返回一个与注册操作相关的ChannelFuture。


channelFactory.newChannel()

channelFactory.newChannel() 中的实现,请移步 Netty Review - NioServerSocketChannel源码分析

init(channel)

@Override
void init(Channel channel) throws Exception {
    // 设置Channel的选项
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }

    // 设置Channel的属性
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }

    // 获取Channel的Pipeline
    ChannelPipeline p = channel.pipeline();

    // 复制当前的子组、子处理器、子选项和子属性
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
    }

    // 向Channel的Pipeline中添加一个ChannelInitializer
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 添加用户配置的处理器到Pipeline中
                pipeline.addLast(handler);
            }

            // 在Channel的事件循环中执行
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 添加一个ServerBootstrapAcceptor到Pipeline中,用于接收新连接
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

init()方法的作用是初始化Channel,设置Channel的选项和属性,然后向ChannelPipeline中添加一个ChannelInitializer,该InitializerChannel的事件循环中执行,并向Pipeline中添加一个ServerBootstrapAcceptor,用于接收新连接。


config().group().register(channel)

在这里插入图片描述

    @Override
    public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

在这里插入图片描述

在这里插入图片描述

next()

io.netty.util.concurrent.DefaultEventExecutorChooserFactory.PowerOfTwoEventExecutorChooser#next
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    // 使用原子整数来维护索引,以确保在多线程环境中安全地获取下一个 EventExecutor 实例。
    private final AtomicInteger idx = new AtomicInteger();
    // 存储所有可用的 EventExecutor 实例的数组。
    private final EventExecutor[] executors;

    // 构造方法,接收一个 EventExecutor 实例数组作为参数,并将其存储在 executors 成员变量中。
    PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
        this.executors = executors;
    }

    // 选择下一个要使用的 EventExecutor 实例。
    // 通过对索引进行按位与操作(idx.getAndIncrement() & executors.length - 1),
    // 来确保索引始终在 executors 数组的有效范围内。
    // 由于 executors.length 必须是 2 的幂次方,因此使用按位与运算(&)可以有效地实现取模操作,
    // 从而将索引限制在数组长度范围内。
    @Override
    public EventExecutor next() {
        return executors[idx.getAndIncrement() & executors.length - 1];
    }
}

在这里插入图片描述

@Override
public ChannelFuture register(final ChannelPromise promise) {
    // 检查传入的 promise 参数是否为 null,如果为 null,则抛出 NullPointerException 异常。
    ObjectUtil.checkNotNull(promise, "promise");
    // 获取与该 promise 关联的 Channel 实例,通过 unsafe() 方法获取 Channel 的 Unsafe 实例,然后调用 register() 方法注册 Channel。
    // 这里调用的是 unsafe() 方法,表示使用一种不安全的方式直接注册 Channel,而不经过 EventLoop 的事件循环。
    // register() 方法将 Channel 注册到当前 EventLoop,由 EventLoop 负责管理该 Channel。
    promise.channel().unsafe().register(this, promise);
    // 返回传入的 promise 对象,即注册 Channel 的异步结果。
    return promise;
}

AbstractUnsafe#register

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 检查 eventLoop 参数是否为 null,如果为 null,则抛出 NullPointerException 异常。
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    }
    // 检查 Channel 是否已经注册到某个 EventLoop,如果已经注册,则设置 promise 的失败状态,并返回。
    if (isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    // 检查传入的 eventLoop 是否与当前 Channel 兼容,如果不兼容,则设置 promise 的失败状态,并返回。
    if (!isCompatible(eventLoop)) {
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    // 将当前 Channel 的 eventLoop 属性设置为传入的 eventLoop。
    AbstractChannel.this.eventLoop = eventLoop;

    // 判断当前线程是否在 eventLoop 的事件循环中,如果是,则直接调用 register0() 方法进行注册。
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        // 如果当前线程不在 eventLoop 的事件循环中,则通过 eventLoop.execute() 方法提交一个任务,让 eventLoop 执行注册操作。
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            // 如果无法将注册任务提交给 eventLoop 执行,则强制关闭 Channel,并设置 promise 的失败状态。
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }
}
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 检查 eventLoop 参数是否为 null,如果为 null,则抛出 NullPointerException 异常。
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    }
    // 检查 Channel 是否已经注册到某个 EventLoop,如果已经注册,则设置 promise 的失败状态,并返回。
    if (isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    // 检查传入的 eventLoop 是否与当前 Channel 兼容,如果不兼容,则设置 promise 的失败状态,并返回。
    if (!isCompatible(eventLoop)) {
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    // 将当前 Channel 的 eventLoop 属性设置为传入的 eventLoop。
    AbstractChannel.this.eventLoop = eventLoop;

    // 判断当前线程是否在 eventLoop 的事件循环中,如果是,则直接调用 register0() 方法进行注册。
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        // 如果当前线程不在 eventLoop 的事件循环中,则通过 eventLoop.execute() 方法提交一个任务,让 eventLoop 执行注册操作。
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            // 如果无法将注册任务提交给 eventLoop 执行,则强制关闭 Channel,并设置 promise 的失败状态。
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }
}

进入到异步这里

 try {
       eventLoop.execute(new Runnable() {
            @Override
            public void run() {
                register0(promise);
            }
        });
    } catch (Throwable t) {
       ....
    }

eventLoop.execute

在这里插入图片描述

@Override
public void execute(Runnable task) {
    // 检查任务是否为null
    if (task == null) {
        throw new NullPointerException("task");
    }

    // 判断当前线程是否在EventLoop的事件循环中
    boolean inEventLoop = inEventLoop();
    
    // 将任务添加到任务队列中
    addTask(task);
    
    // 如果当前线程不在EventLoop的事件循环中
    if (!inEventLoop) {
        // 启动一个新的线程来执行任务
        startThread();
        
        // 如果EventLoop已经被关闭
        if (isShutdown()) {
            boolean reject = false;
            try {
                // 检查任务是否可以从任务队列中移除
                if (removeTask(task)) {
                    reject = true;
                }
            } catch (UnsupportedOperationException e) {
                // 任务队列不支持移除操作,直接忽略异常,希望在任务完全终止之前能够拾取到任务
                // 最坏的情况下,在终止时进行记录
            }
            
            // 如果需要拒绝执行该任务
            if (reject) {
                reject();
            }
        }
    }

    // 如果不需要唤醒EventLoop来处理任务
    if (!addTaskWakesUp && wakesUpForTask(task)) {
        // 如果当前线程不在EventLoop的事件循环中,则唤醒EventLoop来处理任务
        wakeup(inEventLoop);
    }
}

addTask(task);

/**
 * 将任务添加到任务队列中,如果实例在关闭之前被关闭,则抛出{@link RejectedExecutionException}。
 */
protected void addTask(Runnable task) {
    // 检查任务是否为null
    if (task == null) {
        throw new NullPointerException("task");
    }
    // 如果无法将任务添加到队列中,则拒绝执行该任务
    if (!offerTask(task)) {
        reject(task);
    }
}

startThread();

private void startThread() {
    // 如果线程状态为未启动
    if (state == ST_NOT_STARTED) {
        // 尝试将状态从未启动更改为已启动
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            try {
                // 启动线程
                doStartThread();
            } catch (Throwable cause) {
                // 如果启动线程时发生异常,则将状态重置为未启动,并抛出异常
                STATE_UPDATER.set(this, ST_NOT_STARTED);
                PlatformDependent.throwException(cause);
            }
        }
    }
}

doStartThread()

private void doStartThread() {
    // 确保线程为空
    assert thread == null;
    // 在执行器上执行一个新的 Runnable
    executor.execute(new Runnable() {
        @Override
        public void run() {
            // 将当前线程设置为执行线程
            thread = Thread.currentThread();
            // 如果标记为已中断,则中断线程
            if (interrupted) {
                thread.interrupt();
            }

            boolean success = false;
            // 更新上次执行时间
            updateLastExecutionTime();
            try {
                // 运行单线程事件执行器的主要逻辑
                SingleThreadEventExecutor.this.run();
                // 执行成功
                success = true;
            } catch (Throwable t) {
                // 捕获并记录执行期间的异常
                logger.warn("Unexpected exception from an event executor: ", t);
            } finally {
                // 循环直到能够安全关闭
                for (;;) {
                    int oldState = state;
                    // 尝试将状态更改为正在关闭
                    if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
                            SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
                        break;
                    }
                }

                // 检查是否在循环结束时调用了 confirmShutdown()
                if (success && gracefulShutdownStartTime == 0) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
                                SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " +
                                "be called before run() implementation terminates.");
                    }
                }

                try {
                    // 运行所有剩余任务和关闭钩子
                    for (;;) {
                        if (confirmShutdown()) {
                            break;
                        }
                    }
                } finally {
                    try {
                        // 清理资源
                        cleanup();
                    } finally {
                        // 移除线程上的所有 FastThreadLocals,因为线程即将终止,并通知未来。
                        // 用户可能会在未来上阻塞,一旦解除阻塞,JVM 可能会终止并开始卸载类。
                        // 详情请参阅 https://github.com/netty/netty/issues/6596。
                        FastThreadLocal.removeAll();

                        // 设置执行器状态为终止
                        STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
                        // 释放线程锁
                        threadLock.release();
                        // 如果任务队列不为空,则记录警告日志
                        if (!taskQueue.isEmpty()) {
                            if (logger.isWarnEnabled()) {
                                logger.warn("An event executor terminated with " +
                                        "non-empty task queue (" + taskQueue.size() + ')');
                            }
                        }
                        // 设置终止未来为成功
                        terminationFuture.setSuccess(null);
                    }
                }
            }
        }
    });
}

SingleThreadEventExecutor.this.run()

在这里插入图片描述

@Override
protected void run() {
	// for死循环
    for (;;) {
        try {
            try {
                // 根据选择策略计算下一步的操作
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        // 继续循环,不进行任何操作
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // 忙等待策略,由于 NIO 不支持忙等待,因此执行 select 操作
                        // 不会直接执行 select 方法,而是会先设置 wakenUp 为 false,然后执行 select 方法
                        // 如果 wakenUp 为 true,则再次唤醒 Selector
                        // 这样做是为了减少唤醒 Selector 的开销
                        // 但是存在一个竞态条件:如果 wakenUp 在 select 之前设置为 true,则会导致不必要的唤醒
                        // 因此在 select 之后需要再次判断 wakenUp 是否为 true,如果是则再次唤醒 Selector
                        select(wakenUp.getAndSet(false));

                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                        // 继续执行后续操作
                    default:
                }
            } catch (IOException e) {
                // 如果在这里收到 IOException,则表示 Selector 出现问题,需要重建 Selector 并重试
                rebuildSelector0();
                handleLoopException(e);
                continue;
            }

            cancelledKeys = 0;
            needsToSelectAgain = false;
            final int ioRatio = this.ioRatio;
            if (ioRatio == 100) {
                // 如果 ioRatio 为 100%,则优先处理所有的 IO 事件,然后再执行所有任务
                try {
                    processSelectedKeys();
                } finally {
                    // 确保始终运行所有任务
                    runAllTasks();
                }
            } else {
                // 如果 ioRatio 不为 100%,则按比例处理 IO 事件和任务
                final long ioStartTime = System.nanoTime();
                try {
                    processSelectedKeys();
                } finally {
                    // 确保始终运行所有任务
                    final long ioTime = System.nanoTime() - ioStartTime;
                    runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                }
            }
        } catch (Throwable t) {
            // 处理循环中的异常
            handleLoopException(t);
        }
        // 即使循环处理中抛出异常,也始终处理关闭
        try {
            if (isShuttingDown()) {
                closeAll();
                if (confirmShutdown()) {
                    return;
                }
            }
        } catch (Throwable t) {
            // 处理关闭过程中的异常
            handleLoopException(t);
        }
    }
}

select(wakenUp.getAndSet(false))

private void select(boolean oldWakenUp) throws IOException {
    Selector selector = this.selector;
    try {
        int selectCnt = 0;
        long currentTimeNanos = System.nanoTime();
        long selectDeadLineNanos = currentTimeNanos + delayNanos(currentTimeNanos);

        for (;;) {
            long timeoutMillis = (selectDeadLineNanos - currentTimeNanos + 500000L) / 1000000L;
            if (timeoutMillis <= 0) {
                // 如果超时时间小于等于 0,则立即进行非阻塞的 selectNow 操作
                if (selectCnt == 0) {
                    selector.selectNow();
                    selectCnt = 1;
                }
                break;
            }

            // 如果有任务并且 wakenUp 的值为 true,则立即进行非阻塞的 selectNow 操作
            // 这是为了确保任务在 select 操作之前已经被执行
            if (hasTasks() && wakenUp.compareAndSet(false, true)) {
                selector.selectNow();
                selectCnt = 1;
                break;
            }

            // 执行阻塞的 select 操作,并记录选择的次数
            int selectedKeys = selector.select(timeoutMillis);
            selectCnt++;

            // 如果 select 操作返回了结果,或者已经被唤醒,或者有任务待执行,则立即退出循环
            if (selectedKeys != 0 || oldWakenUp || wakenUp.get() || hasTasks() || hasScheduledTasks()) {
                break;
            }

            // 如果当前线程被中断,则重置选择的键并退出循环
            if (Thread.interrupted()) {
                selectCnt = 1;
                break;
            }

            // 更新当前时间,并判断是否需要继续循环
            long time = System.nanoTime();
            if (time - TimeUnit.MILLISECONDS.toNanos(timeoutMillis) >= currentTimeNanos) {
                selectCnt = 1;
            } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
                // 如果 select 次数超过阈值,则重建 Selector
                selector = selectRebuildSelector(selectCnt);
                selectCnt = 1;
                break;
            }

            currentTimeNanos = time;
        }

        // 如果 select 次数超过了预设的阈值,则输出警告日志
        if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS) {
            logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                    selectCnt - 1, selector);
        }
    } catch (CancelledKeyException e) {
        // 取消键异常通常是无害的,只输出调试日志即可
        logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                selector, e);
    }
}

int selectedKeys = selector.select(timeoutMillis);

在这里插入图片描述

熟悉的NIO代码。


processSelectedKeys()

private void processSelectedKeys() {
   // 如果使用了优化的 selectedKeys 集合,则调用优化过的处理方法
   if (selectedKeys != null) {
       processSelectedKeysOptimized();
   } else {
       // 否则,调用普通的处理方法并传入 selector.selectedKeys() 作为参数
       processSelectedKeysPlain(selector.selectedKeys());
   }
}
private void processSelectedKeysOptimized() {
    // 遍历优化过的 selectedKeys 集合
    for (int i = 0; i < selectedKeys.size; ++i) {
        // 获取当前索引处的 SelectionKey
        final SelectionKey k = selectedKeys.keys[i];
        // 将数组中的该元素置为 null,以便在 Channel 关闭后可以被垃圾回收
        selectedKeys.keys[i] = null;

        // 获取 SelectionKey 对应的附件
        final Object a = k.attachment();

        // 如果附件是 AbstractNioChannel 类型,则调用 processSelectedKey 方法处理
        if (a instanceof AbstractNioChannel) {
            processSelectedKey(k, (AbstractNioChannel) a);
        } else {
            // 否则,认为附件是 NioTask 类型,将其转换为 NioTask<SelectableChannel> 并调用 processSelectedKey 方法处理
            @SuppressWarnings("unchecked")
            NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
            processSelectedKey(k, task);
        }

        // 如果需要再次进行 select,则重置 selectedKeys 集合并再次进行 select
        if (needsToSelectAgain) {
            // 将数组中的剩余元素置为 null,以便在 Channel 关闭后可以被垃圾回收
            selectedKeys.reset(i + 1);

            // 再次进行 select 操作
            selectAgain();
            // 重置索引为 -1,使循环从 0 开始
            i = -1;
        }
    }
}

在这里插入图片描述

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // 获取与 Channel 相关联的 NioUnsafe 对象
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    // 如果 SelectionKey 不再有效,则关闭 Channel
    if (!k.isValid()) {
        final EventLoop eventLoop;
        try {
            eventLoop = ch.eventLoop();
        } catch (Throwable ignored) {
            // 如果 Channel 实现抛出异常,表示没有事件循环,我们忽略此异常
            // 因为我们只想确定 ch 是否注册到此事件循环,因此具有关闭 ch 的权限
            return;
        }
        // 只有在 ch 仍然注册到此 EventLoop 时才关闭 ch
        // 如果 ch 已从事件循环中注销,则 SelectionKey 可能作为注销过程的一部分被取消注册
        // 但是通道仍然健康且不应关闭。
        // 详见 https://github.com/netty/netty/issues/5125
        if (eventLoop != this || eventLoop == null) {
            return;
        }
        // 关闭 Channel
        unsafe.close(unsafe.voidPromise());
        return;
    }

    try {
        int readyOps = k.readyOps();
        // 如果 OP_CONNECT 位被设置,则调用 finishConnect() 完成连接
        if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
            // 移除 OP_CONNECT 位,否则 Selector.select(..) 将总是立即返回而不阻塞
            // 详见 https://github.com/netty/netty/issues/924
            int ops = k.interestOps();
            ops &= ~SelectionKey.OP_CONNECT;
            k.interestOps(ops);
            // 完成连接
            unsafe.finishConnect();
        }

        // 先处理 OP_WRITE,因为我们可能能够写入一些排队的缓冲区,从而释放内存
        if ((readyOps & SelectionKey.OP_WRITE) != 0) {
            // 调用 forceFlush() 方法,该方法还会负责在没有剩余可写内容时清除 OP_WRITE 位
            ch.unsafe().forceFlush();
        }

        // 如果 OP_READ 或 OP_ACCEPT 被设置,或者 readyOps 为 0,则进行读取操作
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
    } catch (CancelledKeyException ignored) {
        // 如果出现 CancelledKeyException 异常,则关闭 Channel
        unsafe.close(unsafe.voidPromise());
    }
}

在这里插入图片描述


runAllTasks()

/**
 * 从任务队列中获取并运行所有任务。
 * 
 * @return 如果至少运行了一个任务,则返回 true
 */
protected boolean runAllTasks() {
    // 断言当前线程在事件循环中
    assert inEventLoop();
    boolean fetchedAll;
    boolean ranAtLeastOne = false;

    do {
        // 从计划任务队列中获取任务
        fetchedAll = fetchFromScheduledTaskQueue();
        // 运行任务队列中的所有任务
        if (runAllTasksFrom(taskQueue)) {
            ranAtLeastOne = true;
        }
    } while (!fetchedAll); // 继续处理,直到获取所有计划任务。

    // 如果至少运行了一个任务,则更新最后执行时间
    if (ranAtLeastOne) {
        lastExecutionTime = ScheduledFutureTask.nanoTime();
    }
    // 在运行完所有任务后执行的操作
    afterRunningAllTasks();
    return ranAtLeastOne;
}
/**
 * 从传入的任务队列中运行所有任务。
 *
 * @param taskQueue 要轮询和执行所有任务的任务队列。
 *
 * @return 如果至少执行了一个任务,则返回 true。
 */
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
    // 从任务队列中轮询出一个任务
    Runnable task = pollTaskFrom(taskQueue);
    if (task == null) {
        return false;
    }
    for (;;) {
        // 执行任务
        safeExecute(task);
        // 继续轮询下一个任务
        task = pollTaskFrom(taskQueue);
        if (task == null) {
            return true;
        }
    }
}
/**
 * 从任务队列中轮询出一个任务。
 *
 * @param taskQueue 要轮询的任务队列。
 *
 * @return 任务队列中的下一个任务,如果没有任务则返回 null。
 */
protected static Runnable pollTaskFrom(Queue<Runnable> taskQueue) {
    for (;;) {
        // 从任务队列中轮询出一个任务
        Runnable task = taskQueue.poll();
        // 如果轮询出来的任务是 WAKEUP_TASK,则继续轮询下一个任务
        if (task == WAKEUP_TASK) {
            continue;
        }
        return task;
    }
}

取出来的任务就是
在这里插入图片描述


执行队列中的任务 : AbstractUnsafe#register0

private void register0(ChannelPromise promise) {
    try {
        // 检查通道是否仍然打开,因为在注册调用之外的时间内,通道可能已关闭
        // 设置 ChannelPromise 为不可取消状态,以确保注册成功后无法取消
        // 并且检查通道是否仍然打开
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        // 判断是否是第一次注册
        boolean firstRegistration = neverRegistered;
        // 执行具体的注册逻辑
        doRegister();
        // 标记通道已经注册过
        neverRegistered = false;
        registered = true;

        // 在实际通知 promise 之前,确保先调用 handlerAdded(...) 方法。这是必要的,因为用户可能已经通过管道触发了事件。
        pipeline.invokeHandlerAddedIfNeeded();

        // 设置注册成功,并通知 ChannelPromise
        safeSetSuccess(promise);
        // 触发 ChannelRegistered 事件,通知管道上下文
        pipeline.fireChannelRegistered();
        // 只有在通道尚未激活并且已经注册过一次时才触发 channelActive 事件,避免重复触发
        if (isActive()) {
            if (firstRegistration) {
                // 如果是第一次注册,则触发 channelActive 事件
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                // 如果通道已经注册过且设置了自动读取,则重新开始读取,以便处理传入数据
                beginRead();
            }
        }
    } catch (Throwable t) {
        // 发生异常时,直接关闭通道以避免 FD 泄漏
        closeForcibly();
        closeFuture.setClosed();
        // 设置注册失败,并通知 ChannelPromise
        safeSetFailure(promise, t);
    }
}

注册 doRegister()

在这里插入图片描述

doRegister();

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // 尝试将通道注册到 EventLoop 的 Selector 上,关注的事件为 0,表示不关注任何事件
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                // 强制 Selector 立即执行 select 操作,因为 "canceled" SelectionKey 可能仍然被缓存,尚未移除,因为尚未调用 Select.select(..) 操作。
                eventLoop().selectNow();
                selected = true;
            } else {
                // 我们在之前已经强制执行了一次选择操作,但是 SelectionKey 仍然因为某种原因被缓存了,可能是 JDK 的 bug
                throw e;
            }
        }
    }
}
@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // 尝试将通道注册到 EventLoop 的 Selector 上,关注的事件为 0,表示不关注任何事件
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                // 强制 Selector 立即执行 select 操作,因为 "canceled" SelectionKey 可能仍然被缓存,尚未移除,因为尚未调用 Select.select(..) 操作。
                eventLoop().selectNow();
                selected = true;
            } else {
                // 我们在之前已经强制执行了一次选择操作,但是 SelectionKey 仍然因为某种原因被缓存了,可能是 JDK 的 bug
                throw e;
            }
        }
    }
}

熟悉的NIO代码

javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

在这里插入图片描述

pipeline.invokeHandlerAddedIfNeeded();

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // 已经注册到 EventLoop。现在是时候调用在注册完成之前添加的 ChannelHandlers 的回调方法了。
        callHandlerAddedForAllHandlers();
    }
}
private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;

        // 当前 Channel 已经注册。
        registered = true;

        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        // 置空以便垃圾回收。
        this.pendingHandlerCallbackHead = null;
    }

    // 必须在 synchronized(...) 块之外执行,否则 handlerAdded(...) 可能在持有锁的情况下被调用,
    // 如果 handlerAdded(...) 尝试从 EventLoop 外部添加另一个处理程序,则可能产生死锁。
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}

在这里插入图片描述

@Override
void execute() {
    EventExecutor executor = ctx.executor();
    if (executor.inEventLoop()) {
        // 如果当前线程在事件循环中,则直接调用 handlerAdded0(ctx)。
        callHandlerAdded0(ctx);
    } else {
        try {
            // 否则,提交任务到事件执行器中。
            executor.execute(this);
        } catch (RejectedExecutionException e) {
            // 如果任务被拒绝,则从处理程序链中移除该处理程序。
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "Can't invoke handlerAdded() as the EventExecutor {} rejected it, removing handler {}.",
                        executor, ctx.name(), e);
            }
            remove0(ctx);
            ctx.setRemoved();
        }
    }
}
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        // 调用处理程序的 handlerAdded 方法。
        ctx.callHandlerAdded();
    } catch (Throwable t) {
        boolean removed = false;
        try {
            // 如果处理程序添加失败,尝试将其从管道中移除。
            remove0(ctx);
            ctx.callHandlerRemoved();
            removed = true;
        } catch (Throwable t2) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to remove a handler: " + ctx.name(), t2);
            }
        }

        // 如果处理程序添加失败且已成功移除,则触发异常捕获事件,报告移除异常。
        // 否则,触发异常捕获事件,报告添加异常和移除异常。
        if (removed) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; removed.", t));
        } else {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; also failed to remove.", t));
        }
    }
}
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        // 调用处理程序的 handlerAdded 方法。
        ctx.callHandlerAdded();
    } catch (Throwable t) {
        boolean removed = false;
        try {
            // 如果处理程序添加失败,尝试将其从管道中移除。
            remove0(ctx);
            ctx.callHandlerRemoved();
            removed = true;
        } catch (Throwable t2) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to remove a handler: " + ctx.name(), t2);
            }
        }

        // 如果处理程序添加失败且已成功移除,则触发异常捕获事件,报告移除异常。
        // 否则,触发异常捕获事件,报告添加异常和移除异常。
        if (removed) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; removed.", t));
        } else {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; also failed to remove.", t));
        }
    }
}

在这里插入图片描述

/**
 * {@inheritDoc} 如果重写此方法,请确保调用 super!
 */
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        // 这应该总是为 true,根据我们当前的 DefaultChannelPipeline 实现。
        // 在 handlerAdded(...) 中调用 initChannel(...) 的好处是,如果 ChannelInitializer 添加了另一个 ChannelInitializer,
        // 不会出现排序意外。这是因为所有处理程序都将按预期的顺序添加。
        if (initChannel(ctx)) {

            // 完成 Channel 的初始化,现在移除初始化程序。
            removeState(ctx);
        }
    }
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.add(ctx)) { // 防止重入。
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            // 在调用 initChannel(...) 之前显式调用 exceptionCaught(...)。
            // 我们这样做是为了防止多次调用 initChannel(...)。
            exceptionCaught(ctx, cause);
        } finally {
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
        }
        return true;
    }
    return false;
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.add(ctx)) { // 防止重入。
        try {
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            // 在调用 initChannel(...) 之前显式调用 exceptionCaught(...)。
            // 我们这样做是为了防止多次调用 initChannel(...)。
            exceptionCaught(ctx, cause);
        } finally {
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
        }
        return true;
    }
    return false;
}

在这里插入图片描述

p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) throws Exception {
        // 获取管道
        final ChannelPipeline pipeline = ch.pipeline();
        // 获取配置的处理程序
        ChannelHandler handler = config.handler();
        // 如果存在处理程序,则将其添加到管道中
        if (handler != null) {
            pipeline.addLast(handler);
        }

        // 通过通道的事件循环执行一个新的Runnable任务
        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                // 添加一个新的 ServerBootstrapAcceptor 到管道中
                pipeline.addLast(new ServerBootstrapAcceptor(
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }
});
p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) throws Exception {
        // 获取管道
        final ChannelPipeline pipeline = ch.pipeline();
        // 获取配置的处理程序
        ChannelHandler handler = config.handler();
        // 如果存在处理程序,则将其添加到管道中
        if (handler != null) {
            pipeline.addLast(handler);
        }

        // 通过通道的事件循环执行一个新的Runnable任务
        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                // 添加一个新的 ServerBootstrapAcceptor 到管道中
                pipeline.addLast(new ServerBootstrapAcceptor(
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
            }
        });
    }
});

pipeline.fireChannelRegistered();

在这里插入图片描述

@Override
public final ChannelPipeline fireChannelRegistered() {
   // 通过头部上下文触发通道注册事件
   AbstractChannelHandlerContext.invokeChannelRegistered(head);
   return this;
}
/**
 * 通过给定的下一个上下文触发通道注册事件,如果在事件循环中,直接调用,否则提交到事件执行器中执行。
 *
 * @param next 要触发通道注册事件的下一个上下文
 */
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // 如果在事件循环中,直接调用通道注册事件
        next.invokeChannelRegistered();
    } else {
        // 否则,提交到事件执行器中执行
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRegistered();
            }
        });
    }
}
/**
 * 调用通道注册事件处理程序。
 * 如果处理程序被调用成功,则调用通道注册方法;否则,继续触发下一个上下文的通道注册事件。
 */
private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            // 如果处理程序被调用成功,则调用通道注册方法
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            // 处理程序调用时发生异常,通知异常处理程序
            notifyHandlerException(t);
        }
    } else {
        // 处理程序未被调用成功,继续触发下一个上下文的通道注册事件
        fireChannelRegistered();
    }
}

在这里插入图片描述

pipeline.fireChannelActive();

if (isActive()) {
    if (firstRegistration) {
        // 如果是首次注册,则触发通道激活事件
        pipeline.fireChannelActive();
    } else if (config().isAutoRead()) {
        // 如果通道已经注册过,并且设置了自动读取,则重新开始读取以处理传入数据
        beginRead();
    }
}
@Override
public final ChannelPipeline fireChannelActive() {
    // 调用头部上下文的 invokeChannelActive 方法来触发 ChannelActive 事件
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    // 如果当前线程是事件循环线程,则直接调用 invokeChannelActive 方法
    if (executor.inEventLoop()) {
        next.invokeChannelActive();
    } else {
        // 否则,通过事件执行器异步执行 invokeChannelActive 方法
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelActive();
            }
        });
    }
}

在这里插入图片描述


源码流程图

在这里插入图片描述

图都给你画好了,戳这里


在这里插入图片描述

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

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

相关文章

【机器学习案例4】为机器学习算法编码分类数据【含源码】

目录 编码分类数据 序数编码 标签编码 一次性编码 目标编码 目标编码的优点 目标编码的缺点 在现实生活中,收集的原始数据很少采用我们可以直接用于机器学习模型的格式,即数值型数据。因此,需要进行一些预处理,以便以正确的格式呈现数据、选择信息丰富的数据或降低其…

C++ 特殊类的实现

一、请设计一个类&#xff0c;不能被拷贝 拷贝只会放生在两个场景中&#xff1a;拷贝构造函数以及赋值运算符重载&#xff0c;因此想要让一个类禁止拷贝&#xff0c;只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 在C98中&#xff1a;将拷贝构造函数与赋值运算符重载…

2024-2-14-复习作业

1> 要求&#xff1a; 源代码&#xff1a; #include<stdio.h> #define N 50 int main(int argc, char const *argv[]) {int arr[N][N];int n;printf("please enter n :");scanf("%d",&n);for(int i1;i<n;i){for(int j1;j<i;j){if(j1 |…

机器学习---规则学习(序贯覆盖、单条规则学习、剪枝优化)

1. 序贯覆盖 回归&#xff1a; 分类&#xff1a; 聚类&#xff1a; 逻辑规则&#xff1a; 读作&#xff1a;若&#xff08;文字1且文字2且...&#xff09;&#xff0c;则目标概念成立 规则集&#xff1a;充分性与必要性&#xff1b;冲突消解&#xff1a;顺序规则、缺省规则…

vuex中mutations详解,与actions的区别

Vuex 的 Mutations 是用于改变 Vuex Store 中状态的一种方式。它是一个同步的操作&#xff0c;用于直接修改 Store 中的状态。 Mutations 有以下特点&#xff1a; 同步操作&#xff1a;Mutations 是同步的&#xff0c;这意味着它们会立即执行并修改状态。原子性&#xff1a;…

计算机组成原理:存储系统【二】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;计算机组成与原理基础 &#x1f6f0;️1 Cache概述&#x1f6e9;️1.1 局部性原理&#x1f6eb;1.1.1 空间局部性&#x1f6eb;1.1.2 时间局部性 &#x1f6e9;️1.2 性能指标&#x1f6eb…

linux系统zabbix自动发现主机

自动发现主机 新的主机浏览器配置创建发现规则创建发现主机后动作 新的主机 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm# yum clean allyum install zabbix-agentvim /etc/zabbix/zabbix_agentd.conf Server10.12.153.1…

SpringBoot+Tess4J实现本地与远程图片的文字识别

Spring Boot应用程序里集成Tess4J来实现OCR&#xff08;光学字符识别&#xff09;&#xff0c;以识别出本地和远程图片中的文字 一、添加依赖 <dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess4j</artifactId><vers…

Linux信号集与信号集相关函数

阻塞信号集和未决信号集&#xff1a; 例如&#xff1a;当进程收到SIGINT信号后&#xff0c;首先被保留在未决信号集中&#xff0c;此时标识位为1&#xff0c;当这个信号被处理之前&#xff0c;先检查阻塞信号集中对应的编号的位上的标识是否为1&#xff1b; 为1表示该信号被当…

ChatGPT高效提问—prompt实践(法律助手)

ChatGPT高效提问—prompt实践&#xff08;法律助手&#xff09; ​ 作为现代法治国家的公民&#xff0c;无论我们是否从事法律相关的工作&#xff0c;都难免会遇到法律问题&#xff0c;那么如何争取自身合法利益最大化呢&#xff1f;很多人大概率会第一时间查询相关的法律知识…

Java奇缘:林浩然与杨凌芸的数学冒险记

Java奇缘&#xff1a;林浩然与杨凌芸的数学冒险记 Java Adventure: The Mathematical Odyssey of Lin Haoran and Yang Lingyun 在Java编程世界的某一个角落&#xff0c;住着两位才华横溢的程序员——林浩然和杨凌芸。林浩然&#xff0c;人称“算法大侠”&#xff0c;对Java Ma…

C语言中整数除法的特性

目录 介绍 解决方法 例1 例2 介绍 在 C 语言中&#xff0c;整数除法有一个特性&#xff0c;即它会对结果进行截断而不是四舍五入。这意味着无论结果是正数还是负数&#xff0c;除法的结果都将向零取整。这也就是说&#xff0c;C 语言中的整数除法会直接截断小数部分&#x…

Spring Boot 笔记 021 项目部署

1.1 引入坐标&#xff0c;并双击package打包成jar包 1.2 在服务器上运行jar包 1.3 使用postman测试 2.1 运行配置 2.1.1 命令更改端口 java -jar big-event-1.0-SNAPSHOT.jar --server.port7777 2.1.2 环境变量更新&#xff08;略&#xff09; 2.1.3 外部配置文件&#xff0c…

LeetCode:118.杨辉三角

118. 杨辉三角 - 力扣&#xff08;LeetCode&#xff09;&#xff0c; 前言&#xff1a;平平无奇的实现&#xff0c;数组理清了的话就很easy&#xff0c;值得说的是给定的参数 int* returnSize, int** returnColumnSizes 是什么意思&#xff0c;还得熟悉适应&#xff0c;博主…

深入了解pip和conda:高效Python环境管理的必备指南

pip相关命令: 更新包之前最好更新一下pip&#xff0c;因为更新其他包底层是依赖 pip pip show pippython -m pip install --upgrade pippython更新包&#xff1a; - ​ pip install --upgrade 包 pip install pandas- ​ pip install --upgrade 包名称版本号查看那些包需要更…

【leetcode热题100】交错字符串

给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 ... snt t1 t2 ... tm|n - m| < 1交错 是 s1 …

CAN通讯协议学习

介绍 它是一种异步通讯&#xff0c;can_high和can_low两条线利用的是电位差传输信号&#xff0c;抗干扰能力强&#xff0c;但是必须要有can控制器如TJA1050&#xff08;我的开发板&#xff09; 当 CAN 节点需要发送数据时&#xff0c;控制器把要发送的二进制编码通过 CAN_Tx 线…

牛客JZ 36二叉搜索树与双向链表

描述 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表。如下图所示 数据范围&#xff1a;输入二叉树的节点数 0≤n≤10000≤n≤1000&#xff0c;二叉树中每个节点的值 0≤val≤10000≤val≤1000 要求&#xff1a;空间复杂度O(1)&#xff08;即在原树上…

计算机毕业设计分享-SSM实验室耗材管理系统 13205(赠送源码数据库)JAVA、PHP,node.js,C++、python,大屏数据可视化等

SSM实验室耗材管理系统 摘 要 本课题研究的实验室耗材管理系统&#xff0c;主要功能模块包括用户管理、耗材管理、入库记录、出库记录、报废登记、供应商管理、耗材类别、实验室管理等&#xff0c;采取面对对象的开发模式进行软件的开发和硬体的架设&#xff0c;能很好的满足实…

CSS设置盒子阴影

语法 box-shadow: *h-shadow v-shadow blur spread color* inset; 注释: box-shadow向框添加一个或多个阴影. 该属性是由逗号分隔的阴影列表,每个阴影由2-4个长度值、可选的颜色值及可选的inset关键词来规定。省略长度的值是0。 外阴影 a、给元素右边框和下边框加外阴影——把…