netty-03-Handler-Pipeline-ByteBuf相关

本文介绍了Netty框架中的ChannelHandler与ByteBuf的基本概念和使用方式。ChannelHandler包括入站和出站处理器,分别用于处理客户端数据和服务端响应。ByteBuf提供了高效的缓冲区管理和池化机制,减少了内存溢出风险并提升了性能。

1-ChannelHandler和Pipeline

ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline

- 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果

- 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工;

类比:每个 Channel 是一个产品的加工车间,Pipeline 是车间中的流水线,ChannelHandler 就是流水线上的各道工序,而后面要讲的 ByteBuf 是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品

场景:模拟客户端向服务端发送hello, server;服务端向客户端回应:hello,client...

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class HandlerServer {
    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
        new ServerBootstrap()
                .group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 1. 通过 channel 拿到 pipeline
                        ChannelPipeline pipeline = ch.pipeline();
                        // 2. 添加处理器 head ->  h1 -> h2 ->  h3 -> h4 -> h5 -> h6 -> tail
                        pipeline.addLast("h1", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg;
                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("1-msgNew={}",msgNew);
                                super.channelRead(ctx, msg);
                            }
                        });
                        pipeline.addLast("h2", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg;
                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("2-msgNew={}",msgNew);
                                super.channelRead(ctx, msg); // 将数据传递给下个 handler,如果不调用,调用链会断开 或者调用 ctx.fireChannelRead(msg);
                            }
                        });

                        pipeline.addLast("h3", new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg;
                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("3-msgNew={}",msgNew);
//ctx.channel().writeAndFlush(msg) 会 从尾部开始触发 后续出站处理器的执行
                                ctx.channel().writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));
                            }
                        });
                        pipeline.addLast("h4", new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg;、、
                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("4-msgNew={}",msgNew);
                                super.write(ctx, msg, promise);
                            }
                        });
                        pipeline.addLast("h5", new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg; 

                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("5-msgNew={}",msgNew);
                                super.write(ctx, msg, promise);
                            }
                        });
                        pipeline.addLast("h6", new ChannelOutboundHandlerAdapter(){
                            @Override
                            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                ByteBuf msg1=(ByteBuf)msg;
                                String msgNew = msg1.toString(Charset.defaultCharset());
                                log.debug("6-msgNew={}",msgNew);
                                super.write(ctx, msg, promise);
                            }
                        });
                    }
                })
                .bind(8080);
    }
}

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;

@Slf4j
public class HandlerClient {
    public static void main(String[] args) throws InterruptedException {
        ChannelFuture channelFuture = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override // 在连接建立后被调用
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                if(msg instanceof ByteBuf){
                                    ByteBuf msg1=(ByteBuf)msg;
                                    String msgNew = msg1.toString(Charset.defaultCharset());
                                    log.debug("in-1-client,msgNew={}",msgNew);
                                }else{
                                    log.debug("in-1-client,000");
                                }
                            }
                        });
                    }
                })
                .connect(new InetSocketAddress("localhost", 8080));
        channelFuture.sync();
        channelFuture.channel().writeAndFlush("hello, server");
    }
}

服务器端日志输出:

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 1-msgNew=hello, server

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 2-msgNew=hello, server

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 3-msgNew=hello, server

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 6-msgNew=hello,client...

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 5-msgNew=hello,client...

14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 4-msgNew=hello,client...

客户端日志输出:

14:39:01 [DEBUG] [nioEventLoopGroup-2-1] c.n.d.d.HandlerClient - in-1-client,msgNew=hello,client...

可以看到,ChannelInboundHandlerAdapter 是按照 addLast 的顺序执行的,而 ChannelOutboundHandlerAdapter 是按照 addLast 的逆序执行的。ChannelPipeline 的实现是一个 ChannelHandlerContext(包装了 ChannelHandler) 组成的双向链表;

注意:

Handler3处的,如果调用ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));

客户端会收到消息,但是服务端的4,5,6不会输出在控制台;

ctx.channel().writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));

客户端会收到消息,但是服务端的4,5,6会输出在控制台;

2-ByteBuf

优点:

  • 池化 - 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能

  • 读写指针分离,不需要像 ByteBuffer 一样切换读写模式

  • 可以自动扩容

  • 支持链式调用,使用更流畅

  • 很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf

2.1-直接内存 vs 堆内存

//创建池化基于堆的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);

//创建池化基于直接内存的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);

//直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
//直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放

2.2-池化 vs 非池化

池化的最大意义在于可以重用 ByteBuf,优点有

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力

  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率

  • 高并发时,池化功能更节约内存,减少内存溢出的可能

Unpooled 是一个工具类,类如其名,提供了非池化的 ByteBuf 创建、组合、复制等操作;

2.3-堆外内存回收

由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可

  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存

  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

基本规则是,谁是最后使用者,谁负责 release,详细分析如下:

  • 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))

  • 入站 ByteBuf 处理原则

  • 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release

  • 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release

  • 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release

  • 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release

  • 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)

  • 出站 ByteBuf 处理原则

  • 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release

  • 异常处理原则

  • 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值