一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(二)

lemonvita
发布于 2022-7-18 17:20
浏览
0收藏

2.1 HeadContext

    private static final String HEAD_NAME = generateName0(HeadContext.class);

    final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {
       //headContext中持有对channel unsafe操作类的引用 用于执行channel底层操作
        private final Unsafe unsafe;

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, HeadContext.class);
            //持有channel unsafe操作类的引用,后续用于执行channel底层操作
            unsafe = pipeline.channel().unsafe();
            //设置channelHandler的状态为ADD_COMPLETE
            setAddComplete();
        }

        @Override
        public ChannelHandler handler() {
            return this;
        }

        .......................
    }

我们知道双向链表结构的 pipeline 中的节点元素为 ChannelHandlerContext ,既然 HeadContext 作为 pipeline 的头结点,那么它一定是 ChannelHandlerContext 类型的,所以它需要继承实现 AbstractChannelHandlerContext ,相当于一个哨兵的作用,因为用户可以以任意顺序向 pipeline 中添加 ChannelHandler ,需要用 HeadContext 来固定指向第一个 ChannelHandlerContext 。

 

在《一文搞懂Netty发送数据全流程》 一文中的《1. ChannelHandlerContext》小节中,笔者曾为大家详细介绍过 ChannelHandlerContext 在 pipeline 中的作用,忘记的同学可以在回看下。


于此同时 HeadContext 又实现了 ChannelInboundHandler 和 ChannelOutboundHandler  接口,说明 HeadContext 即是一个 ChannelHandlerContext 又是一个 ChannelHandler ,它可以同时处理 Inbound 事件和 Outbound 事件。

 

我们也注意到 HeadContext 中持有了对应 channel 的底层操作类 unsafe ,这也说明 IO 事件在 pipeline 中的传播最终会落在 HeadContext 中进行最后的 IO 处理。它是 Inbound 事件的处理起点,也是 Outbound 事件的处理终点。这里也可以看出 HeadContext 除了起到哨兵的作用,它还承担了对 channel 底层相关的操作。

 

比如我们在《Reactor在Netty中的实现(启动篇)》中介绍的 NioServerSocketChannel 在向 main reactor 注册完成后会触发 ChannelRegistered 事件从 HeadContext 开始依次在 pipeline 中向后传播。

      @Override
        public void channelRegistered(ChannelHandlerContext ctx) {
            //此时firstRegistration已经变为false,在pipeline.invokeHandlerAddedIfNeeded中已被调用过
            invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
        }

以及 NioServerSocketChannel 在与端口绑定成功后会触发 ChannelActive 事件从 HeadContext 开始依次在 pipeline 中向后传播,并在 HeadContext 中通过 unsafe.beginRead() 注册 OP_ACCEPT 事件到 main reactor 中。

     @Override
        public void read(ChannelHandlerContext ctx) {
            //触发注册OP_ACCEPT或者OP_READ事件
            unsafe.beginRead();
        }

同理在 NioSocketChannel 在向 sub reactor 注册成功后。会先后触发 ChannelRegistered 事件和 ChannelActive 事件从 HeadContext 开始在 pipeline 中向后传播。并在 HeadContext 中通过 unsafe.beginRead() 注册 OP_READ 事件到 sub reactor 中。

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            //pipeline中继续向后传播channelActive事件
            ctx.fireChannelActive();
            //如果是autoRead 则自动触发read事件传播
            //在read回调函数中 触发OP_ACCEPT或者OP_READ事件注册
            readIfIsAutoRead();
        }

《一文搞懂Netty发送数据全流程》中介绍的 write 事件和 flush 事件最终会在 pipeline 中从后向前一直传播到 HeadContext ,并在 HeadContext 中相应事件回调函数中调用 unsafe 类操作底层 channel 发送数据。

        @Override
        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
            //到headContext这里 msg的类型必须是ByteBuffer,也就是说必须经过编码器将业务层写入的实体编码为ByteBuffer
            unsafe.write(msg, promise);
        }

        @Override
        public void flush(ChannelHandlerContext ctx) {
            unsafe.flush();
        }

从本小节的内容介绍中,我们可以看出在 Netty 中对于 Channel 的相关底层操作调用均是在 HeadContext 中触发的。

 

2.2 TailContext
 

private static final String TAIL_NAME = generateName0(TailContext.class);

    final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {

        TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, TAIL_NAME, TailContext.class);
            //设置channelHandler的状态为ADD_COMPLETE
            setAddComplete();
        }

        @Override
        public ChannelHandler handler() {
            return this;
        }
    
        ......................
}

同样 TailContext 作为双向链表结构的 pipeline 中的尾结点,也需要继承实现 AbstractChannelHandlerContext 。但它同时又实现了 ChannelInboundHandler 。

 

这说明 TailContext 除了是一个 ChannelHandlerContext 同时也是一个 ChannelInboundHandler 。

 

2.2.1 TailContext 作为一个 ChannelHandlerContext 的作用


TailContext 作为一个 ChannelHandlerContext 的作用是负责将 outbound 事件从 pipeline 的末尾一直向前传播直到 HeadContext 。当然前提是用户需要调用 channel 的相关 outbound 方法。

public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {

    ChannelFuture write(Object msg);

    ChannelFuture write(Object msg, ChannelPromise promise);

    ChannelOutboundInvoker flush();

    ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);

    ChannelFuture writeAndFlush(Object msg);

}
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {

   @Override
    public ChannelFuture write(Object msg) {
        return pipeline.write(msg);
    }

    @Override
    public Channel flush() {
        pipeline.flush();
        return this;
    }

    @Override
    public ChannelFuture writeAndFlush(Object msg) {
        return pipeline.writeAndFlush(msg);
    }
}
public class DefaultChannelPipeline implements ChannelPipeline {

   @Override
    public final ChannelFuture write(Object msg) {
        return tail.write(msg);
    }

    @Override
    public final ChannelPipeline flush() {
        tail.flush();
        return this;
    }

   @Override
    public final ChannelFuture writeAndFlush(Object msg) {
        return tail.writeAndFlush(msg);
    }

}

这里我们可以看到,当我们在自定义 ChannelHandler 中调用 ctx.channel().write(msg) 时,会在 AbstractChannel 中触发 pipeline.write(msg) ,最终在 DefaultChannelPipeline 中调用 tail.write(msg) 。使得 write 事件可以从 pipeline 的末尾开始向前传播,其他 outbound 事件的传播也是一样的道理。

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        ctx.channel().write(msg);
    }

}

而我们自定义的 ChannelHandler 会被封装在一个 ChannelHandlerContext 中从而加入到 pipeline 中,而这个用于装载自定义 ChannelHandler 的 ChannelHandlerContext 与 TailContext 一样本质也都是 ChannelHandlerContext ,只不过在 pipeline 中的位置不同罢了。一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(二)-鸿蒙开发者社区

客户端channel pipeline结构.png

 

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {

    ChannelFuture write(Object msg);

    ChannelFuture write(Object msg, ChannelPromise promise);

    ChannelOutboundInvoker flush();

    ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);

    ChannelFuture writeAndFlush(Object msg);

}

我们看到 ChannelHandlerContext 接口本身也会继承 ChannelInboundInvoker 和 ChannelOutboundInvoker 接口,所以说 ContextHandlerContext 也可以触发 inbound 事件和 outbound 事件,只不过表达的语义是在 pipeline 中从当前 ChannelHandler 开始向前或者向后传播 outbound 事件或者 inbound 事件。

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        ctx.write(msg);
    }

}

这里表示 write 事件从当前 EchoServerHandler 开始在 pipeline 中向前传播直到 HeadContext 。

一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(二)-鸿蒙开发者社区

 客户端channel pipeline结构.png


2.2.2 TailContext 作为一个 ChannelInboundHandler 的作用


最后 TailContext 作为一个 ChannelInboundHandler 的作用就是为 inbound 事件在 pipeline 中的传播做一个兜底的处理。

 

这里提到的兜底处理是什么意思呢?

 

比如我们前边介绍到的,在 NioSocketChannel 向 sub reactor 注册成功后之后触发的 ChannelRegistered 事件和 ChannelActive 事件。或者在 reactor 线程读取 NioSocketChannel 中的请求数据时所触发的 channelRead 事件和 ChannelReadComplete 事件。

 

这些 inbound 事件都会首先从 HeadContext 开始在 pipeline 中一个一个的向后传递。

 

极端的情况是如果 pipeline 中所有 ChannelInboundHandler 中相应的 inbound 事件回调方法均不对事件作出处理,并继续向后传播。如下示例代码所示:

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.fireChannelReadComplete();
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelRegistered();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }
}

最终这些 inbound 事件在 pipeline 中得不到处理,最后会传播到 TailContext 中。

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            onUnhandledInboundMessage(ctx, msg);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            onUnhandledInboundChannelReadComplete();
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            onUnhandledInboundChannelActive();
        }

}

而在 TailContext 中需要对这些得不到任何处理的 inbound 事件做出最终处理。比如丢弃该 msg,并释放所占用的 directByteBuffer,以免发生内存泄露。

 

protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) {
        onUnhandledInboundMessage(msg);
        if (logger.isDebugEnabled()) {
            logger.debug("Discarded message pipeline : {}. Channel : {}.",
                         ctx.pipeline().names(), ctx.channel());
        }
    }

    protected void onUnhandledInboundMessage(Object msg) {
        try {
            logger.debug(
                    "Discarded inbound message {} that reached at the tail of the pipeline. " +
                            "Please check your pipeline configuration.", msg);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

标签
已于2022-7-18 17:20:20修改
收藏
回复
举报
回复
    相关推荐