Netty学习 - ChannelPipeline流水线

DefaultChannelPipeline是ChannelPipeline的默认实现,从AbstractChannel类的构造函数可以看到,当创建新的Channel时与之关联的流水线也会被创建:

protected AbstractChannel(Channel parent, ChannelId id) {
    this.parent = parent;
    this.id = id;
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

正如《Netty实战》6.2节所述:

每一个新创建的Channel都将会被分配一个新的ChannelPipeline。这项关联是永久性的;Channel既不能附加另一个ChannelPipeline,也不能分离其当前的。

DefaultChannelPipeline类

以下代码片段列出了DefaultChannelPipeline类比较重要的成员变量:

public class DefaultChannelPipeline implements ChannelPipeline {
    private static final String HEAD_NAME = generateName0(HeadContext.class);
    private static final String TAIL_NAME = generateName0(TailContext.class);
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    private final Channel channel;
    private boolean firstRegistration = true;
    private boolean registered;
    // 省略一些代码

    private PendingHandlerCallback pendingHandlerCallbackHead;
    private boolean registered;

    protected DefaultChannelPipeline(Channel channel) {
        this.channel = ObjectUtil.checkNotNull(channel, "channel");
        succeededFuture = new SucceededChannelFuture(channel, null);
        voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }
    // 省略一些代码
}
  • head和tail:在DefaultChannelPipeline内部使用AbstractChannelHandlerContext类型的双向链表来维护添加的处理器,这两个值分别表示链表的头和尾,均为空节点;
  • HEAD_NAME和TAIL_NAME分别表示链表头和尾的类名,链表头的类型是HeadContext,链表尾的类型是TailContext,它们是DefaultChannelPipeline的内部类,均继承了AbstractChannelHandlerContext类;
  • channel表示这个流水线关联的通道;
  • pendingHandlerCallbackHead也是一个链表的头,这个单向链表用来存待定任务。

AbstractChannelHandlerContext类

我们先看一下AbstractChannelHandlerContext类,该处理器上下文类是用来包装处理器的,类层次如下图所示。


AbstractChannelHandlerContext.png

成员变量与构造函数

abstract class AbstractChannelHandlerContext extends DefaultAttributeMap
        implements ChannelHandlerContext, ResourceLeakHint {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannelHandlerContext.class);
    volatile AbstractChannelHandlerContext next;
    volatile AbstractChannelHandlerContext prev;

    private static final AtomicIntegerFieldUpdater<AbstractChannelHandlerContext> HANDLER_STATE_UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(AbstractChannelHandlerContext.class, "handlerState");

    /**
     * {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} is about to be called.
     */
    private static final int ADD_PENDING = 1;
    /**
     * {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called.
     */
    private static final int ADD_COMPLETE = 2;
    /**
     * {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
     */
    private static final int REMOVE_COMPLETE = 3;
    /**
     * Neither {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}
     * nor {@link ChannelHandler#handlerRemoved(ChannelHandlerContext)} was called.
     */
    private static final int INIT = 0;

    private final boolean inbound;
    private final boolean outbound;
    private final DefaultChannelPipeline pipeline;
    private final String name;
    private final boolean ordered;

    // Will be set to null if no child executor should be used, otherwise it will be set to the
    // child executor.
    final EventExecutor executor;
    private ChannelFuture succeededFuture;

    // Lazily instantiated tasks used to trigger events to a handler with different executor.
    // There is no need to make this volatile as at worse it will just create a few more instances then needed.
    private Runnable invokeChannelReadCompleteTask;
    private Runnable invokeReadTask;
    private Runnable invokeChannelWritableStateChangedTask;
    private Runnable invokeFlushTask;
    private volatile int handlerState = INIT;

    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                                  boolean inbound, boolean outbound) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.inbound = inbound;
        this.outbound = outbound;
        // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }

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

    @Override
    public ChannelPipeline pipeline() {
        return pipeline;
    }

    @Override
    public ByteBufAllocator alloc() {
        return channel().config().getAllocator();
    }

    @Override
    public EventExecutor executor() {
        if (executor == null) {
            return channel().eventLoop();
        } else {
            return executor;
        }
    }
}
  • 一个状态字段handlerState,初始值为INIT,在后续过程中会变化为ADD_PENDING、ADD_COMPLETE和REMOVE_COMPLETE,分别表示handlerAdded方法将要被调用、handlerAdded方法已经被调用和handlerRemoved方法已经被调用三种情况。HANDLER_STATE_UPDATER是用来更新该字段的原子更新类实例;
  • 两个布尔值inbound和outbound分别表示该上下文承载的处理器是否为入站处理器与是否出站处理器;
  • pipeline即为与之关联的流水线,name即为处理器的名字,即调用流水线的添加方法时传入的名字;
  • next指向下一个处理器上下文,prev指向上一个;
  • executor表示与该上下文绑定的EventExecutor或EventLoop,用来执行该上下文所关联的处理器的回调函数。

触发事件方法

本节以fireChannelRegistered为例说明AbstractChannelHandlerContext类很多事件处理方法的特点,该方法是ChannelHandlerContext接口定义的方法之一,用来触发流水线中下一个入站处理器注册事件的回调方法。跟事件相关的方法一般都有一个public方法以实现接口,紧跟着有一个静态方法和一个private方法。

@Override
public ChannelHandlerContext fireChannelRegistered() {
    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();
    }
}
  1. 调用next.executor(); 返回用来执行该上下文处理器回调方法的EventExecutor或EventLoop;
  2. 按当前线程是否是与上下文绑定的EventExecutor的支撑线程分类讨论,两种情况均会调用invokeChannelRegistered方法。

辅助方法

@Override
public EventExecutor executor() {
    if (executor == null) {
        return channel().eventLoop();
    } else {
        return executor;
    }
}

executor()方法返回该上下文绑定的EventExecutor或EventLoop,处理器的回调方法可以在与Channel关联的EvenLoop中执行,也可以在其他的EvenLoop中执行,这是由AbstractChannelHandlerContext类的构造函数参数决定的。

private AbstractChannelHandlerContext findContextInbound() {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

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

AbstractChannelHandlerContext类的事件触发方法都会调用findContextInbound或者findContextOutbound方法,这两个方法作用如下:

  • findContextInbound是找到该处理器之后的第一个入站处理器,注意TailContext是入站处理器的一种;
  • findContextOutbound是找到该处理器之前的第一个出站处理器,注意HeadContext是出站处理器的一种。

正因为有这两个方法,Netty的入站事件和出站事件才会被ChannelInboundHandler和ChannelOutboundHandler分别处理,从上述分析不难理解《Netty实战》3.2.2节所述:

通过使用作为参数传递到每个方法的ChannelHandlerContext,事件可以被传递给当前ChannelHandler链中的下一个ChannelHandler。通过调用ChannelHandlerContext上的对应方法,每个都提供了简单地将事件传递给下一个ChannelHandler的方法实现。

以及《Netty实战》6.2节所述:

根据事件的起源,事件将会被ChannelInboundHandler或者ChannelOutboundHandler处理。随后,通过调用ChannelHandlerContext实现,它将被转发给同一个超类型的下一个ChannelHandler。

/**
 * Makes best possible effort to detect if {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called
 * yet. If not return {@code false} and if called or could not detect return {@code true}.
 *
 * If this method returns {@code false} we will not invoke the {@link ChannelHandler} but just forward the event.
 * This is needed as {@link DefaultChannelPipeline} may already put the {@link ChannelHandler} in the linked-list
 * but not called {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}.
 */
private boolean invokeHandler() {
    // Store in local variable to reduce volatile reads.
    int handlerState = this.handlerState;
    return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}

invokeHandler方法是去检测处理器的handlerAdded是否被调用,如果没有则返回false,如果已经被调用或者无法判断,那么返回true。返回true意味着接下来会调用下一个处理器(入站的话是next,出站的话是prev)中对应事件的回调方法,以上文为例就是调用下一个处理器的channelRegistered回调方法。处理器的handlerAdded被调用有两种情况:

  • 处理器上下文的状态是ADD_COMPLETE,这会在DefaultChannelPipeline的callHandlerAdded0方法中被设置;
  • 处理器上下文的状态是ADD_PENDING,这会是在通道尚未注册却已经添加了处理器的情况下被设置,以DefaultChannelPipeline的addFirst方法为例,newCtx.setAddPending(); 这句会设置该状态。

实现类DefaultChannelHandlerContext

该类继承了AbstractChannelHandlerContext类并实现了handler()方法,代码如下:

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
    private final ChannelHandler handler;

    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;
    }
}

两个静态内部方法解释了《Netty实战》3.2.2节所述:

鉴于出站操作和入站操作是不同的,你可能会想知道如果将两个类别的ChannelHandler都混合添加到同一个ChannelPipeline中会发生什么。虽然ChannelInboundHandler和ChannelOutboundHandler都扩展自ChannelHandler,但是Netty能区分ChannelInboundHandler实现和ChannelOutboundHandler实现,并确保数据只会在具有相同定向类型的两个ChannelHandler之间传递。

构造函数解释了《Netty实战》6.3节所述:

ChannelHandlerContext和ChannelHandler之间的关联是永远不会改变的,所以缓存对它的引用是安全的。

ChannelPipeline添加处理器

流水线中添加处理器有addFirst、addLast、addBefore和addAfter等方法,以addFirst为例,其重载版本一共有5个,比较重要的是下面这个,另外4个重载方法同理。

@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        name = filterName(name, handler);
        newCtx = newContext(group, name, handler);
        addFirst0(newCtx);

        // If the registered is false it means that the channel was not registered on an eventloop yet.
        // In this case we add the context to the pipeline and add a task that will call
        // ChannelHandler.handlerAdded(...) once the channel is registered.
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            newCtx.setAddPending();
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerAdded0(newCtx);
                }
            });
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

private void addFirst0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext nextCtx = head.next;
    newCtx.prev = head;
    newCtx.next = nextCtx;
    head.next = newCtx;
    nextCtx.prev = newCtx;
}

addFirst方法中的参数group值得注意,它表示以后会在group表示的EventExecutorGroup中执行处理器的回调函数,如果传入null则表示会在与Channel绑定的EventLoop中执行。正如《Netty实战》6.2.1所述:

通常ChannelPipeline中的每一个ChannelHandler都是通过它的EventLoop(I/O线程)来处理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的I/O处理产生负面的影响。
但有时可能需要与那些使用阻塞API的遗留代码进行交互。对于这种情况,ChannelPipeline有一些接受一个EventExecutorGroup的add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup,它将被包含在这个EventExecutorGroup中的某个EventExecutor所处理,从而被从该Channel本身的EventLoop中移除。

添加处理器的过程如下:

  1. 首先获取流水线对象的锁,需要获取锁的原因是为了后面操作两个链表;
  2. checkMultiplicity方法检查作为参数的处理器是否是Sharable的,如果不是却已经添加过了则报错;
  3. 为处理器生成名称并验证名称不能重复;
  4. 用处理器新建DefaultChannelHandlerContext实例,并将其添加到链表头;
  5. 如果通道尚未注册,那么将处理器状态设置为添加待定,并添加到pendingHandlerCallbackHead表示的单向链表中,然后返回,处理器的handlerAdded回调方法何时被调用请看下文;
  6. 如果当前运行于该上下文绑定的EventExecutor/EventLoop上,那么直接调用callHandlerAdded0方法,否则交由绑定的EventExecutor执行callHandlerAdded0方法;
  7. 在callHandlerAdded0方法中,该上下文的状态被设置为添加完成,处理器的handlerAdded回调方法被调用,添加失败时处理器会被移除出流水线,处理器的handlerRemoved回调方法被调用。
    • 移除成功时会调用fireExceptionCaught触发异常事件,说明是处理器添加失败;
    • 移除失败时会调用fireExceptionCaught触发异常事件,说明是处理器添加且移除失败。
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
    try {
        // We must call setAddComplete before calling handlerAdded. Otherwise if the handlerAdded method generates
        // any pipeline events ctx.handler() will miss them because the state will not allow it.
        ctx.setAddComplete();
        ctx.handler().handlerAdded(ctx);
    } catch (Throwable t) {
        boolean removed = false;
        try {
            remove0(ctx);
            try {
                ctx.handler().handlerRemoved(ctx);
            } finally {
                ctx.setRemoved();
            }
            removed = true;
        } catch (Throwable t2) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to remove a handler: " + ctx.name(), t2);
            }
        }

        if (removed) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; removed.", t));
        } else {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() +
                    ".handlerAdded() has thrown an exception; also failed to remove.", t));
        }
    }
}

ChannelPipeline移除处理器

流水线中移除处理器有remove、removeIfExists、removeFirst和removeLast等方法,以remove为例,比较重要的是下面这个,其他的方法同理。移除的流程与添加相似,在此不再赘述。

private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
    assert ctx != head && ctx != tail;

    synchronized (this) {
        remove0(ctx);
        // If the registered is false it means that the channel was not registered on an eventloop yet.
        // In this case we remove the context from the pipeline and add a task that will call
        // ChannelHandler.handlerRemoved(...) once the channel is registered.
        if (!registered) {
            callHandlerCallbackLater(ctx, false);
            return ctx;
        }

        EventExecutor executor = ctx.executor();
        if (!executor.inEventLoop()) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerRemoved0(ctx);
                }
            });
            return ctx;
        }
    }
    callHandlerRemoved0(ctx);
    return ctx;
}

private static void remove0(AbstractChannelHandlerContext ctx) {
    AbstractChannelHandlerContext prev = ctx.prev;
    AbstractChannelHandlerContext next = ctx.next;
    prev.next = next;
    next.prev = prev;
}

调用callHandlerRemoved0方法,处理器的handlerRemoved回调方法被调用,移除失败时调用fireExceptionCaught触发异常事件。

private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
    // Notify the complete removal.
    try {
        try {
            ctx.handler().handlerRemoved(ctx);
        } finally {
            ctx.setRemoved();
        }
    } catch (Throwable t) {
        fireExceptionCaught(new ChannelPipelineException(
                ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
    }
}

添加待定与移除待定

在添加处理器和移除处理器的代码中有一句callHandlerCallbackLater,它的参数有两个,第一个表示处理器上下文,第二个表示是否是添加。该函数会构造待定添加任务或者待定移除任务并添加到以pendingHandlerCallbackHead为链表头的单向链表中。

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;
    PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
    PendingHandlerCallback pending = pendingHandlerCallbackHead;
    if (pending == null) {
        pendingHandlerCallbackHead = task;
    } else {
        // Find the tail of the linked-list.
        while (pending.next != null) {
            pending = pending.next;
        }
        pending.next = task;
    }
}

PendingHandlerCallback是DefaultChannelPipeline的静态抽象内部类,实现了Runnable接口,PendingHandlerAddedTask和PendingHandlerRemovedTask类都是它的子类,彼此很相似:

private abstract static class PendingHandlerCallback implements Runnable {
    final AbstractChannelHandlerContext ctx;
    PendingHandlerCallback next;

    PendingHandlerCallback(AbstractChannelHandlerContext ctx) {
        this.ctx = ctx;
    }

    abstract void execute();
}

private final class PendingHandlerAddedTask extends PendingHandlerCallback {

    PendingHandlerAddedTask(AbstractChannelHandlerContext ctx) {
        super(ctx);
    }

    @Override
    public void run() {
        callHandlerAdded0(ctx);
    }

    @Override
    void execute() {
        EventExecutor executor = ctx.executor();
        if (executor.inEventLoop()) {
            callHandlerAdded0(ctx);
        } else {
            try {
                executor.execute(this);
            } catch (RejectedExecutionException e) {
                // 省略一些代码
            }
        }
    }
}

private final class PendingHandlerRemovedTask extends PendingHandlerCallback {

    PendingHandlerRemovedTask(AbstractChannelHandlerContext ctx) {
        super(ctx);
    }

    @Override
    public void run() {
        callHandlerRemoved0(ctx);
    }

    @Override
    void execute() {
        EventExecutor executor = ctx.executor();
        if (executor.inEventLoop()) {
            callHandlerRemoved0(ctx);
        } else {
            try {
                executor.execute(this);
            } catch (RejectedExecutionException e) {
                // 省略一些代码
            }
        }
    }
}

待定任务链表中都是通道注册前添加或移除的处理器,它们的handlerAdded或者handlerRemoved回调方法尚未执行,只有在通道真正注册后才会执行。通道注册时会执行AbstractChannel的内部类AbstractUnsafe的register0方法:

private void register0(ChannelPromise promise) {
    try {
        // 省略一些代码
        boolean firstRegistration = neverRegistered;
        doRegister();
        neverRegistered = false;
        registered = true;
        pipeline.invokeHandlerAddedIfNeeded();
        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        // 省略一些代码
    } catch (Throwable t) {
        // 省略一些代码
    }
}

在注册的过程中,流水线的invokeHandlerAddedIfNeeded方法会被执行。从assert channel.eventLoop().inEventLoop(); 这一行可以看到此时执行线程一定是与该通道绑定的EventLoop的I/O线程,如果是首次注册,那么接着调用流水线的invokeHandlerAddedIfNeeded方法。

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // We are now registered to the EventLoop. It's time to call the callbacks for the ChannelHandlers,
        // that were added before the registration was done.
        callHandlerAddedForAllHandlers();
    }
}

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;
        // This Channel itself was registered.
        registered = true;
        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        // Null out so it can be GC'ed.
        this.pendingHandlerCallbackHead = null;
    }

    // This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
    // holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
    // the EventLoop.
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}

callHandlerAddedForAllHandlers方法的synchronized块内将registered置为true,以后再添加处理器时便不会再有待定的了,接着遍历链表执行这些注册前添加的处理器的handlerAdded或handlerRemoved回调方法。注释的意思是遍历待定任务链表必须不能持有流水线的对象锁,否则会死锁,这是为什么呢?

避免死锁

从上文AbstractChannelHandlerContext类的executor()方法可以看到,其返回的EventExecutor可能不是与通道绑定的EventLoop,而是在构造函数中另外指定的。

  • 在遍历之前即注册阶段一直运行在与通道绑定的EventLoop的I/O线程上(记为线程1);
  • 若PendingHandlerCallback的execute方法走到else分支那么就会在当前运行线程之外的线程(记为线程2)中调用callHandlerAdded0方法,接着该上下文对应处理器的handlerAdded回调方法被调用。若在handlerAdded回调方法中调用同一个流水线的addFirst等添加处理器方法,从前文对添加处理器过程的分析可知线程2就会去尝试获取流水线的对象锁。

综合上述分析,如果callHandlerAddedForAllHandlers方法的sychronized块包含了while循环,即遍历的过程中持有锁,那么线程1会一直持有流水线的对象锁,线程2始终无法获得。
但受本人理解能力有限,我个人认为这不会产生死锁,最多是锁竞争,因为线程2异步执行了并不会影响链表的遍历,还请各位读者不吝赐教。

ChannelPipeline触发事件

流水线对入站事件和出站事件的触发部分代码如下,触发入站事件都是从头部开始,而触发出站事件都是从尾部开始,注意看head均是作为参数,而tail都是调用者。流水线触发事件本质上是由相应的ChannelHandlerContext去触发的。

@Override
public final ChannelPipeline fireChannelRegistered() {
    AbstractChannelHandlerContext.invokeChannelRegistered(head);
    return this;
}

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}

@Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
    AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
    return this;
}

@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

@Override
public final ChannelPipeline fireChannelReadComplete() {
    AbstractChannelHandlerContext.invokeChannelReadComplete(head);
    return this;
}
// 省略一些代码
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
    return tail.connect(remoteAddress);
}

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

@Override
public final ChannelFuture writeAndFlush(Object msg) {
    return tail.writeAndFlush(msg);
}
// 省略一些代码

此时不难理解《Netty实战》3.2.2节所述:

在Netty中,有两种发送消息的方式。你可以直接写到Channel中,也可以写到和ChannelHandler相关联的ChannelhandlerContext对象中。前一种方式将会导致消息从ChannelPipeline的尾端开始流动,而后者将导致消息从ChannelPipeline中的下一个Channelhandler开始流动。

  • 从AbstractChannel的部分代码可以看到直接写到Channel中实际上是由流水线完成的,而流水线的写正是从尾部开始。
    @Override
    public ChannelFuture write(Object msg) {
        return pipeline.write(msg);
    }
    
  • 下面为AbstractChannelHandlerContext的写和冲刷操作的核心代码,从findContextOutbound调用可以看到写操作是从该上下文的下一个出站处理器开始(对出站事件实际是用prev往前搜的)。
    private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }
    

在分别分析了ChannelhandlerContext和ChannelPipeline的触发事件之后,本文引用《Netty实战》6.3节所述总结两者的区别:

ChannelhandlerContext有很多的方法,其中一些方法也存在于Channel和ChannelPipeline本身上,但是有一点重要的不同。如果调用Channel和ChannelPipeline上的这些方法,它们将沿着整个ChannelPipeline进行传播。而调用位于ChannelhandlerContext上的相同方法,则将从当前所关联的Channelhandler开始,并且智慧传播给位于该ChannelPipeline中的下一个能够处理该事件的Channelhandler。

总结

深入分析ChannelPipeline有助于加深对Netty的事件定义和传播的理解,使运用ChannelHandler等接口的回调方法以及方法中的ChannelhandlerContext参数更加自如。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容