Netty源码分析---ChannelInitializer工作原理详解

Netty源码分析:服务端启动全过程(篇幅很长) Netty源码分析:服务端启动全过程一般服务端的代码如下所示: public final class SimpleServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); 阅读详情

ChannelInitializer 工作原理详解

关键理解:两个阶段

阶段一:添加 ChannelInitializer 到 pipeline

// 在 ServerBootstrap 中
b.childHandler(new MyChannelInitializer())

此时发生了什么:

Pipeline 状态:
head -> MyChannelInitializer -> tail

注意:
- LineBasedFrameDecoder 还没添加
- StringDecoder 还没添加  
- StringEncoder 还没添加
- MyServerHandler 还没添加

只有 MyChannelInitializer 这一个 handler 在 pipeline 中!

阶段二:register 时触发 ChannelInitializer.handlerAdded()

当 invokeHandlerAddedIfNeeded() 被调用时:

// 1. 触发 MyChannelInitializer 的 handlerAdded() 方法
//    (这个方法在父类 ChannelInitializer 中)
MyChannelInitializer.handlerAdded(ctx)  // 继承自父类

// 2. handlerAdded() 内部会调用你重写的 initChannel() 方法
protected void initChannel(SocketChannel channel) {
    // 现在才真正添加这些 handlers
    channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
    channel.pipeline().addLast(new StringDecoder(...));
    channel.pipeline().addLast(new StringEncoder(...));
    channel.pipeline().addLast(new MyServerHandler());
}

// 3. initChannel() 执行完后,ChannelInitializer 把自己从 pipeline 中移除

最终 Pipeline 状态:

head -> LineBasedFrameDecoder -> StringDecoder -> StringEncoder -> MyServerHandler -> tail

注意:MyChannelInitializer 已经不在 pipeline 中了!

详细时序图

时间线:从启动到 pipeline 构建完成

1. ServerBootstrap 启动
   ↓
   b.childHandler(new MyChannelInitializer())
   ↓
   【此时】Pipeline: head -> MyChannelInitializer -> tail
   ↓
   
2. 客户端连接,创建 SocketChannel
   ↓
   init(channel) - 将 MyChannelInitializer 添加到新 channel 的 pipeline
   ↓
   【此时】新 channel 的 Pipeline: head -> MyChannelInitializer -> tail
   ↓
   
3. register 操作
   ↓
   doRegister() - 注册到 Selector
   ↓
   invokeHandlerAddedIfNeeded() ⭐ 关键点
   ↓
   
4. 执行 MyChannelInitializer.handlerAdded()
   (这个方法在父类 ChannelInitializer 中实现)
   ↓
   handlerAdded() 内部调用 initChannel(channel)
   ↓
   
5. 执行你重写的 initChannel() 方法
   ↓
   pipeline.addLast(new LineBasedFrameDecoder(1024))
   pipeline.addLast(new StringDecoder(...))
   pipeline.addLast(new StringEncoder(...))
   pipeline.addLast(new MyServerHandler())
   ↓
   
6. ChannelInitializer 移除自己
   ↓
   【最终】Pipeline: head -> LineBasedFrameDecoder -> StringDecoder 
                          -> StringEncoder -> MyServerHandler -> tail

源码验证

1. ChannelInitializer 的 handlerAdded() 方法

// ChannelInitializer 类(你的 MyChannelInitializer 的父类)
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    // 这就是被 invokeHandlerAddedIfNeeded() 触发的方法!
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 这里会调用你重写的 initChannel 方法
            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;
}

2. 你的 MyChannelInitializer

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    // 你只需要重写这个方法
    @Override
    protected void initChannel(SocketChannel channel) {
        // 这个方法会在父类的 handlerAdded() 中被调用
        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
        channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
        channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
        channel.pipeline().addLast(new MyServerHandler());
    }
    
    // 注意:你不需要重写 handlerAdded(),因为父类已经实现了
}

时序:

// 1. invokeHandlerAddedIfNeeded() 触发
MyChannelInitializer.handlerAdded()  // 父类的方法

// 2. 在 handlerAdded() 内部调用
initChannel(channel)  // 你重写的方法

// 3. 在 initChannel() 中
pipeline.addLast(new LineBasedFrameDecoder(1024))
    ↓
    // 这时会立即触发 LineBasedFrameDecoder.handlerAdded()
    
pipeline.addLast(new StringDecoder(...))
    ↓
    // 这时会立即触发 StringDecoder.handlerAdded()
    
// 以此类推...

触发了两层 handlerAdded():

第一层(直接触发):

MyChannelInitializer.handlerAdded()  // 继承自父类 ChannelInitializer

第二层(间接触发):

// 在 MyChannelInitializer.handlerAdded() 内部
// 调用 initChannel() 时,每添加一个 handler 都会触发它的 handlerAdded()

LineBasedFrameDecoder.handlerAdded()
StringDecoder.handlerAdded()
StringEncoder.handlerAdded()
MyServerHandler.handlerAdded()

完整的调用链

register0()
    ↓
invokeHandlerAddedIfNeeded()
    ↓
callHandlerAddedForAllHandlers()
    ↓
PendingHandlerAddedTask.execute()
    ↓
callHandlerAdded0(MyChannelInitializer的context)
    ↓
MyChannelInitializer.handlerAdded()  // 父类 ChannelInitializer 的方法
    ↓
initChannel(channel)  // 调用你重写的方法
    ↓
pipeline.addLast(new LineBasedFrameDecoder(1024))
    ↓
    callHandlerAdded0(LineBasedFrameDecoder的context)
    ↓
    LineBasedFrameDecoder.handlerAdded()
    
pipeline.addLast(new StringDecoder(...))
    ↓
    callHandlerAdded0(StringDecoder的context)
    ↓
    StringDecoder.handlerAdded()
    
// ... 以此类推

最后:
pipeline.remove(MyChannelInitializer)  // 移除自己

为什么要这样设计?

1. 延迟初始化

// 启动时只添加 ChannelInitializer
b.childHandler(new MyChannelInitializer())

// 等到 channel 真正 register 后,才添加真正的 handlers
// 这样可以确保在正确的时机、正确的线程中初始化

2. 代码复用

// 你只需要关心添加哪些 handlers
protected void initChannel(SocketChannel channel) {
    channel.pipeline().addLast(...);
}

// 不需要关心何时调用、如何移除等细节
// 这些都由父类 ChannelInitializer 处理

3. 自动清理

// ChannelInitializer 完成任务后自动移除自己
// 不会占用 pipeline 的空间
// 最终 pipeline 中只有真正的业务 handlers

实际运行示例

假设你启动服务器,有客户端连接:

【步骤1】服务器启动
Pipeline: head -> MyChannelInitializer -> tail

【步骤2】客户端连接,触发 register
invokeHandlerAddedIfNeeded() 被调用

【步骤3】执行 MyChannelInitializer.handlerAdded()
进入你的 initChannel() 方法

【步骤4】添加第一个 handler
pipeline.addLast(new LineBasedFrameDecoder(1024))
Pipeline: head -> MyChannelInitializer -> LineBasedFrameDecoder -> tail
触发 LineBasedFrameDecoder.handlerAdded()

【步骤5】添加第二个 handler
pipeline.addLast(new StringDecoder(...))
Pipeline: head -> MyChannelInitializer -> LineBasedFrameDecoder -> StringDecoder -> tail
触发 StringDecoder.handlerAdded()

【步骤6】添加第三个 handler
pipeline.addLast(new StringEncoder(...))
Pipeline: head -> MyChannelInitializer -> LineBasedFrameDecoder 
         -> StringDecoder -> StringEncoder -> tail
触发 StringEncoder.handlerAdded()

【步骤7】添加第四个 handler
pipeline.addLast(new MyServerHandler())
Pipeline: head -> MyChannelInitializer -> LineBasedFrameDecoder 
         -> StringDecoder -> StringEncoder -> MyServerHandler -> tail
触发 MyServerHandler.handlerAdded()

【步骤8】移除 ChannelInitializer
pipeline.remove(MyChannelInitializer)
最终 Pipeline: head -> LineBasedFrameDecoder -> StringDecoder 
                    -> StringEncoder -> MyServerHandler -> tail

总结

核心理解:

  1. ChannelInitializer 是一个特殊的 handler

    • 它的作用是添加其他 handlers
    • 完成任务后会自动移除自己
  2. invokeHandlerAddedIfNeeded() 直接触发的是 ChannelInitializer.handlerAdded()

    • 这个方法在父类中实现
    • 你不需要重写它
  3. 你重写的 initChannel() 会被 handlerAdded() 调用

    • 在 initChannel() 中添加的每个 handler
    • 都会立即触发它自己的 handlerAdded()
  4. 最终结果

    • ChannelInitializer 消失
    • 你添加的 handlers 都在 pipeline 中
    • 每个 handler 的 handlerAdded() 都被调用过了

简单比喻

ChannelInitializer 就像一个"装修工人":

  1. 雇佣阶段(添加到 pipeline)

    b.childHandler(new MyChannelInitializer())
    // 此时:只有装修工人进场了
    // Pipeline: head -> 装修工人 -> tail
    
  2. 开工信号(invokeHandlerAddedIfNeeded)

    // register 完成后,告诉装修工人:"可以开始干活了!"
    装修工人.handlerAdded()  // 父类的方法
    
  3. 装修工人开始干活(调用你的 initChannel)

    protected void initChannel(SocketChannel channel) {
        // 装修工人开始搬运真正的设备进场
        channel.pipeline().addLast(new LineBasedFrameDecoder(1024));  // 搬进设备1
        channel.pipeline().addLast(new StringDecoder(...));            // 搬进设备2
        channel.pipeline().addLast(new StringEncoder(...));            // 搬进设备3
        channel.pipeline().addLast(new MyServerHandler());             // 搬进设备4
    }
    
  4. 装修工人离场

    // 设备都搬进来了,装修工人的任务完成,自己离开
    // 最终 Pipeline: head -> 设备1 -> 设备2 -> 设备3 -> 设备4 -> tail
    

关键点:

  • invokeHandlerAddedIfNeeded() 触发的是"装修工人"(ChannelInitializer)的 handlerAdded()
  • "装修工人"的 handlerAdded() 在父类中已经实现好了,你不需要写
  • 你只需要告诉"装修工人"要搬哪些"设备"进来(重写 initChannel())
  • 每个"设备"搬进来时,也会触发它自己的 handlerAdded()
Netty 源码分析 09 ChannelHandler ChannelHandler(一) @Sharable注解,ChannelHandler 是否可共享,即是否可以被多次添加 ChannelInitializer ,一个特殊的 ChannelHandler ,用于 Channel 注册到 EventLoop 后,执行自定义的初始化操作。一般情况下,初始化自定义的 ChannelHandler 到 Channel 中 对于#h... 阅读详情

相关推荐

Netty源码分析(三)—数据管道ChannelPipeline源码分析

Netty源码分析(三)—数据管道ChannelPipeline源码分析 ChannelPipeline数据管道是ChannelHandler数据处理器的容器,负责ChannelHandler的管理和事件的拦截与调度 个人主页:tuzhenyu’s page 原文地址:Netty源码分析(三)—数据管道ChannelPipeline源码分析 (0) Channe

tuzhenyu的博客 4887

netty理论之源码分析⑥ 细讲ChannelInitializer

1.ChannelInitializer是啥 一种特殊的ChannelInboundHandler,它提供了在通道注册到eventLoop后初始化通道的简单方法,ChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始化操作。ChannelInitializer虽然会在一开始会被注册到Cha...

weixin_39666581的博客 3609

MinerU安装指南:从源码到验证的完整流程

本文详细介绍了MinerU文档转换工具的安装流程,从源码获取、依赖安装到验证步骤,提供了完整的安装指南和常见问题解决方案。特别适合技术写作者和开发者快速部署这款高效的文档转换工具,提升工作效率。

weixin_29243279的博客 547

15-ChannelInitializer

文章目录ChannelInitializer一、继承关系二、源码2.1 注释2.2 源码分析2.3 handlerAdded和channelRegistered三、使用示例 ChannelInitializer ChannelInitializer 是一种特殊的 ChannelHandler,它也是一种 ChannelInboundHandler,它提供了在通道注册到 EventLoop 后初...

my_momo_csdn的博客 1959

2025年春科研论文写作指导(如何写好科研论文)研究生网课期末考试

希望能帮助到大家

beijieweiyu的博客 5798

Linux 使用rm -rf /*命令后,恢复系统方法

Linux 使用rm -rf /*命令后,恢复系统方法 个人学习记录 这里实验环境采用的是radhel7.1的版本: 个人认为核心就是缺失什么组件,就从别的地方复制什么组件过来,先把系统拉起来,再慢慢恢复配置 进入救援模式 1、将/dev/sda两个分区分别挂载看看,里面的内容,有内容的就是根存在的分区 2、ifconfig eno16777777 192.168.8.2 3、从另外一台一样系统的根 。用 tar打包,一个个文件夹拷贝过来 4、到坏系统,scp另外一个系统的etc.tar.gz 5

weixin_43603476的博客 1万+

Netty源码篇 ChannelInitializer

netty 的 ChannelInitializer 与注意事项

qq_45090949的博客 2104

vCenter6.7证书过期导致503报错的全面修复指南:从STS证书更新到SSL信任修复

本文详细解析了VMware vCenter 6.7因STS证书过期导致503服务不可用错误的根本原因,并提供了从诊断、更新STS证书到修复SSL信任链的完整解决方案。通过命令行工具检查证书状态,使用官方脚本更新过期STS证书,并利用lsdoctor工具修复深层SSL信任问题,最终指导用户使用certificate-manager彻底重置所有证书,帮助管理员快速恢复vCenter服务。

pz890123的博客 1294

Netty中ChannelInitializer的使用

Netty中ChannelInitializer的使用

Lemon_MY的博客 5592

ROS2多线程处理RTSP视频流避坑指南:OpenCV解码丢帧问题解决方案

本文针对ROS2中处理RTSP视频流时OpenCV解码丢帧的常见问题,提供了专业的解决方案。通过深入分析单线程模型的瓶颈,提出并详细拆解了基于生产者-消费者多线程模型的健壮实现方案,有效分离数据采集与处理,从而显著提升视频流的稳定性和流畅度。

weixin_29199873的博客 168

python123题目——个税计算器

类型:简单分支。

m0_62488776的博客 5805

Netty源码解析之ChannelInitializer(三十六)

ChannelInitializer是一种特殊的ChannelInboundHandler,可以通过一种简单的方式(调用initChannel方法)来初始化Channel。 注意:当initChannel被执行完后,会将当前的handler从Pipeline中移除。 执行流程,点击 ServerBootstrap 类: 1、 2、点击AbstractBootstrap 类: // 服务器绑定端口监听 ChannelFuture f = b.bind(port).sync(); .

nandao158的博客 1334

Converting Phase Noise to Random Jitter(Period)

推导了Phase Noise to Random Jitter(Period)的转换过程,解释了分频对Phase Noise & Spur(每2分频改善6dB)的影响,每N分频,TIE RJ(1𝜎)不变(之前已推导),而Period RJ(1𝜎)增加√N倍。

u010486560的博客 2039

Netty源码分析-ChannelInitializer

ChannelInitializer是一个PPLine的初始化工具,可以往PPLine里面设置Handler。 protected abstract void initChannel(C ch) throws Exception; 我们可以重写此方法来完成初始化动作,往PPLine当中加入Handler。 .childHandler(new ChannelInitializer<S...

大新博客 1508

把 SAP Query 用到极致:ABAP Query 的核心概念、配置流程、权限与运输治理全解析

摘要:SAP Query 是一款面向业务用户的自助报表工具,可解决标准报表无法满足业务需求的问题。其核心由四大组件构成:Queries(报表定义)、InfoSets(数据视图)、User Groups(权限分组)和Translation(多语言支持)。通过SQ03、SQ02、SQ01事务码分步创建用户组、数据视图和查询,业务用户无需ABAP知识即可生成Basic List(明细)、Statistics(统计)和Ranked List(排名)三种报表形式。典型案例展示了如何为采购部门设计包含明细追溯和Top1

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 182

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【Netty学习】 ChannelInitializer 学习

ChannelInitializer在Netty中是一个很重要的东西。也是4.x版本中用户接触比较多的一个类 它本身是继承ChannelInboundHandlerAdapter的。实现ChannelInboundHandler类 【推荐1】Netty4 ChannelPipeLine分析★★★★☆ 【推荐2】java netty之ChannelPipeline★★★☆...

440

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Netty 源码分析_ChannelHandlerContext和ChannelInitializer_(4)写数据

可以看到,Channel被包装为ChannelPromise,它持有Channel和EventLoop。 在上节中介绍了ChannelPipeline,其中常用的方法是addLast(handler)将各类ChannelHandler实例添加到Channel的 ChannelPipeline中。下面我们来看addLast(handler)方法的实现: 最后调用:callHandlerAdded0 方法: 执行刚刚被添加的ChannelHander对象的handlerAdde...

专注Java(全栈)应用开发,求知若渴,虚心若愚,talk is cheap, show me the code. 648

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Reactor主从模型以及netty之ChannelInitializer源码分析

fd

qq_43409811的博客 278

Netty 源码分析(一)

Netty 实现通信的步骤: 1.创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信读写。 2.创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。 3.创建一个实际处理数据的类Channellnitalizer,进行初始化的准备工作,比如设置接受传出数据的字符 格式 以及实际处理数据的接口 4.绑定接

lm324114的专栏 8175

Netty 源码分析——ChannelPipeline

Netty 源码分析——ChannelPipeline 通过前面的两章我们分析了客户端和服务端的流程代码,其中在初始化 Channel 的时候一定会看到一个 ChannelPipeline。所以在 Netty 每个 Channel 中有且仅有一个 ChannelPipeline。 比如我们来看 NioSocketChannel 的构造器初始化流程是 NioSocketChannel -> ...

qq_16830879的博客 301

【Netty 源码】服务端启动流程源码分析 篇一

使用Java NIO创建服务端时,通常我们需要先创建Channel,Selector两个对象,然后将Channel绑定端口并注册到Selector上,最后对事件轮询监听 2.Netty 服务端创建流程 Netty的服务端创建流程都在 ServerBootstrap.bind方法中完成 2.1 initAndRegister() 初始化Channel并将Channel注册到Selector 通过堆栈信息可以看到此方法由main线程进行调用channelFactory.newChannel()通过DEBUG追

纸上得来终觉浅,绝知此事要躬行 1201

Netty源码分析---netty-init()方法解析

摘要 ChannelInitializer 是 Netty 中一个特殊的 ChannelHandler,主要用于延迟初始化管道(Pipeline)。它的核心特点包括: 延迟初始化 - 只在 Channel 注册到 EventLoop 后才执行真正的初始化逻辑,避免直接添加 Handler 时因未注册导致的错误。 自动移除机制 - 执行完 initChannel() 方法后会自动从 Pipeline 中移除,保证一次性使用。 简化配置 - 提供统一入口配置 Pipeline,ServerBootstrap 在

java_rutu的博客 391

netty系列之源码分析

netty系列源码分析

qq_37436172的博客 1051

Netty源码分析---initChannel方法调用流程详解

摘要: 本文深入分析了Netty中ChannelInitializer的调用流程,重点解答了handler().handlerAdded(this)方法调用的是哪个ChannelInitializer实例的问题。通过追踪代码执行路径发现:该调用实际触发的是Bootstrap.init()中创建的匿名ChannelInitializer(Initializer-A)的handlerAdded()方法。文章详细阐述了Pipeline初始化过程,解释了为什么Initializer-A会被调用,以及它如何最终触发用

java_rutu的博客 402

【Netty源码分析摘录】(四)服务端Channel注册

文章目录问题上篇回顾register(channel)源码总结 问题 在JDK的原生NIO的写法中,通过serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT)将服务端channel注册到多路复用器selector上,那么在Netty中,又是如何将NioServerSocketChannel注册到多路复用器上的呢?在注册过程中,Netty又额外做了哪些事情呢? 在上一篇文章【Netty源码分析摘录】(三)服务端Channel初始化中,

m0_45406092的博客 682

Netty源码分析篇:Netty Pipeline 初始化流程

Netty源码分析篇:Netty Pipeline 初始化流程

java_rutu的博客 657
上一篇: AbstractChannel-register方法详解
下一篇: ChannelPipeline入站事件处理机制详解
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值