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

Netty源码分析:ChannelPipeline Netty源码分析:ChannelPipeline在博文Netty源码分析:服务端启动全过程我们在知道NioServerSocketChannel这个类的构造函数的调用链如下: public NioServerSocketChannel() { this(newSocket(DEFAULT_SELECTOR_PROVIDER));//newSocket的功能为:利用Select 阅读详情

handlerAdded() 方法调用详解

1. ChannelHandlerAdapter 的实现

public class ChannelHandlerAdapter implements ChannelHandler {

    // 这个方法有默认实现,是空方法!
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 默认什么都不做
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 默认什么都不做
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.fireExceptionCaught(cause);
    }
}

关键点:

  • ChannelHandlerAdapter 不是抽象类,是普通类!
  • 它提供了 handlerAdded() 的默认空实现
  • 子类可以选择重写,也可以不重写

2. 继承关系图

ChannelHandler (接口)
    ↑
ChannelHandlerAdapter (类,提供默认空实现)
    ↑
ChannelInboundHandlerAdapter (类)
    ↑
ByteToMessageDecoder (类)
    ↑
LineBasedFrameDecoder (类)
ChannelHandler (接口)
    ↑
ChannelHandlerAdapter (类,提供默认空实现)
    ↑
ChannelInboundHandlerAdapter (类)
    ↑
MessageToMessageDecoder (类)
    ↑
StringDecoder (类)

3. 各个类的 handlerAdded() 实现情况

(1) LineBasedFrameDecoder
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
    // 没有重写 handlerAdded() 方法
    // 使用父类的默认实现
}
(2) ByteToMessageDecoder
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {
    
    // 重写了 handlerAdded() 方法!
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 如果 channel 已经注册,初始化累加器
            if (cumulation == null) {
                handlerAdded0(ctx);
            }
        }
    }
    
    protected void handlerAdded0(ChannelHandlerContext ctx) throws Exception { }
}

所以 LineBasedFrameDecoder 调用的是 ByteToMessageDecoder 的 handlerAdded()!

(3) StringDecoder
public class StringDecoder extends MessageToMessageDecoder<ByteBuf> {
    // 没有重写 handlerAdded() 方法
    // 使用父类的默认实现
}
(4) MessageToMessageDecoder
public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter {
    // 没有重写 handlerAdded() 方法
    // 使用父类 ChannelInboundHandlerAdapter 的默认实现
}
(5) ChannelInboundHandlerAdapter
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
    // 没有重写 handlerAdded() 方法
    // 使用父类 ChannelHandlerAdapter 的默认实现
}

所以 StringDecoder 调用的是 ChannelHandlerAdapter 的空实现!

(6) ChannelInitializer(特殊情况)
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
    
    // 重写了 handlerAdded() 方法,这是关键!
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isRegistered()) {
            // 调用 initChannel 方法
            if (initChannel(ctx)) {
                removeState(ctx);
            }
        }
    }
    
    protected abstract void initChannel(C ch) throws Exception;
}

所以 MyChannelInitializer 调用的是 ChannelInitializer 的 handlerAdded()!

完整的调用链分析

场景:执行 invokeHandlerAddedIfNeeded()

// 假设此时 pipeline 中只有 MyChannelInitializer
Pipeline: head -> MyChannelInitializer -> tail

第一步:触发 MyChannelInitializer.handlerAdded()

invokeHandlerAddedIfNeeded()
    ↓
callHandlerAdded0(MyChannelInitializer的context)
    ↓
MyChannelInitializer.handlerAdded()  // 实际调用的是父类 ChannelInitializer 的实现
    ↓
// ChannelInitializer.handlerAdded() 的实现:
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        if (initChannel(ctx)) {  // 调用你重写的 initChannel
            removeState(ctx);
        }
    }
}
    ↓
initChannel(channel)  // 你重写的方法

第二步:在 initChannel() 中添加 handlers

protected void initChannel(SocketChannel channel) {
    // 1. 添加 LineBasedFrameDecoder
    channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
}

addLast() 内部会做什么?

// DefaultChannelPipeline.addLast()
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);

        // 关键:如果 pipeline 已经注册,立即调用 handlerAdded
        if (registered) {
            callHandlerAdded0(newCtx);  // ⭐ 这里会调用 handler 的 handlerAdded()
            return this;
        }
        
        // 如果还没注册,创建待处理任务
        callHandlerCallbackLater(newCtx, true);
    }
    return this;
}

因为此时 pipeline 已经 registered(在 register0 中设置的),所以会立即调用:

callHandlerAdded0(newCtx)
    ↓
newCtx.callHandlerAdded()
    ↓
handler().handlerAdded(this)  // handler() 返回 LineBasedFrameDecoder 实例
    ↓
// LineBasedFrameDecoder 没有重写,调用父类 ByteToMessageDecoder 的实现
ByteToMessageDecoder.handlerAdded(ctx)
    ↓
// ByteToMessageDecoder 的实现:
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        if (cumulation == null) {
            handlerAdded0(ctx);  // 初始化累加器
        }
    }
}

第三步:继续添加其他 handlers

// 2. 添加 StringDecoder
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
    ↓
callHandlerAdded0(StringDecoder的context)
    ↓
StringDecoder.handlerAdded(ctx)  // 没有重写,调用 ChannelHandlerAdapter 的空实现
    ↓
// ChannelHandlerAdapter 的实现:
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    // 空方法,什么都不做
}

// 3. 添加 StringEncoder
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
    ↓
callHandlerAdded0(StringEncoder的context)
    ↓
StringEncoder.handlerAdded(ctx)  // 没有重写,调用 ChannelHandlerAdapter 的空实现

// 4. 添加 MyServerHandler
channel.pipeline().addLast(new MyServerHandler());
    ↓
callHandlerAdded0(MyServerHandler的context)
    ↓
MyServerHandler.handlerAdded(ctx)  // 如果你重写了就调用你的,否则调用父类的空实现

总结表格

Handler 类handlerAdded() 实现位置是否有实际逻辑
MyChannelInitializerChannelInitializer(父类)✅ 有,调用 initChannel()
LineBasedFrameDecoderByteToMessageDecoder(祖父类)✅ 有,初始化累加器
StringDecoderChannelHandlerAdapter(曾祖父类)❌ 空实现
StringEncoderChannelHandlerAdapter(曾祖父类)❌ 空实现
MyServerHandler取决于你是否重写取决于你的实现

关键理解

1. ChannelHandlerAdapter 不是抽象类

// 这是一个普通类,提供了默认实现
public class ChannelHandlerAdapter implements ChannelHandler {
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 默认空实现,子类可以选择重写
    }
}

2. 不是所有 handler 都需要重写 handlerAdded()

大多数 handler 不需要在添加时做特殊处理,所以使用默认空实现就够了。

需要重写的情况:

  • ChannelInitializer:需要添加其他 handlers
  • ByteToMessageDecoder:需要初始化累加器
  • 你的自定义 handler:如果需要在添加时做初始化

3. 调用链的完整流程

invokeHandlerAddedIfNeeded()
    ↓
【第一层】触发 MyChannelInitializer.handlerAdded()
    ↓
    调用 initChannel(channel)
    ↓
    【第二层】在 initChannel() 中添加 handlers
        ↓
        pipeline.addLast(new LineBasedFrameDecoder(...))
            ↓
            触发 LineBasedFrameDecoder.handlerAdded()
            (实际调用 ByteToMessageDecoder 的实现)
        ↓
        pipeline.addLast(new StringDecoder(...))
            ↓
            触发 StringDecoder.handlerAdded()
            (实际调用 ChannelHandlerAdapter 的空实现)
        ↓
        pipeline.addLast(new StringEncoder(...))
            ↓
            触发 StringEncoder.handlerAdded()
            (实际调用 ChannelHandlerAdapter 的空实现)
        ↓
        pipeline.addLast(new MyServerHandler())
            ↓
            触发 MyServerHandler.handlerAdded()
            (取决于你是否重写)

验证代码

你可以在你的 MyServerHandler 中重写 handlerAdded() 来验证:

public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("MyServerHandler.handlerAdded() 被调用了!");
        super.handlerAdded(ctx);  // 调用父类的空实现
    }
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 你的业务逻辑
    }
}

运行后你会看到这个输出,证明 handlerAdded() 确实被调用了!

最终答案

调用的是哪里的 handlerAdded()?

  1. MyChannelInitializer:调用父类 ChannelInitializer 的实现(有实际逻辑)
  2. LineBasedFrameDecoder:调用祖父类 ByteToMessageDecoder 的实现(有实际逻辑)
  3. StringDecoder/StringEncoder:调用曾祖父类 ChannelHandlerAdapter 的实现(空方法)
  4. MyServerHandler:如果你重写了就调用你的,否则调用父类 ChannelHandlerAdapter 的空实现

核心:

  • ChannelHandlerAdapter 提供了默认空实现
  • 子类可以选择重写,也可以不重写
  • 不重写就使用默认空实现,不会报错
  • 只有需要在添加时做特殊处理的 handler 才会重写这个方法
【智能机器人开发全流程:硬件选型、软件架构与ROS实战,打造高效机器人系统】 通过模块化设计与ROS框架的灵活扩展,可快速搭建适应不同场景的机器人系统。实际开发中需重点关注。 阅读详情

相关推荐

精益六西格玛Python数字化应用开发-Day18 应用场景:家电企业生产效率优化

本文将重点介绍专门针对精益六西格玛黑带使用场景的 Python 库,这些库功能侧重于数据分析和流程优化,特别适用于 DMAIC 方法论和看板管理。考虑到读者具备中高级Python 使用经验,我们将深入探讨各库的高级功能和实际应用应用场景。 某家电企业生产线自动化程度高,数据量巨大,希望通过精益管理提升生产效率。企业借助 Python 脚本,打通各类数据源(MES、ERP、设备日志),自动归集生产数据,分析各工序的生产节拍、设备停机、效率损失,快速定位瓶颈环节

m0_56592100的博客 906

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

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

java_rutu的博客 402

Tesseract-Ocr踩坑下载以及进行检验

1 OCR,即Optical Character Recognition:光学字符识别,是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程。下载完成后进行解压找到这两个,然后复制到之前下载Tesseract-Ocr的tessdata文件下。可能会出现报错说chi下载的问题咱们不用管继续ok让它先下载完毕,后续会有下载chi的步骤。四、检验tesseract使用(将以下图片进行下载我这里将以下图片命名为3.png)我这里选的是2024年的版本如果下载不了;4 在安装的时候(找到下载的exe文件,双击)

m0_51428325的博客 1919

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

摘要:本文详细解析了Netty中AbstractChannel.register0()方法的实现机制。该方法负责将Channel注册到EventLoop的核心流程,包括Selector注册、状态更新和事件触发。关键点包括:1) 确保在EventLoop线程执行;2) 通过doRegister()完成底层Selector注册;3) 触发handlerAdded和channelRegistered事件;4) 根据首次注册状态决定是否触发channelActive事件。该方法还包含完善的异常处理机制,是Netty

java_rutu的博客 531

手把手教你用UNet实现轨道缺陷检测(附RSDDs数据集下载与完整代码)

本文详细介绍了如何使用UNet模型实现轨道缺陷检测,包括RSDDs数据集的下载与处理、UNet架构的改进与优化、训练策略设计以及模型部署的全流程。通过实战案例,展示了如何利用深度学习技术提升工业质检效率,特别适合从事工业缺陷检测和图像处理的开发者参考。

dapp9builder的博客 749

从零构建eSIM RSP服务:云原生架构与GSMA合规实践

本文详细阐述了从零开始构建一个符合GSMA SAS-SM安全标准的云原生eSIM RSP服务的完整路径。内容涵盖云原生基础设施选型、容器化部署、GSMA标准接口集成,并重点分享了通过SAS-SM安全审计的实战经验与避坑指南,为物联网及智能设备企业自建可控的远程SIM配置服务提供了系统性的解决方案。

weixin_29322553的博客 461

第五节 netty源码分析-ChannelPipeline的addLast方法解析

一、概述 在第四节中,分析了ServerBootstrap的bind()方法,主要分析了initAndRegister方法,这个方法调用的层次很深,还有几个主要的地方没有讲到,其中在initAndRegister()调用的ServerBootstrap.init()方法里面会用pipeline.addlast()方法。前面分析过new NioServerSocketChannel是默认创建DefaultChannelPipeline。所以就看io.netty.channel.DefaultChannelPi

Resee_Z的博客 3820

Linux显卡管理避坑指南:为什么Xorg总是占用你的NVIDIA GPU?

本文深入解析了在Linux系统上,尤其是双显卡笔记本中,Xorg显示服务器进程占用NVIDIA GPU的根本原因。文章从Xorg的驱动加载机制和NVIDIA驱动的独占性入手,详细对比了禁用配置文件、手动配置xorg.conf、使用官方PRIME方案以及切换到Wayland等主流解决方案的优缺点与适用场景,旨在帮助用户有效释放GPU资源,确保深度学习、游戏等计算任务能独占显卡性能。

xgboost6farmer的博客 269

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

Netty 的 invokeHandlerAddedIfNeeded() 方法解析 核心功能 invokeHandlerAddedIfNeeded() 是 Netty 中用于触发 pipeline 中所有 handler 的 handlerAdded() 回调的关键方法,主要特点: 延迟执行机制:通过 PendingHandlerCallback 链表管理待执行的回调任务 线程安全保证:确保在正确的 EventLoop 线程中执行回调 初始化关键点:在 Channel 注册完成后统一执行所有 pending

java_rutu的博客 375

车辆网络安全ISO/SAE 21434解读(一)概述

ISO/SAE 21434 《道路车辆 网络安全工程》是SAE和ISO共同制定的一项针对道路车辆的网络安全标准,它是一个面向汽车行业全供应链(OEM及各级供应商)的车辆网络安全管理指导文件

xiaoxiabang的博客 8427

LLC谐振变换器软启动,不只是“慢启动”,还是“智能启动”

LLC谐振变换器的软启动,不只是“慢慢来”,而是要在控制时序、限流、响应速度之间精妙权衡。而数字控制,特别是用查表法或混合信号法,为实现真正的“最佳轨迹”提供了可能。✨小Tips:如果你的系统成本敏感,选择查表法准没错;如果你的设计目标是超高速响应,那就上混合信号的高配方案!

weixin_43199439的博客 691

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

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

java_rutu的博客 391

阿里云百炼全解析:一站式大模型开发平台的架构与行业实践

摘要: 阿里云百炼平台通过一站式架构革新大模型开发范式,将传统碎片化工具链整合为统一工作流,实现开发周期从月级到小时级的跃迁。其核心架构包含接入层、引擎层与资源层,关键技术突破包括MCP服务协议、多模态引擎及高性能训练优化。平台提供全生命周期开发支持,从数据准备到模型部署,并通过VPC隔离、数据加密构建企业级安全体系。已在医疗、电力等行业实现显著增效,如阿斯利康药物分析系统准确率提升至95%。未来将向多Agent协同、边缘计算等方向演进,推动AI技术深度融入产业核心。

fudaihb的博客 1991

Netty源码分析---Netty-Pipeline初始化流程详解

本文深入解析了Netty Pipeline的初始化流程,主要分为两个阶段:准备阶段和执行阶段。在准备阶段,Netty通过initAndRegister()方法创建Channel实例并将ChannelInitializer添加到Pipeline中,但此时不会立即执行初始化逻辑。执行阶段发生在Channel注册到EventLoop后,触发handlerAdded()方法完成真正的初始化。文章详细跟踪了从客户端启动代码到addLast()方法的核心逻辑,揭示了Netty采用"延迟初始化"策略的

java_rutu的博客 421

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

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

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

Netty核心源码分析(四)心跳检测源码分析

IdleStateHandler 可以实现心跳功能,当服务器和客户端没有任何读写交互时,并超过了给定的时间,则会触发用户 handler 的 userEventIriggered 方法。用户可以在这个方法中尝试向对方发送信息,如果发送失败,则关闭连接。IdleStateHandler 的实现基于 EventLoop 的定时任务,每次读写都会记录一个值,在定时任务运行的时候通过计算当前时间和设置时间和上次事件发生时间的结果,来判断是否空闲。内部有 3 个定时任务,分别对应读事件,写事件,读写事件。

秃了也弱了 938

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

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

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

java_rutu的博客 657

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

【Netty源码分析】01 Netty Server 启动流程 上

Netty服务端一般如下面代码模式,简化了NIO编程的复杂性同时,并且借助于Pipeline模型,可以很简单的就构建出高性能、可扩展的应用程序。publicclassDemoServ...

god_86的专栏 294

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

Netty的ChannelInitializer工作原理分为两个阶段:首先将初始化器添加到pipeline中,然后在register时触发其handlerAdded()方法。该过程会调用用户重写的initChannel()方法添加实际处理器,最后ChannelInitializer会从pipeline中自动移除。整个过程通过父类ChannelInitializer的handlerAdded()方法触发,形成完整的处理器链初始化机制。

java_rutu的博客 218

Netty——ChannelPipline源码分析

摘要 netty在服务端端口绑定和新连接建立的过程中会建立相应的channel,而与channel的动作密切相关的是pipeline这个概念,pipeline像是可以看作是一条流水线,原始的原料(字节流)进来,经过加工,最后输出。我们已经知道了创建NioSocketChannel的时候会将netty的核心组件创建出来。 pipeline基本结构 pipeline是其中的一员,在下面这段代码中被创建。由双链表组成。NioSocketChannel中保存了pipeline的引用。 protecte

庄小焱 822

Netty源码分析之ChannelHandler

一、ChannelHandler public interface ChannelHandler { void handlerAdded(ChannelHandlerContext var1) throws Exception; void handlerRemoved(ChannelHandlerContext var1) throws Exception; /** @deprecated */ @Deprecated void exceptionCaught(C

solayang的博客 613

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

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

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

Netty源码分析之pipeline中事件传播(Netty4)

文章目录添加ChannelHandlerContext删除channelHandlerinbound事件的传播 添加ChannelHandlerContext // 实例代码 ,首先通过addLast()方法进行添加 ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)

诺图的博客 299

Netty源码之Pipeline

在本文中我们讲解

m0_37343985的博客 240

四、Netty核心源码剖析

目录 为什么要看源码: 一、Netty线程模型图 1.1 Netty线程模型源码剖析图 二、Netty高并发高性能架构设计精髓 2.1 无锁串行化设计思想 2.2 直接内存 2.3 直接内存分配源码分析: 2.3.1 使用直接内存的优缺点: 2.4 Netty零拷贝 2.5 ByteBuf内存池设计 2.6 灵活的TCP参数配置能力 2.7 ByteBuf扩容机制 2.8补充:handler的生命周期回调接口调用顺序 为什么要看源码: 提升技术功底:学习源码里的优秀设计..

Xx__WangQi的博客 728

netty学习4:ChannelPipeline源码分析

netty学习4:ChannelPipeline源码分析

weixin_45453628的博客 266

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

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

m0_45406092的博客 553
上一篇: Netty源码分析--认真系列(一)
下一篇: Netty源码分析---execute-method解析
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值