《Netty深入剖析》之五:大动脉Pipeline

5 pipeline

5.1 pipeline的初始化

5.1.1 pipeline在创建Channel的时候被创建

AbstractChannel的构造方法中会进行创建DefaultChannelPipeline

即服务端和客户端channel被创建的时候创建

5.1.2 pipeline节点数据结构:ChannelHandlerContext接口

扩展自AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker

AttributeMap:可以存储自定义属性

ChannelInboundInvoker:可以传播读事件(注册/Active)

ChannelOutboundInvoker:可以传播写事件

  • pipeline的串行结构如何实现?

依靠AbstractChannelHandlerContext的next、prev指针

5.1.3 Pipeline中的两大哨兵tail、head

在5.1.1中被初始化:
在这里插入图片描述
TailContext:会传播读事件,主要做一些收尾的事情,如异常处理

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler

HeadContext: 这里的Inbound会赋值为false,所以会传播写事件;
会进行事件的传播,每次进行读写事件传播都从HeadContext开始;
进行读写操作时,都会委托给Unsafe

final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler

channelActive()方法会调用readIfIsAutoRead(),即连接刚建立成功之后,默认情况下自动注册读事件,底层对应的NioEventLoop的selector可以轮询到一个读事件

  • 为什么TailContext和HeadContext会相反?

todo(位置不同,起到的作用不同)

5.2 添加删除ChannelHandler

5.2.1 添加

使用者:自定义handler一般继承ChannelOutboundHandlerAdapter或ChannelInboundHandlerAdapter

//ChannelInitializer最后会被移除
//作用:调用initChannel方法来添加自定义handler
.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new XXXHandler());
    }
});

流程:addlast()

1.判断是否重复添加(checkMultiplicity():直接通过(added标志位(而不是遍历)&是否可共享(@Sharable注解))来判断,减少时间复杂度(性能点))

2.创建节点(通过组合,把handler包装成ChannelHandlerContext接口默认实现类DefaultChannelHandlerContext)并添加至链表(tail的前一个)

3.回调添加完成事件

5.2.2 删除

  • 场景:权限校验
public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf password) throws Exception {
        if (paas(password)) {
            ctx.pipeline().remove(this);
        } else {
            ctx.close();
        }
    }

    private boolean paas(ByteBuf password) {
        return false;
    }
}
  • 流程:remove为入口

1.找到节点

2.通过标准的链表删除来删除节点(默认情况下都会有头尾节点head、tail,所以不需要判断“是否为首或尾节点”)(head、tail是不可被删除的,由assert保护)

3.回调删除handler事件(callHandlerRemoved0(ctx))

5.3 事件和异常的传播

ChannelHandler接口继承关系:
在这里插入图片描述

5.3.1 Inbound事件的传播

5.3.1.1 何为inbound事件以及ChannelInbounHandler

inbound事件主要包括Register、Active、read等事件

ChannelInbounHandler:在ChannelHandler基础上添加了一些Register、Active、read等功能,被添加到pipeline后,通过instanceof识别出为inboundHandler,从而可以处理inbound事件

5.3.1.2 ChannelRead()事件的传播
//服务端msg其实是一个连接,客户端msg是bytebuf数据
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception 

按addLast的顺序依次传播

传播案例:

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerB: " + msg);
        //此方法会调用下一个inbound
        //没有这一行的话,则不会传播
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //此方法会传播到channelRead,传入的参数就是msg
        ctx.channel().pipeline().fireChannelRead("hello world");
    }
}

pipeline的fireChannelRead会从head开始传播

ChannelInboundHandler的fireChannelRead则会传播给下一个inbound

bytebuf会在tailContext进行释放

5.3.1.3 SimpleInboundHandler处理器
  • 作用

能够自动释放bytebuf;
因为可能某些handler没有向下传播,无法到达tailContext,无法释放,所以需要这个处理器

  • 原理

传入bytebuf泛型,定义了抽象方法channelRead0,子类重写,在此方法上编写自己的清除逻辑,内部会调用此方法

5.3.2 Ounbound事件的传播

5.3.2.1 何为outbound事件以及ChannelOutbounHandler

outbound事件:写事件,更多是使用者使用的,主动,而inbound为被动

5.3.2.2 write()事件的传播
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception

按addLast的顺序逆序依次传播(pipeline为双向链表,从tail开始往前即可)

谁真正负责写操作?head中的unsafe

传播案例:

public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerB: " + msg);
        ctx.write(msg, promise);//没有这一行的话,则不会传播
    }


    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) {
        ctx.executor().schedule(() -> {
            //都是向客户端写数据
            //区别:
            //第一行会回调上面的write方法,传入的参数会给msg参数;是从tail开始的
            //第二行则会传播给上一个outhandler(通过findContextOutbound()来找到next);是从当前节点开始的,可能会错过一些outboundhandler
            ctx.channel().write("hello world");
            ctx.write("hello world");
        }, 3, TimeUnit.SECONDS);
    }
}

5.3.3 异常的传播

案例代码:

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
	//BusinessException为自定义异常
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        throw new BusinessException("from InBoundHandlerB");
    }
	//AC只有此方法
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InBoundHandlerB.exceptionCaught()");

        ctx.fireExceptionCaught(cause);
    }
}
//ABC相同
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("OutBoundHandlerC.exceptionCaught()");
        ctx.fireExceptionCaught(cause);
    }
}
public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // ..
        if (cause instanceof BusinessException) {
            System.out.println("BusinessException");
        }
    }
}
.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new InBoundHandlerA());
        ch.pipeline().addLast(new InBoundHandlerB());
        ch.pipeline().addLast(new InBoundHandlerC());
        ch.pipeline().addLast(new OutBoundHandlerA());
        ch.pipeline().addLast(new OutBoundHandlerB());
        ch.pipeline().addLast(new OutBoundHandlerC());
        ch.pipeline().addLast(new ExceptionCaughtHandler());
    }
});

结果:异常定义在B,A先执行channelRead过去了,所以不会执行exceptionCaught
在这里插入图片描述

5.3.3.1 异常的触发链

channelRead中抛出的异常被catch捕捉,调用notifyHandlerException,会回调到exceptionCaught方法
然后使用ctx.fireExceptionCaught(cause)继续往next传播

异常传播顺序:按addlast添加顺序(不管是inbound还是outbound),传播到tail节点,进行最终处理

5.3.3.2 异常处理的最佳实践

在childHandler方法(即pipeline链)的最后添加一个异常处理器,对每一种异常分别做处理,其他的handler一直使用ctx.fireExceptionCaught(cause)传播下去即可;
这样就不会被tail处理:tail会打印logger.warn级别的日志进行提醒

5.4 总结

  • Netty是如何判断ChannelHandler类型的?

调用”pipeline添加节点“方法时,使用instanceof,通过两个boolean类型的字段inbound和outbound来设置类型

  • 对于ChannelHandler的添加应该遵循什么样的顺序?

in会顺序执行、out会逆序执行,所以inbound正着来,outbound要反着来

  • 用户手动触发事件传播,不同的触发方式有什么样的区别?

ctx.channel().pipeline().fireChannelRead()从head开始,ctx.fireChannelRead()从下一个开始(可能会错过一些inboundHandler)

ctx.channel().write()从tail开始, ctx.write()从下一个开始(可能会错过一些outboundHandler)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值