NioEventLoop事件检测与触发机制总结

【Netty系列10】Netty源码探秘:核心机制解析 本文深入解析Netty框架的两大核心机制:EventLoop运行流程和ByteBuf池化分配。EventLoop采用单线程串行处理模式,通过任务队列、IO事件监听和任务执行的循环实现高效事件处理。ByteBuf池化分配通过分级内存块、引用计数和本地线程缓存实现零拷贝和内存复用,显著提升性能。文章结合源码剖析设计思想,揭示Netty如何通过事件循环调度与内存池化实现高并发与低GC开销的平衡,为构建高性能网络应用提供经典范本。 阅读详情

NioEventLoop 事件检测与触发机制总结

需要区分两种情况:

  1. 服务端 ServerSocketChannel(监听新连接)

    • 当有新连接到达时,触发 pipeline.fireChannelRead(socketChannel)
    • 注意:传递的是 SocketChannel 对象,不是 ByteBuf
  2. 客户端/服务端的 SocketChannel(数据传输)

    • 当有数据可读时,触发 pipeline.fireChannelRead(byteBuf)
    • 这里传递的才是 ByteBuf 对象

完整的理解框架

1. Register 之后发生了什么?

// Channel 注册到 EventLoop
channel.register(eventLoop);
    ↓
// EventLoop 的 run() 方法开始执行(如果还没启动)
NioEventLoop.run() {
    for (;;) {  // 无限循环
        // 1. 等待事件(阻塞或非阻塞)
        select(wakenUp.getAndSet(false));
        
        // 2. 处理 I/O 事件
        processSelectedKeys();
        
        // 3. 处理任务队列
        runAllTasks();
    }
}

关键点:

  • run() 方法是一个无限循环
  • 只要 EventLoop 不关闭,就会一直运行
  • 每次循环都会检测事件、处理事件、执行任务

2. processSelectedKeys() 检测所有就绪的事件

private void processSelectedKeys() {
    // 遍历所有就绪的 SelectionKey
    for (int i = 0; i < selectedKeys.size; ++i) {
        final SelectionKey k = selectedKeys.keys[i];
        final Object a = k.attachment();  // 获取 Channel
        
        if (a instanceof AbstractNioChannel) {
            // 处理这个 Channel 的事件
            processSelectedKey(k, (AbstractNioChannel) a);
        }
    }
}

关键点:

  • 检测的是所有注册到这个 EventLoop 的 Channel
  • 每个就绪的 Channel 都会被处理

3. 根据事件类型触发不同的操作

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    int readyOps = k.readyOps();  // 获取就绪的操作类型
    
    // 情况 1:OP_CONNECT(客户端连接完成)
    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        unsafe.finishConnect();
        // 最终触发:pipeline.fireChannelActive()
    }
    
    // 情况 2:OP_WRITE(可写)
    if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        ch.unsafe().forceFlush();
    }
    
    // 情况 3:OP_ACCEPT(服务端接受新连接)
    // 情况 4:OP_READ(有数据可读)
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
        unsafe.read();  // ← 这里会触发 fireChannelRead
    }
}

详细区分:不同 Channel 类型的处理

场景 1:服务端 ServerSocketChannel(OP_ACCEPT)

代码流程
// NioServerSocketChannel.NioServerSocketChannelUnsafe
public void read() {
    List<Object> readBuf = new ArrayList<>();
    
    try {
        do {
            // 接受新连接,返回 SocketChannel
            int localRead = doReadMessages(readBuf);
            if (localRead == 0) break;
        } while (allocHandle.continueReading());
        
        // 遍历所有新接受的连接
        for (int i = 0; i < readBuf.size(); i++) {
            // 触发 channelRead 事件
            // 注意:msg 是 NioSocketChannel,不是 ByteBuf!
            pipeline.fireChannelRead(readBuf.get(i));  // ← 传递的是 SocketChannel
        }
        
        // 触发 channelReadComplete 事件
        pipeline.fireChannelReadComplete();
    } catch (Throwable t) {
        pipeline.fireExceptionCaught(t);
    }
}
实际示例
// 服务端的 Handler
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // msg 是 NioSocketChannel(新连接)
        SocketChannel child = (SocketChannel) msg;
        
        System.out.println("接受新连接: " + child.remoteAddress());
        
        // 为新连接配置 Pipeline
        child.pipeline().addLast(new ChildHandler());
        
        // 继续传播
        ctx.fireChannelRead(msg);
    }
}

关键点:

  • 事件类型:OP_ACCEPT
  • 触发的方法:pipeline.fireChannelRead(socketChannel)
  • 传递的对象:NioSocketChannel(新连接)
  • 发生在:ServerSocketChannel(服务端监听 Channel)

场景 2:客户端/服务端 SocketChannel(OP_READ)

代码流程
// NioSocketChannel.NioByteUnsafe
public final void read() {
    ByteBuf byteBuf = null;
    
    try {
        do {
            // 分配 ByteBuf
            byteBuf = allocHandle.allocate(allocator);
            
            // 从 Socket 读取数据到 ByteBuf
            allocHandle.lastBytesRead(doReadBytes(byteBuf));
            
            if (allocHandle.lastBytesRead() <= 0) {
                byteBuf.release();
                byteBuf = null;
                break;
            }
            
            // 触发 channelRead 事件
            // 注意:msg 是 ByteBuf,包含读取的数据!
            pipeline.fireChannelRead(byteBuf);  // ← 传递的是 ByteBuf
            
            byteBuf = null;
        } while (allocHandle.continueReading());
        
        // 触发 channelReadComplete 事件
        pipeline.fireChannelReadComplete();
    } catch (Throwable t) {
        handleReadException(pipeline, byteBuf, t, close, allocHandle);
    }
}
实际示例
// 客户端/服务端的数据处理 Handler
public class DataHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // msg 是 ByteBuf(数据)
        ByteBuf buf = (ByteBuf) msg;
        
        String message = buf.toString(CharsetUtil.UTF_8);
        System.out.println("收到数据: " + message);
        
        // 继续传播
        ctx.fireChannelRead(msg);
    }
}

关键点:

  • 事件类型:OP_READ
  • 触发的方法:pipeline.fireChannelRead(byteBuf)
  • 传递的对象:ByteBuf(数据缓冲区)
  • 发生在:SocketChannel(数据传输 Channel)

完整的事件类型对照表

事件类型Channel 类型触发的 Pipeline 方法传递的对象说明
OP_ACCEPTServerSocketChannelfireChannelRead(socketChannel)NioSocketChannel新连接到达
OP_READSocketChannelfireChannelRead(byteBuf)ByteBuf数据可读
OP_CONNECTSocketChannelfireChannelActive()无连接建立(客户端)
OP_WRITESocketChannel不触发 fire 方法无可写(直接 flush)

典型的 Netty 服务端架构

1. 两个 EventLoopGroup

// Boss Group:处理新连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);

// Worker Group:处理数据读写
EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
    .channel(NioServerSocketChannel.class)
    .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) {
            ch.pipeline().addLast(new DataHandler());
        }
    });

2. 两个 Channel 类型

┌─────────────────────────────────────────────────────────┐
│ ServerSocketChannel (监听 8080 端口)                     │
│ - 注册到 bossGroup 的 EventLoop                          │
│ - 监听 OP_ACCEPT 事件                                    │
│ - 当有新连接时,触发 fireChannelRead(socketChannel)      │
└─────────────────────────────────────────────────────────┘
                        ↓ 接受新连接
┌─────────────────────────────────────────────────────────┐
│ SocketChannel (客户端连接)                               │
│ - 注册到 workerGroup 的 EventLoop                        │
│ - 监听 OP_READ 事件                                      │
│ - 当有数据时,触发 fireChannelRead(byteBuf)              │
└─────────────────────────────────────────────────────────┘

3. 两个 Pipeline

ServerSocketChannel 的 Pipeline:
head -> ServerBootstrapAcceptor -> tail
        ↑
        └─ 接收 SocketChannel,注册到 workerGroup

SocketChannel 的 Pipeline:
head -> Decoder -> Handler -> Encoder -> tail
        ↑
        └─ 接收 ByteBuf,处理业务逻辑

完整的事件流程示例

场景:客户端连接并发送 “Hello”

1. 客户端发起连接
   ↓
2. 服务端 ServerSocketChannel 检测到 OP_ACCEPT
   ↓
3. bossGroup 的 EventLoop 处理
   NioEventLoop.run()
     └─ processSelectedKeys()
        └─ processSelectedKey(k, serverSocketChannel)
           └─ unsafe.read()  // NioServerSocketChannelUnsafe
              └─ SocketChannel child = serverSocket.accept()
              └─ pipeline.fireChannelRead(child)  ← 传递 SocketChannel
   ↓
4. ServerBootstrapAcceptor 接收新连接
   └─ child.register(workerGroup.next())  // 注册到 worker
   ↓
5. SocketChannel 注册到 workerGroup
   └─ pipeline.fireChannelActive()  // 连接激活
   ↓
6. 客户端发送 "Hello"
   ↓
7. SocketChannel 检测到 OP_READ
   ↓
8. workerGroup 的 EventLoop 处理
   NioEventLoop.run()
     └─ processSelectedKeys()
        └─ processSelectedKey(k, socketChannel)
           └─ unsafe.read()  // NioByteUnsafe
              └─ ByteBuf buf = allocator.buffer()
              └─ int n = socket.read(buf)  // 读取 "Hello"
              └─ pipeline.fireChannelRead(buf)  ← 传递 ByteBuf
   ↓
9. 你的 Handler 接收数据
   public void channelRead(ChannelHandlerContext ctx, Object msg) {
       ByteBuf buf = (ByteBuf) msg;
       String message = buf.toString(CharsetUtil.UTF_8);
       System.out.println("收到: " + message);  // 输出: 收到: Hello
   }

纠正后的完整理解

✅ 正确的理解

  1. Register 之后

    • NioEventLoop.run() 方法开始执行(无限循环)
    • 循环中不断调用 select() 等待事件
  2. processSelectedKeys() 检测事件

    • 检测所有注册到这个 EventLoop 的 Channel
    • 根据就绪的操作类型(readyOps)进行处理
  3. 根据 Channel 类型和事件类型触发不同的方法

    • ServerSocketChannel + OP_ACCEPT

      • 触发:pipeline.fireChannelRead(socketChannel)
      • 传递:NioSocketChannel 对象(新连接)
    • SocketChannel + OP_READ

      • 触发:pipeline.fireChannelRead(byteBuf)
      • 传递:ByteBuf 对象(数据)
    • SocketChannel + OP_CONNECT

      • 触发:pipeline.fireChannelActive()
      • 传递:无参数
  4. 事件在 Pipeline 中传播

    • 从 head 到 tail
    • 每个 InboundHandler 依次处理

关键要点总结

要点说明
EventLoop 何时启动?第一个 Channel 注册时启动
run() 方法何时结束?EventLoop 关闭时结束
processSelectedKeys() 检测什么?所有注册到这个 EventLoop 的 Channel 的就绪事件
OP_ACCEPT 触发什么?fireChannelRead(socketChannel)
OP_READ 触发什么?fireChannelRead(byteBuf)
传递的对象是什么?取决于 Channel 类型和事件类型

记忆口诀

Register 后 EventLoop 跑,
run 方法无限循环绕。
processSelectedKeys 来检测,
就绪事件逐个找。

ServerSocket 接连接,
fireChannelRead 传 Channel。
普通 Socket 读数据,
fireChannelRead 传 ByteBuf。

事件类型要分清,
传递对象各不同。
Pipeline 传播从 head 起,
Handler 依次来处理。
【机械臂算法】机械臂动力学参数辨识仿真 本文以puma560m机械臂为例子进行动力学参数辨识的讲解,puma560m可以在robotic toolbox中找到,这里以它真实机械臂对他的动力学参数进行辨识。此外这里还有要说的是,机械臂参数辨识其实是一个系统工程,其中和系统的机械、电子、嵌入式都有着很深的联系,并不是仿真这么简单的。 阅读详情

相关推荐

RV1126B芯片实战:如何用这款视觉处理器SoC打造你的第一个AI摄像头项目

本文详细介绍了如何利用瑞芯微RV1126B视觉处理器SoC从零构建AI摄像头项目。涵盖硬件选型、开发环境搭建、ISP配置、AI模型部署及系统集成等关键步骤,帮助开发者高效实现边缘计算方案,特别适合智能安防和工业检测应用。

dropout6artist的博客 931

Netty源码分析之NioEventLoop执行流程

NioEventLoop启动触发条件: 1.服务端绑定本地端口 2.新连接接入通过chooser绑定一个NioEventLoop 服务端绑定本地端口   绑定本地端口,使用下面方法; ChannelFuture future = bootstrap.bind(host, port).sync();   最终会调用doBind0()方法: private static ...

weixin_30273175的博客 234

腾讯云TDSQL MySQL版 - 开发指南兼容性

语言结构 分布式实例支持所有 MySQL 使用的文字格式,包括: String Literals Numeric Literals Date and Time Literals Hexadecimal Literals Bit-Value Literals Boolean Literals NULL Values String Literals String Literals 是一个 bytes 或者 characters 的序列,两端被单引号'或者双引号"包围,TDSQL MySQL版

weixin_43872058的博客 911

Netty源码分析之处理新连接

Netty服务端处理新连接的流程: 1.检测新连接 2.基于NioServerSocketChannel创建客户端的NioSocketChannel 3.分配客户端channel的线程,注册线程所对应的selector 4.向selector注册读事件 新连接检测   服务端在创建完服务端的NioServerSocketChannel之后,绑定完端口号之后,会注册accept事件...

weixin_30763397的博客 134

AD创建属于自己的元件库(详细)

我今天开始学习AD2022,但是我想自己创建属于自己的元件库,但是奈何找了好多教程就是随便几句话就把我打发走了,弄了一天都没搞定(也许是我比较菜),所以现在乱懂了,自己写一个属于我们自己小白的教程,希望有用吧哈哈我比较偷懒,直接使用嘉立创上画好的元件导入,和我们自己创建自己画的步骤其实都一样。

qq_33591173的博客 2万+

车辆横向控制MPC实战:从‘老王模型’到CasADi求解的保姆级避坑指南

本文详细解析了车辆横向控制MPC的实战应用,从经典动力学模型选择到CasADi求解器的优化配置,提供了一套完整的避坑指南。重点介绍了车辆动力学模型的验证要点、CasADi环境的高效配置技巧以及MPC问题建模的核心要素,帮助开发者快速实现稳健的横向控制方案。

weixin_42524499的博客 346

Netty4源码分析- read

本文为原创,转载请注明出处 Netty4源码分析- read    当selector检测到OP_READ事件时,触发read操作: //NioEventLoop if ((readyOps &amp; (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { ...

海浪儿 265

从底层结构开始学习FPGA(8)----Block RAM(BRAM,块RAM)

BRAM作为FPGA底层的存储资源,作用很大,比如可以RAM/ROM/FIFO。

孤独的单刀 2万+

Netty中NioEventLoop源码解析

类是 Netty 4.1 中实现 Reactor 模型的单线程事件循环类,继承自 ,负责处理 I/O 事件、任务调度和事件分发。以下是类中定义的所有字段,逐一解析其作用和实现细节。定义: 初始化:硬编码为 256。作用:使用场景: 防止频繁调用 ,优化性能。 设计理念:定义: 类型:,静态常量。修饰符:,不可修改。初始化:通过 读取系统属性,默认为 。作用:使用场景: 若优化启用(),使用 (基于数组)替换默认 ,提升 处理效率。 设计理念:定义: 初始化:硬编码为 3。 作用

eclipseC的博客 1470

STM32F103VCT6+W5500 TCP 服务端通信

STM32F103VCT6 W5500的TCP连接通讯的程序

坚韧与专注 7820

STM32F103/F407/H743不同GPIO速度配置(HAL库)对应的最高速度

详细介绍了STM32F103/F407/H743不同GPIO速度配置(HAL库)对应的最高速度。

kevin1499的博客 5594

Netty NioEventLoop 详解及详细源码展示

本文深入剖析 Netty 核心组件 NioEventLoop 的设计与实现。该组件基于 Java NIO Selector 构建,采用单线程循环模型处理 I/O 事件和任务队列,支持高效的无锁化 MPSC 队列、定时任务调度和空轮询检测等优化技术。通过源码分析可见,NioEventLoop 通过线程复用、批量任务处理等机制实现高性能网络通信,相比原生 Java NIO 具有更优的任务调度能力和稳定性。关键实现包括事件注册处理、任务队列管理和性能优化策略,

csdn_tom_168的博客 904

YOLOv12改进策略【小目标改进】| 添加专用于小目标的检测层 附YOLOv1~YOLOv12的检测头变化详解

在目标检测领域,小目标检测一直是一个具有挑战性的问题。算法以其高效快速的特点受到广泛关注,然而在面对小目标时,仍存在一些局限性。本文将介绍如何在中添加小目标检测层,以提高对小目标的检测能力。原始模型结构如下:详细内容参考:YOLOv12原始模型结构介绍 二、有效特征层对应的检测头类别 2.1 P3/8 - small检测头 原始模型中的对应的检测头主要用于检测相对较小的目标。其特征图大小相对较大,空间分辨率较高。 适合检测尺寸大概在到像素左右的目标。 2.2 P4/16 - medium检测头 这个检

Limiiiing的博客 1742

【Netty】学习NioEventLoop

欢迎关注公众号:【爱编程】 如果有需要后台回复2019赠送1T的学习资料哦!! 简介 Netty框架的主要线程就是I/O线程,线程模型的设计决定了系统的吞吐量、并发性和安全性等架构质量属性。所以了解一下NioEventLoop。 Reactor线程模型 基本上所有的网络处理程序都有以下基本的处理过程: Read request Decode request Process service E...

weixin_33941350的博客 271

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

温度PID控制PLC程序

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

AI理解NioEventLoopGroup与NioEventLoop

**Channel 绑定:** 一个 `Channel` 在其生命周期内通常只注册到一个 `EventLoop` 上,后续该 `Channel` 的所有 I/O 事件处理和任务执行都由这个绑定的 `EventLoop` 负责,保证了操作的**线程安全性**(避免复杂的并发控制)。**每个 `NioEventLoop` 绑定一个唯一的 Java `Thread`**。* **类比:** 想象成一个**车间 (`Group`)**,里面有多个**工人 (`EventLoop`)**。

device_tang_的博客 768

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

阅读 Netty 源码关于 NioEventLoop 和 Channel 初始化部分的思考

启动:Netty 很懒,你把任务扔进去它才启动线程。分配:一个线程(EventLoop)像是包工头,手下管着几千个工人(Channel)。每个工人只听这一个包工头的话。异步:包工头接活时,先给你开个收据(Future),然后让你一边玩去。活干完了,他再按收据上的电话通知你。队列:如果你想指挥工人干活,但你不是包工头,你不能直接吼工人。你得写个纸条(Task)塞进包工头的信箱(Queue)。包工头忙完手头的活(IO),会去信箱拿纸条执行。

Kiyra的博客 865

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

NioEventLoop

三个问题 1.默认情况下,Netty服务端起多少线程?何时启动? 答: 2*CPU核数个线程。在调用execute方法时,会判断当前是否在本线程,如果是在本线程,说明线程已经启动;如果是在web线程,调用execute方法,首先会调用startThread方法,这个方法首先会判断当前线程是否启动,如果没有启动就启动该线程。 2.Netty是如何解决jdk空轮询bug的? Netty...

weixin_30457881的博客 122

Netty 源码解密:如何用 512 次空转检测破解 JDK NIO 的 epoll 死循环 Bug?

/ 正常情况:无事件时应阻塞// 空轮询 Bug 触发时:selector.select() 立即返回 0// 这里会无限循环打印,CPU 飙到 100%System.out.println("空轮询!");continue;// 处理就绪事件...问题现象本应在无 I/O 事件时阻塞,等待事件到来Bug 触发时,select() 会立即返回 0,但没有任何就绪事件线程陷入无限循环,CPU 占用率瞬间飙升至 100%参数值效果过小(如 16)

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 2203

Netty深度剖析(2)— 事件调度

深度解析netty高性能的核心-事件调度的相关设计原理

weixin_46628206的博客 953

【Netty】二.Channel与EventLoop深度解析

本文深入剖析了Netty中Channel与EventLoop的核心机制。Channel作为网络连接的抽象,具有完整的生命周期(创建、注册、激活、非激活、注销),每个状态变化都会触发相应事件。EventLoop是单线程事件循环执行器,负责处理I/O事件、执行任务和定时任务。关键点在于:1)Channel与EventLoop绑定后关系终身不变;2)所有Channel操作都在同一EventLoop线程执行,确保线程安全;3)通过inEventLoop()方法判断当前线程环境,正确处理任务调度。文章还对比了NIO与

hrh的博客 699

如何用一个线程处理上万个Channel?Netty底层Selector机制全曝光

掌握Java NIO Selector多路复用机制,轻松实现单线程高效管理上万个Channel。适用于高并发网络编程,揭示Netty底层事件驱动原理与选择器轮询优化策略,提升系统性能,值得收藏。

ProceShoal的博客 354

netty之新连接接入源码分析

前言 分析之前,提出已下问题: 1.1 Netty是在哪里检测有新连接接入的? 1.2 新连接是怎样注册到NioEventLoop线程的? 本篇文章按照下图顺序进行分析 检测新连接 通过上述截图以及之前文章可知,netty通过selector#select获取IO就绪的SelectionKey 1.1 获取SelectionKey的事件:int readyOps = k.readyOps(); 1.2 如果readyOps为SelectionKey.OP_ACCEPT,则检测到新连接 获取Sel

snail-jie的博客 231

Netty新连接的接入

新连接的建立 可以分为三个步骤 1.检测到有新的连接 2.将新的连接注册到worker线程组 3.注册新连接的读事件 检测到有新连接的接入 我们已经知道,当服务端启动后,服务端channel已经注册到boss reactor线程中,reactor不断检测有新的事件,直到检测出有accept事件发生 NioEventLoop.java private void processS...

weixin_38068301的博客 269

Netty官方为什么说自己是基于事件驱动的异步编程

Netty的异步编程前言一、先前阅读Netty源码的心得1、NioEventLoop的轮询调度二进制运算实现2、Netty的线程模型3、Netty的异步编程二、Netty的事件驱动1、什么是事件2、Netty中的动作与事件 前言 之前有阅读过Netty源码,当再次在百度搜索Netty时候,跳出Netty官方地址官方网站。当读到 Netty is an asynchronous event-driven network application framework for rapid development

Dream2World,SayHi 1114

Netty是如何处理新连接接入事件的?

更多技术分享可关注我 前言石头 www.10tou.com 前面的分析从Netty服务端启动过程入手,一路走到了Netty的心脏——NioEventLoop,又总结了Netty的异步API和设计原理,现在回到Netty服务端本身,看看服务端对客户端新连接接入的处理是怎么样的过程。 原文:​Netty是如何处理新连接接入事件的? Java NIO处理新连接的编码模板 首先,对于新连接接...

keocce的博客 510
上一篇: NioEventLoop-run方法完整学习笔记
下一篇: Netty源码分析---SelectionKey与位运算详解
不早睡不改名@
博客等级 码龄5年 536粉丝 306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值