AbstractChannelHandlerContext 笔记

本文详细探讨了Netty中的AbstractChannelHandlerContext类如何通过双向链表结构组织ChannelPipeline,以及inbound/outbound属性的作用。重点讲解了事件触发机制,如fireChannelRegistered和fireExceptionCaught的实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类定义:

abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
        implements ChannelHandlerContext, ResourceLeakHint {}

类属性:

next && prev

每个AbstractChannelHandlerContext都保存有两个属性, next和prev, 明显是做链表.

volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;

在DefaultChannelPipeline中, 则保存了名为head和tail的两个引用:

final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;

这样在DefaultChannelPipeline中就实现了一个从head一路next/next到tail, 再从tail一路prev/prev到head的一个双向链表.

inbound && outbound

在这里插入图片描述

这两个属性用来表明当前handler是inbound还是outbound, 通常和ChannelInboundHandler/ChannelOutboundHandler联系起来,.
比如HeadContext实现了ChannelOutboundHandler, 而TailContext实现了ChannelInboundHandler, 他们在调用super构造函数时就写死了inbound和outbound属性:

final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {

        private final Unsafe unsafe;

        HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, HEAD_NAME, false, true);
            unsafe = pipeline.channel().unsafe();
            setAddComplete();
        }
 }

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {

        TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, null, TAIL_NAME, true, false);
            setAddComplete();
        }
}

netty设计上是使用两个boolean来记录inbound/outbound,

在DefaultChannelHandlerContext的实现中, 通过检查传入的handler来判断inbound/outbound, 判断的方法非常直接, instanceof ChannelInboundHandler/ChannelOutboundHandler:

DefaultChannelHandlerContext(
    DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
    super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    this.handler = handler;
	}

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

    private static boolean isInbound(ChannelHandler handler) {
        return handler instanceof ChannelInboundHandler;
    }

    private static boolean isOutbound(ChannelHandler handler) {
        return handler instanceof ChannelOutboundHandler;
    }

类方法:

setRemoved()

这是一个非常特别的属性, 只用于非常极端的情况, getter/setter方法如下:

final void setRemoved() {
        handlerState = REMOVE_COMPLETE;
    }


@Override
public boolean isRemoved() {
    return handlerState == REMOVE_COMPLETE;
}

其中setRemoved()还不是public的, 调用的地方只有一处, 在类DefaultChannelPipeline:

private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
    // Notify the complete removal.
    try {
        try {
            // 从context 删除之后不在调用, handlerRemoved()自带方法
            ctx.handler().handlerRemoved(ctx);
        } finally {
            ctx.setRemoved();
        }
    } catch (Throwable t) {
        fireExceptionCaught(new ChannelPipelineException(
            ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
    }
}
AttributeKey 相关方法

ChannelHandlerContext申明继承AttributeMap, AbstractChannelHandlerContext中有实现AttributeMap要求的两个方法:

@Override
public <T> Attribute<T> attr(AttributeKey<T> key) {
    return channel().attr(key);
}

@Override
public <T> boolean hasAttr(AttributeKey<T> key) {
    return channel().hasAttr(key);
}

最终还是delegate给channel的对应方法了.

Inbound 的IO 事件方法

这些方法最终都是delegate给invoker的对应方法, 以fireChannelRegistered()为例:

@Override
public ChannelHandlerContext fireChannelRegistered() {
    // 找到下一个inbound 的 context,并调用invokeChannelRegistered() 方法
    invokeChannelRegistered(findContextInbound());
    return this;
}

static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRegistered();
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRegistered();
            }
        });
    }
}

private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRegistered();
    }
}

private AbstractChannelHandlerContext findContextInbound() {
    // ctx 初始化为指向当前的context, 也就是this
        AbstractChannelHandlerContext ctx = this;
        do {
            // 然后指向ctx 的next 指针,向后找
            ctx = ctx.next;
        } while (!ctx.inbound); // 检查是否是inbound,如果不是则继续next,直到找到下一个
        return ctx;
    }


这和javadoc中对这些方法的说明一致: “这些方法会导致当前Channel的ChannelPipeline中包含的下一个ChannelInboundHandler的相应的方法被调用”
类似的方法有一下:

ChannelHandlerContext fireChannelRegistered();
ChannelHandlerContext fireChannelUnregistered();
ChannelHandlerContext fireChannelActive();
ChannelHandlerContext fireChannelInactive();
ChannelHandlerContext fireExceptionCaught(Throwable cause);        // 这个例外!
ChannelHandlerContext fireUserEventTriggered(Object event);
ChannelHandlerContext fireChannelRead(Object msg);
ChannelHandlerContext fireChannelReadComplete();
ChannelHandlerContext fireChannelWritabilityChanged();

但是fireExceptionCaught方法非常的特殊, 与众不同的是, 这个方法中的next是不区分inbound和outbound的, 直接取this.next:

@Override
    public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
        invokeExceptionCaught(next, cause);
        return this;
    }

    static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) {
        ObjectUtil.checkNotNull(cause, "cause");
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeExceptionCaught(cause);
        } else {
            try {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        next.invokeExceptionCaught(cause);
                    }
                });
            } catch (Throwable t) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to submit an exceptionCaught() event.", t);
                    logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
                }
            }
        }
    }

outbound 的 IO方法

首先, 下面这些方法在实现时都会转变为他们对应的带promise的版本:

ChannelFuture bind(SocketAddress localAddress);
ChannelFuture connect(SocketAddress remoteAddress);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
ChannelFuture disconnect();
ChannelFuture close();
ChannelFuture deregister();

例如bind()方法, 通过newPromise()方法创建一个promise之后, 就调用带promise的版本了:

@Override
public ChannelFuture bind(SocketAddress localAddress) {
    return bind(localAddress, newPromise());
}

@Override
    public ChannelPromise newPromise() {
        return new DefaultChannelPromise(channel(), executor());
    }

@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (isNotValidPromise(promise, false)) {
            // cancelled
            return promise;
        }
        // 找到下一个 outbound 的context
        final AbstractChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }


private AbstractChannelHandlerContext findContextOutbound() {
        AbstractChannelHandlerContext ctx = this;
        do {
            // 向前找
            ctx = ctx.prev;
        } while (!ctx.outbound);
        return ctx;
    }

注意:在前面咱们已经说过,inbound 入站流是自下而上方向依次处理,出站流是自上而下的方向处理,入站表示接受IO请求,例如read() 读取数据, 出战outbound 表示请求IO操作,例如写数据、关闭IO等,所以inbound 找下一个是next, outbound 是 prev。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值