netty小入门

Java异步框架之Netty详解 Netty是一个基于Java的异步事件驱动网络应用框架,以高性能、高可扩展性著称。它采用异步非阻塞I/O处理,通过事件驱动模型实现高效的并发连接处理。Netty提供丰富的功能和工具集,包括支持多种协议、模块化设计、易用API、内置编解码器、健壮性、错误处理机制等。其工作原理围绕Reactor(EventLoop)、Handler、NIO和Channel等核心组件展开。Netty广泛应用于企业级应用、分布式系统、实时数据处理、游戏服务器、RPC框架及自定义协议实现等场景。 阅读详情
1、简介 
Java1.4提供了NIO使开发者可以使用Java编写高性能的服务端程序,但使用原生的NIO API就像Linux C中网络编程一样,还是需要做IO处理、协议处理等低层次工作。所以,就像C服务端程序大量使用libevent作为网络应用框架一样,Java社区也不断涌现出基于NIO的网络应用框架。在这其中,Jboss出品的Netty就是个中翘楚。Netty是个异步的事件驱动网络应用框架,具有高性能、高扩展性等特性。Netty提供了统一的底层协议接口,使得开发者从底层的网络协议(比如TCP/IP、UDP)中解脱出来。就使用来说,开发者只要参考 Netty提供的若干例子和它的指南文档,就可以放手开发基于Netty的服务端程序了。 
在Java社区,最知名的开源Java NIO框架要属Mina和Netty,而且两者渊源颇多,对两者的比较自然不少。实际上,Netty的作者原来就是Mina作者之一,所以可以想到,Netty和Mina在设计理念上会有很多共同点。我对Mina没什么研究,但其作者介绍,Netty的设计对开发者有更友好的扩展性,并且性能方面要优于Mina,而Netty完善的文档也很吸引人。所以,如果你在寻找Java NIO框架,Netty是个很不错的选择。本文的内容就是围绕一个demo介绍使用Netty的点点滴滴。 
2、服务端程序 
2.1、ChannelHandler 
服务端程序通常的处理过程是:解码请求数据、业务逻辑处理、编码响应。从框架角度来说,可以提供3个接口来控制并调度该处理过程;从更通用的角度来说,并不特化处理其中的每一步,而把每一步当做过滤器链中的一环,这也是Netty的做法。Netty对请求处理过程实现了过滤器链模式(ChannelPipeline),每个过滤器实现了ChannelHandler接口。Netty中有两种请求事件流类型也做了细分: 
1)downstream event:其对应的ChannelHandler子接口是ChannelDownstreamHandler。downstream event是说从头到尾执行ChannelPipeline中的ChannelDownstreamHandler,这一过程相当于向外发送数据的过程。 downstream event有:”write”、”bind”、”unbind”、 “connect”、 “disconnect”、”close”。 
2)upstream event:其对应的ChannelHandler子接口是ChannelUpstreamHandler。upstream event处理的事件方向和downstream event相反,这一过程相当于接收处理外来请求的过程。upstream event有:”messageReceived”、 “exceptionCaught”、”channelOpen”、”channelClosed”、 “channelBound”、”channelUnbound”、 “channelConnected”、”writeComplete”、”channelDisconnected”、”channelInterestChanged”。 
Netty中有个注释@interface ChannelPipelineCoverage,它表示被注释的ChannelHandler是否能添加到多个ChannelPipeline中,其可选的值是”all”和”one”。”all”表示ChannelHandler是无状态的,可被多个ChannelPipeline共享,而”one”表示ChannelHandler只作用于单个ChannelPipeline中。但ChannelPipelineCoverage只是个注释而已,并没有实际的检查作用。对于ChannelHandler是”all”还是”one”,还是根据逻辑需要而定。比如,像解码请求handler,因为可能解码的数据不完整,需要等待下一次读事件来了之后再继续解析,所以解码请求handler就需要是”one”的(否则多个Channel共享数据就乱了)。而像业务逻辑处理hanlder通常是”all”的。 
下面以一个简单的例子说明如何编写“解码请求数据、业务逻辑处理、编码响应”这一过程中涉及的ChannelHandler。该例子实现的协议格式很简单,请求和响应流中头4个字节表示后面跟的内容长度,根据该长度可得到内容体。 
首先看下解码器的实现: 
public class MessageDecoder extends FrameDecoder { 

    @Override 
    protected Object decode( 
            ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { 
        if (buffer.readableBytes() < 4) { 
            return null;//(1) 
        } 
        int dataLength = buffer.getInt(buffer.readerIndex()); 
        if (buffer.readableBytes() < dataLength + 4) { 
            return null;//(2) 
        } 

        buffer.skipBytes(4);//(3) 
        byte[] decoded = new byte[dataLength]; 
        buffer.readBytes(decoded); 
        String msg = new String(decoded);//(4) 
        return msg; 
    } 
} 
MessageDecoder继承自FrameDecoder,FrameDecoder是Netty codec包中的辅助类,它是个ChannelUpstreamHandler,decode方法是FrameDecoder子类需要实现的。在上面的代码中,有: 
(1)检查ChannelBuffer中的字节数,如果ChannelBuffer可读的字节数少于4,则返回null等待下次读事件。 
(2)继续检查ChannelBuffer中的字节数,如果ChannelBuffer可读的字节数少于dataLength + 4,则返回null等待下次读事件。 
(3)越过dataLength的字节。 
(4)构造解码的字符串返回。 
@ChannelPipelineCoverage("all") 
public class MessageServerHandler extends SimpleChannelUpstreamHandler { 

    private static final Logger logger = Logger.getLogger( 
            MessageServerHandler.class.getName()); 

    @Override 
    public void messageReceived( 
            ChannelHandlerContext ctx, MessageEvent e) { 
        if (!(e.getMessage() instanceof String)) { 
            return;//(1) 
        } 
        String msg = (String) e.getMessage(); 
        System.err.println("got msg:"+msg); 
        e.getChannel().write(msg);//(2) 
    } 

    @Override 
    public void exceptionCaught( 
            ChannelHandlerContext ctx, ExceptionEvent e) { 
        logger.log( 
                Level.WARNING, 
                "Unexpected exception from downstream.", 
                e.getCause()); 
        e.getChannel().close(); 
    } 
} 
MessageServerHandler是服务端业务处理handler,其继承自SimpleChannelUpstreamHandler,并主要实现messageReceived事件。关于该类,有如下注解: 
(1)该upstream事件流中,首先经过MessageDecoder,其会将decode返回的解码后的数据构造成 MessageEvent.getMessage(),所以在handler上下文关系中,MessageEvent.getMessage()并不一定都返回ChannelBuffer类型的数据。 
(2)MessageServerHandler只是简单的将得到的msg再写回给客户端。e.getChannel().write(msg);操作将触发DownstreamMessageEvent事件,也就是调用下面的MessageEncoder将编码的数据返回给客户端。 
@ChannelPipelineCoverage("all") 
public class MessageEncoder extends OneToOneEncoder { 

    @Override 
    protected Object encode( 
            ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { 
        if (!(msg instanceof String)) { 
            return msg;//(1) 
        } 

        String res = (String)msg; 
        byte[] data = res.getBytes(); 
        int dataLength = data.length; 
        ChannelBuffer buf = ChannelBuffers.dynamicBuffer();//(2) 
        buf.writeInt(dataLength); 
        buf.writeBytes(data); 
        return buf;//(3) 
    } 
} 
MessageEncoder是个ChannelDownstreamHandler。对该类的注解如下: 
(1)如果编码的msg不是合法类型,就直接返回该msg,之后OneToOneEncoder会调用 ctx.sendDownstream(evt);来调用下一个ChannelDownstreamHandler。对于该例子来说,这种情况是不应该出现的。 
(2)开发者创建ChannelBuffer的用武之地就是这儿了,通常使用dynamicBuffer即可,表示得到的ChannelBuffer可动态增加大小。 
(3)返回编码后的ChannelBuffer之后,OneToOneEncoder会调用Channels.write将数据写回客户端。 
2.2、MessageServerPipelineFactory 
创建了3个ChannelHandler,需要将他们注册到ChannelPipeline,而ChannelPipeline又是和Channel对应的(是全局单例还是每个Channel对应一个ChannelPipeline实例依赖于实现)。可以实现ChannelPipeline的工厂接口 ChannelPipelineFactory实现该目的。MessageServerPipelineFactory的代码如下: 
public class MessageServerPipelineFactory implements 
        ChannelPipelineFactory { 

    public ChannelPipeline getPipeline() throws Exception { 
        ChannelPipeline pipeline = pipeline(); 

        pipeline.addLast("decoder", new MessageDecoder()); 
        pipeline.addLast("encoder", new MessageEncoder()); 
        pipeline.addLast("handler", new MessageServerHandler()); 

        return pipeline; 
    } 
} 
2.3、MessageServer 
服务端程序就剩下启动代码了,使用Netty的ServerBootstrap三下五除二完成之。 
public class MessageServer { 

    public static void main(String[] args) throws Exception { 
        // Configure the server. 
        ServerBootstrap bootstrap = new ServerBootstrap( 
                new NioServerSocketChannelFactory( 
                        Executors.newCachedThreadPool(), 
                        Executors.newCachedThreadPool())); 

        // Set up the default event pipeline. 
        bootstrap.setPipelineFactory(new MessageServerPipelineFactory()); 

        // Bind and start to accept incoming connections. 
        bootstrap.bind(new InetSocketAddress(8080)); 

    } 
} 
稍加补充的是,该Server程序并不完整,它没有处理关闭时的资源释放,尽管暴力的来看并不一定需要做这样的善后工作。 
3、客户端程序 
客户端程序和服务端程序处理模型上是很相似的,这里还是付上代码并作简要说明。 
3.1、 ChannelHandler 
客户端是先发送数据到服务端(downstream事件流),然后是处理从服务端接收的数据(upstream事件流)。这里有个问题是,怎么把需要发送的数据送到downstream事件流里呢?这就用到了ChannelUpstreamHandler的channelConnected事件了。实现的 MessageClientHandler代码如下: 
@ChannelPipelineCoverage("all") 
public class MessageClientHandler extends SimpleChannelUpstreamHandler { 

    private static final Logger logger = Logger.getLogger( 
            MessageClientHandler.class.getName()); 


    @Override 
    public void channelConnected( 
            ChannelHandlerContext ctx, ChannelStateEvent e) { 
        String message = "hello kafka0102"; 
        e.getChannel().write(message); 
    } 

    @Override 
    public void messageReceived( 
            ChannelHandlerContext ctx, MessageEvent e) { 
        // Send back the received message to the remote peer. 
        System.err.println("messageReceived send message "+e.getMessage()); 
        try { 
            Thread.sleep(1000*3); 
        } catch (Exception ex) { 
            ex.printStackTrace(); 
        } 
        e.getChannel().write(e.getMessage()); 
    } 

    @Override 
    public void exceptionCaught( 
            ChannelHandlerContext ctx, ExceptionEvent e) { 
        // Close the connection when an exception is raised. 
        logger.log( 
                Level.WARNING, 
                "Unexpected exception from downstream.", 
                e.getCause()); 
        e.getChannel().close(); 
    } 
} 
对于编码和解码Handler,复用MessageEncoder和MessageDecoder即可。 
3.2、 MessageClientPipelineFactory 
    public class MessageClientPipelineFactory implements 
        ChannelPipelineFactory { 

    public ChannelPipeline getPipeline() throws Exception { 
        ChannelPipeline pipeline = pipeline(); 

        pipeline.addLast("decoder", new MessageDecoder()); 
        pipeline.addLast("encoder", new MessageEncoder()); 
        pipeline.addLast("handler", new MessageClientHandler()); 

        return pipeline; 
    } 
} 
3.3、MessageClient 
public class MessageClient { 

    public static void main(String[] args) throws Exception { 
        // Parse options. 
        String host = "127.0.0.1"; 
        int port = 8080; 
        // Configure the client. 
        ClientBootstrap bootstrap = new ClientBootstrap( 
                new NioClientSocketChannelFactory( 
                        Executors.newCachedThreadPool(), 
                        Executors.newCachedThreadPool())); 
        // Set up the event pipeline factory. 
        bootstrap.setPipelineFactory(new MessageClientPipelineFactory()); 
        // Start the connection attempt. 
        ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); 
        // Wait until the connection is closed or the connection attempt fails. 
        future.getChannel().getCloseFuture().awaitUninterruptibly(); 
        // Shut down thread pools to exit. 
        bootstrap.releaseExternalResources(); 
    } 
} 
在写客户端例子时,我想像的代码并不是这样的,对客户端的代码我也没做过多的研究,所以也可能没有找到更好的解决方案。在上面的例子中,bootstrap.connect方法中会触发实际的连接操作,接着触发 MessageClientHandler.channelConnected,使整个过程运转起来。但是,我想要的是一个连接池,并且如何写数据也不应该在channelConnected中,这样对于动态的数据,只能在构造函数中传递需要写的数据了。但到现在,我还不清楚如何将连接池和 ChannelPipeline有效的结合起来。或许,这样的需求可以跨过Netty来实现。 
4、总结 
关于Netty的初步使用,尚且总结到这里。关于这篇文章,写得断断续续,以至于到后来我都没兴趣把内容都整理出来。当然,这多少也是因为我是先整理 Netty原理方面的东西所致。我也只能卑微的期望,该文对Netty入门者会有些许帮助。
详解netty长连接网关请求处理模型 想要支持海量的客户端请求,首先要有一套高效的请求处理模型。本文以开源项目SONA为例,详解如何基于netty设计请求处理模型,帮助读者动手实践。本文最后附上开源项目地址。 阅读详情

相关推荐

超详细Netty入门,看这篇就够了!

本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。官方的介绍:Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

DreamSun的博客 4995

Java-Netty(入门)

i/o 种类Netty是什么?Netty是一个基于Java NIO的网络编程框架,提供了一套高效的、事件驱动的异步网络通信机制。简化了网络应用程序的开发过程,提供了可靠的、高性能的网络传输。Netty的特点是什么?异步和事件驱动:Netty使用异步的、非阻塞的I/O模型,通过事件驱动的方式处理网络操作。Netty能够高效地处理并发连接和大量的并发请求。高性能:Netty采用了一系列优化策略,如零拷贝技术、内存池和可定制的线程模型等,以提供出色的性能和吞吐量。能处理高负载和大规模并发。

weixin_45203607的博客 5894

【Netty入门教程】Netty基础篇之入门详情介绍

本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。 Netty是 一个异步事件驱动的网络应用程序框架,用于**快速开发可维护的高性能协议服务器和客户端。 Netty是一个NIO客户端-服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它大大简化和简化了TCP和UDP套接字服务器等网络编程。

No8g攻城狮的博客 1648

java必备Netty技术(1)

涉及的一些基础 涉及的设计模式:观察者模式、命令模式、责任链模式 涉及的数据结构:链表(管道的底层使用了链表) 一.netty介绍 是一个java开源项目 是一个异步的、基于事件驱动的网络应用框架,用以开发高性能、高可用的网络io程序 异步是相对于同步而言的 同步:在同步中,当浏览器向服务器发送了一个请求1,需要等待服务器向浏览器返回了响应2后,浏览器才能进行操作3。 异步:在异步中,浏览器向服务器发起一个请求,但是它并不会因为响应没有到达,而发生阻塞,而是可以进行操作3,不需要等待响应2,提供.

qq_56769991的博客 6850

Netty入门学习

打个比喻,每个 Channel 是一个产品的加工车间,Pipeline 是车间中的流水线,ChannelHandler 就是流水线上的各道工序,而后面要讲的 ByteBuf 是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品。如果事件轮询的持续时间大于等于 timeoutMillis,那么说明是正常的,否则表明阻塞时间并未达到预期,可能触发了空轮询的 Bug,则计数器+1,如果计数器超过阙值,则重建Selector并迁移所有其管理的channel。如果还是没有则创建对象。

hsnsnusjs的博客 879

请叫我鱼拆,2024扫地机器人的第一次拆机

从背面可以看出,这个机器人是两轮差速模型,两个驱动轮,一个万向轮,驱动轮位于机器人中心位置,这样原地旋转的圆心就是机器人的中心。可以看到设备已经连接,但是没有办法进shell,厂家应该有做了安全保护之类的,有知道从这个口可以登录系统办法的小伙伴可以后台留言。但这台机器人一直报警说,机器人轮子离地,小鱼 估计是防跌落模块有问题,误触发了,所以打算先拆机看看。接着小鱼试了下,机器可以开机,然后给他重新配置wifi,连上 米家。Allwinner,全志的片子,不知道哪个型号的,看产品id,应该支持adb。

生活不止眼前的苟且 3930

Netty入门(快速了解以及使用netty)

Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。Netty 在 Java 网络应用框架中的地位就好比:Spring 框架在 JavaEE 开发中的地位 以下的框架都使用了 Netty,因为它们有网络通信需求! Cassandra - nosql 数据库 Spark - 大数据分布式计算框架 Hadoop - 大数据分布式存储框架 RocketMQ - ali 开源的消息队列 ElasticSearch - 搜索引擎 gRPC

lonelykkk的博客 8627

指标异常检测和诊断

贡献度,即用定量衡量的方式来说明:一个指标YYY的异动△Y△Y△Y,具体是由其中哪些成分XiX_iXi​带来了多少异动△Yi△Y_i△Yi​?

qq_43349542的博客 3167

Netty入门

【零拷贝】的体现之一,对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针。netty 中的 Future 与 jdk 中的 Future 同名,但是是两个接口,netty 的 Future 继承自 jdk 的 Future,而 Promise 又对 netty Future 进行了扩展。connect 方法是异步的,意味着不等连接建立,方法执行就返回了。

我的博客 1690

【汽车主动降噪】【ANC算法优化】基于深度学习的汽车主动噪声控制技术研究及Matlab实现

本文探讨了基于深度学习的汽车主动降噪(ANC)算法优化方案。针对传统FXLMS算法在应对复杂、非线性车内噪声时的局限性,文章详细解析了如何利用神经网络作为非线性建模器、深度滤波器或智能步长调节器来增强ANC系统性能,并提供了完整的Matlab仿真实战流程,展示了深度学习在提升降噪效果和收敛速度方面的潜力。

seed的博客 540

保姆级教程:从Salmon输出到DESeq2,搞定RNA-seq表达矩阵标准化与下游分析准备

本文提供从Salmon输出到DESeq2标准化的RNA-seq数据分析全流程指南,详细解析表达矩阵构建、标准化方法选择及下游分析准备。重点介绍如何将Salmon定量结果转换为DESeq2兼容格式,包括TPM与原始计数的处理技巧,以及常见问题排查方法,帮助研究人员高效完成RNA-seq数据分析。

weixin_42699679的博客 304

Netty快速入门

Netty快速入门

Zhangsama1的博客 8989

西门子200 SMART与博图WinCC通讯全攻略:从PC Access到S7-200协议避坑指南

本文详细解析了西门子S7-200 SMART PLC与博途WinCC上位机通讯的两种主流方案:基于PC Access SMART的OPC通讯和利用博途内置S7-200协议驱动直连。文章深入对比了两种架构的优缺点、配置步骤,并提供了从网络层到软件层的深度排错指南与性能优化建议,帮助工程师高效、稳定地实现系统集成。

gg901的博客 810

Netty学习——Netty入门

二. Netty 入门 1. 概述 1.1 Netty 是什么? Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty 是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端。 这里的异步是指Netty采用了

maruofan666的博客 1985

PyCharm 怎么不升级直接使用Jupyter Notebook

在PyCharm 中需要升级到 PyCharm Professional 版本,才能直接使用 Jupyter Notebook。从截图来看,Jupyter Notebook 已经成功启动,并且正在运行。这将在默认网络浏览器中打开一个新的标签页,显示文件浏览器界面。可以在这里创建和编辑。文件添加到 PyCharm 项目中,并使用纯文本编辑功能进行修改。支持与外部工具集成,可以配置它以调用命令行工具或脚本来启动。,参数可以根据需要设置,工作目录设置为项目根目录。,你仍然可以在本地环境中安装和运行。

Davina_yu的博客 996

第七天 Labview进阶(7.3 Teststand简单使用)

TestStand是NI开发的自动化测试管理软件,支持多语言编程和硬件集成,广泛应用于电子产品和工业测试。安装时需注意版本选择,2022版支持离线安装。使用步骤包括:1)创建测试序列文件;2)插入测试动作并配置参数(如选择VI路径、设置变量);3)添加条件判断和延时控制;4)执行测试序列时具备流程控制功能,如失败步骤自动跳过后续测试。该软件能有效提升测试效率与准确性,通过可视化操作实现复杂测试流程管理。

weixin_38637405的博客 1730

Netty入门 -- 什么是Netty?

通俗易懂,一文让你搞懂Netty~

wanghuichen的博客 2330

点云从入门到精通技术详解100篇-点云的特征检测(续)

对每个维度的序列分别计算 DTW 距离,最后。权重将距离限制在较小的范围内,对于时间跨度大的数据集,过度限制了点对点的路径,同的,因此不能用欧氏距离来计算两个多元时间序列之间的距离,本文选用的是动态时。到的股票数据是未知分类的,因此该数据的相似性分析缺少有效的评价方式,通常需要。时间序列,简而言之,就是与时间相关的数据的集合,应用在生活工作中的方方面。时间序列的分段线性化表示的难点在于如何找出分段点,目前,对于分段点的研究。经过分段线性化之后,每个多元时间序列的维度是相同的,但是每一维的长度是不。

getusushu的博客 334

Netty入门教程——认识Netty

我们知道,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用内存空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达他的的终点,如果数据量大,就会造成不必要的资源浪费。NIO和BIO的区别主要是在第一步。多路复用IO,他的两个步骤处理是分开的,也就是说,一个连接可能他的数据接收是线程a完成的,数据处理是线程b完成的,他比BIO能处理更多请求。原因就是图二中出现的。

lonelykkk的博客 1439

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

超详细Netty入门

本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。官方的介绍:Netty isNetty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

ff0123_的博客 1044

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

上一篇: maven插件安装和配置
下一篇: serlvet获取全局及局部参数
java_min
博客等级 码龄18年 61粉丝 161原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值