Netty7# Netty之事件傳遞

前言

前面的文章中寫了Channel實例化、Channel初始化、Channel註冊、異步通知機制、客戶端發起連接、事件的輪詢和處理機制。Netty作爲client/server高效通信框架,事件在ChannelPipeline是如何傳遞的,本文就聊聊這事。

一、事件傳遞過程 


ChannelPipeline隨着Channel的創建而創建,在 Netty2# Netty組件之Channel初始化 文章中梳理了ChannelPipeline、ChannelHandlerContext、ChannelHandler的關係如下圖。

ChannelPipeline大管道維護了一個ChannelHandlerContext鏈表,頭部爲HeadContext,尾部爲TailContext。事件傳播會沿着鏈表逐級向下傳遞。


1.Inbound&Outbound標識
 

當ChannelHandlerContext創建時,它是Inbound還是Outbound,是哪個方向的就確定了。下面分析是這種身份是如何確定的。

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                              String name, Class<? extends ChannelHandler> handlerClass) {
    this.name = ObjectUtil.checkNotNull(name, "name");
    this.pipeline = pipeline;
    this.executor = executor;
    this.executionMask = mask(handlerClass); // 註解@1
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}

註解@1 父類的構造函數中有一個mask(handlerClass)方法,這個方法確定了ChannelHandlerContext的身份。如下代碼ChannelInboundHandler.class.isAssignableFrom(handlerType)即如果是ChannelInboundHandler類型mask |= MASK_ALL_INBOUND;反之如果是ChannelOutboundHandler類型,mask |= MASK_ALL_OUTBOUND;通過賦予mask不同的值來區分是哪個方向的Handler。isSkippable中的邏輯判斷主要對加註解@Skip的方法不再進行事件回調。

private static int mask0(Class<? extends ChannelHandler> handlerType) {
    int mask = MASK_EXCEPTION_CAUGHT;
    try {
        if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
            mask |= MASK_ALL_INBOUND; // 標識爲INBOUND
            if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)){
                mask &= ~MASK_CHANNEL_REGISTERED;
            }
            // ...
        }
        if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
            mask |= MASK_ALL_OUTBOUND; // 標識爲OUTBOUND
            if (isSkippable(handlerType, "bind", ChannelHandlerContext.class,
                    SocketAddress.classChannelPromise.class)) 
{
                mask &= ~MASK_BIND;
            }
            // ...
        }
        // ...
    } catch (Exception e) {
       // ...
    }
    return mask;
}
2.Outbound傳遞過程
 

示例入口

ChannelFuture future = b.connect(HOST, PORT).sync(); 
future.channel().writeAndFlush("Hi");

以writeAndFlush方法跟蹤下Outbound事件在ChannelPipeline的傳遞過程。

@Override
public final ChannelFuture writeAndFlush(Object msg) {
    return tail.writeAndFlush(msg); // 註解@2
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
    // ...
   final AbstractChannelHandlerContext next = findContextOutbound(flush ?
            (MASK_WRITE | MASK_FLUSH) : MASK_WRITE); // 註解@3
    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 {
        final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
        if (!safeExecute(executor, task, promise, m, !flush)) {
            //...
            task.cancel();
        }
    }
}
private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.prev;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND)); // 註解@4
    return ctx;
}
private static boolean skipContext(
        AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask)
 
{
    return (ctx.executionMask & (onlyMask | mask)) == 0 ||
            (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0); // 註解@5
}

註解@2 從鏈表尾部TailContext開始執行。

註解@3 我們示例writeAndFlush所以findContextOutbound的mask爲(MASK_WRITE | MASK_FLUSH)

註解@4 循環鏈表查找,注意skipContext是判斷的跳過邏輯。我們查找Outbound的ChannelHandlerContext,遇到Inbound的都會跳過。

註解@5 判斷是否跳過,這段邏輯是位操作,不好閱讀。下面示例抽取各個入參的值測試下:如果下一個ChannelHandlerContext爲inBound,則skipContext返回true,從而在查找outBound的do/while循環中跳過。

int executionMask =  MASK_EXCEPTION_CAUGHT |= MASK_ALL_INBOUND; // inBound 身份標識

int mask = MASK_WRITE | MASK_FLUSH; // writeAndFlush 標識

int onlyMask = MASK_ONLY_OUTBOUND; // outBound操作集合

System.out.println((executionMask & (onlyMask | mask))==0);

輸出爲:true

小結: outBound事件在ChannelPipeline中傳遞時,只會選擇身份爲outBound的ChannelHandlerContext執行。

3.Inbound傳遞過程
 

以上一篇文章 Netty6# Netty之事件輪詢與處理 當有新的客戶端的連接時觸發unsafe.read()執行。具體爲NioMessageUnsafe#read()方法。具體爲上文中註解@17

// 入口
pipeline.fireChannelRead(readBuf.get(i));

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

private void invokeChannelRead(Object msg) {
  if (invokeHandler()) {
    try {
      ((ChannelInboundHandler) handler()).channelRead(this, msg);
    } catch (Throwable t) {
      notifyHandlerException(t);
    }
  } else {
    fireChannelRead(msg); // 註解@6
  }
}

註解@6 下面的方法findContextInbound(MASK_CHANNEL_READ),只查找Inbound的ChannelHandlerContext。具體邏輯與Outbound傳遞過程相似,不再重複。

@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
    return this;
}

小結: inBound事件在ChannelPipeline中傳遞時,只會選擇身份爲inBound的ChannelHandlerContext執行。

二、ChannelPipeline重要API 


ChannelPipeline的默認實現爲DefaultChannelPipeline,以下API源碼梳理均來自該實現類。

接口 說明
addFirst 在ChannelPipeline中,將ChannelHandlerContext添加HeadContext的後面。
addLast 在ChannelPipeline中,將ChannelHandlerContext添加到TailContext的前面。
addBefore 在ChannelPipeline中,將ChannelHandlerContext添加到某ChannelHandlerContext的前面。
 remove 在ChannelPipeline中,將ChannelHandlerContext從鏈表中移除。
replace 在ChannelPipeline中,將新newCtx在鏈表中替換就得oldCtx。
1.addFirst
 

說明:在ChannelPipeline中,將ChannelHandlerContext添加HeadContext的後面。

@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler); // 註解@7
            name = filterName(name, handler); // 註解@8

            newCtx = newContext(group, name, handler); 

            addFirst0(newCtx); // 註解@9
      
           // ...
        }
        callHandlerAdded0(newCtx); // 註解@10
        return this;
    }

註解@7:checkMultiplicity()方法校驗ChannelHandler是否重複。如果ChannelHandler中有註解@Sharable標識,則允許同一個ChannelHandler添加到不同的ChannelPipeline中。未加@Sharable註解的ChannelHandler只允許添加到一個ChannelPipeline

private static void checkMultiplicity(ChannelHandler handler) {
    if (handler instanceof ChannelHandlerAdapter) {
        ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
        if (!h.isSharable() && h.added) { // 判斷@Sharable註解和該Handler是否已經被添加
            throw new ChannelPipelineException(
                    h.getClass().getName() +
                    " is not a @Sharable handler, so can't be added or removed multiple times.");
        }
        h.added = true;
    }
}

@Sharable使用示例

@Sharable
public class StringEncoder extends MessageToMessageEncoder<CharSequence{
}

註解@8: ChannelHandler名字判斷,沒有設置Handler名字則自動生成一個;設置了Handler名字,不能與該ChannelPipeline其他ChannelHandler名字重複。

private String filterName(String name, ChannelHandler handler) {
    if (name == null) {
        return generateName(handler); 
    }
    checkDuplicateName(name);
    return name;
}

註解@9 創建DefaultChannelHandlerContext,並加入將其加入到鏈表中。

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

經過上面鏈表的順序調整,addFirst將ChannelHandlerContext添加到了HeadContext的後面。

註解@10 :當ChannelHandler添加ChannelPipeline後,回調該Handler的handlerAdded方法,也是通知機制的常用做法。

2.addLast
 

說明:在ChannelPipeline中,將ChannelHandlerContext添加到TailContext的前面。

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);

        newCtx = newContext(group, filterName(name, handler), handler);

        addLast0(newCtx); // 註解@11

        // ...
    }
    callHandlerAdded0(newCtx);
    return this;
}

註解@11  其他邏輯同addFirst,addLast0將HandlerContext添加到TailContext的前一個位置

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}


3.addBefore
 


說明:在ChannelPipeline中,將ChannelHandlerContext添加到某ChannelHandlerContext的前面。

public final ChannelPipeline addBefore(
        EventExecutorGroup group, String baseName, String name, ChannelHandler handler)
 
{
    final AbstractChannelHandlerContext newCtx;
    final AbstractChannelHandlerContext ctx;
    synchronized (this) {
        checkMultiplicity(handler);
        name = filterName(name, handler);
        ctx = getContextOrDie(baseName); // 註解@12

        newCtx = newContext(group, name, handler); 

        addBefore0(ctx, newCtx); // 註解@13

        // ...
    }
    callHandlerAdded0(newCtx);
    return this;
}

註解@12 根據傳入的baseName在ChannelPipleline查找對應的HandlerContext。

private AbstractChannelHandlerContext getContextOrDie(String name) {
    AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
    if (ctx == null) {
        throw new NoSuchElementException(name);
    } else {
        return ctx;
    }
}

註解@13 添加到鏈表中,添加到baseName的前面。

private static void addBefore0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
    newCtx.prev = ctx.prev;
    newCtx.next = ctx;
    ctx.prev.next = newCtx;
    ctx.prev = newCtx;
}


4.addAfter
 

說明:在ChannelPipeline中,將ChannelHandlerContext添加到指定的ChannelHandlerContext的後面。

public final ChannelPipeline addAfter(
        EventExecutorGroup group, String baseName, String name, ChannelHandler handler)
 
{
    final AbstractChannelHandlerContext newCtx;
    final AbstractChannelHandlerContext ctx;

    synchronized (this) {
        checkMultiplicity(handler);
        name = filterName(name, handler);
        ctx = getContextOrDie(baseName);

        newCtx = newContext(group, name, handler);

        addAfter0(ctx, newCtx); // 註解@14

        
    }
    callHandlerAdded0(newCtx);
    return this;
}

註解@14: 調整鏈表順序,調整方式同上。

private static void addAfter0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
    newCtx.prev = ctx;
    newCtx.next = ctx.next;
    ctx.next.prev = newCtx;
    ctx.next = newCtx;
}


5.remove
 

說明:在ChannelPipeline中,將ChannelHandlerContext從鏈表中移除。

private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
    assert ctx != head && ctx != tail; // 註解@15
    synchronized (this) {
        atomicRemoveFromHandlerList(ctx); // 註解@16
    }
    // ... 
    callHandlerRemoved0(ctx);
    return ctx;
}

註解@15 被移除的ChannelHandlerContext不能是HeadContext和TailContext。

註解@16 通過調整前後ChannelHandlerContext的指針指向實現移除操作。

private synchronized void atomicRemoveFromHandlerList(AbstractChannelHandlerContext ctx) {
    AbstractChannelHandlerContext prev = ctx.prev;
    AbstractChannelHandlerContext next = ctx.next;
    prev.next = next;
    next.prev = prev;
}


6.replace
 

說明:在ChannelPipeline中,將新newCtx在鏈表中替換就得oldCtx。

private ChannelHandler replace(
        final AbstractChannelHandlerContext ctx, String newName, ChannelHandler newHandler)
 
{
    assert ctx != head && ctx != tail; // 註解@17

    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(newHandler);
        if (newName == null) {
            newName = generateName(newHandler);
        } else {
            boolean sameName = ctx.name().equals(newName);
            if (!sameName) {
                checkDuplicateName(newName);
            }
        }
        newCtx = newContext(ctx.executor, newName, newHandler);
        replace0(ctx, newCtx); // 註解@18
        // ...
    }
    //...
    return ctx.handler();
}

註解@17 被替換的ChannelHandlerContext不能是HeadContext和TailContext。

註解@18 下面替換邏輯中,首先將oldCtx的前後指針暫存;newCtx前後指針指向剛纔的暫存;把暫存的pre的next指向newCtx,暫存的next的prev指向newCtx,此時newCtx已經替換到了鏈表中;將oldCtx的prev和next都指向了newCtx,目的爲了已經進入了oldCtx的數據正確流轉無論是inbound還是outbound數據。

private static void replace0(AbstractChannelHandlerContext oldCtx, AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = oldCtx.prev;
    AbstractChannelHandlerContext next = oldCtx.next;
    newCtx.prev = prev;
    newCtx.next = next;

    // Finish the replacement of oldCtx with newCtx in the linked list.
    // Note that this doesn't mean events will be sent to the new handler immediately
    // because we are currently at the event handler thread and no more than one handler methods can be invoked
    // at the same time (we ensured that in replace().)
    prev.next = newCtx;
    next.prev = newCtx;

    // update the reference to the replacement so forward of buffered content will work correctly
    oldCtx.prev = newCtx;
    oldCtx.next = newCtx;
}

小結: ChannelPipeline提供了方便的API對鏈表中的ChannelHandlerContext進行插入、刪除、添加、替換操作。


本文分享自微信公衆號 - 瓜農老梁(gh_01130ae30a83)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章