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參數更加自如。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容