netty源碼分析(20)- 刪除ChannelHandler過程

上一節學習了添加ChannelHandler的過程瞭解了,也明白了添加handler是如何促發handlerAdded事件。其中還包括了ChannelInitializer這樣特殊的handler,在完成了添加handler的任務後,就從pipeline中刪除了。

本節研究刪除ChannelHandler的邏輯。類似ChannelInitializer這種,handler用完一次就沒什麼用的場景,類似權限校驗,每次新連接接入需要校驗權限,之後的數據傳輸就不需要了,這時候就可以動態的刪掉權限校驗的handler。如下

class DataServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ch.pipeline().addLast(
                        new AuthHandler(),
                        new DataServerHandler()
                );
    }
}


class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf password) throws Exception {
        //校驗密碼
        if (pass(password)) {
            //校驗通過,後續不需要繼續校驗,刪除該handler
            ctx.pipeline().remove(this);
        }else {
            ctx.close();
        }
    }

    private boolean pass(ByteBuf password) {
        //ignore: 校驗邏輯
        return false;
    }
}

ChannelHandler是背ChannelHanderContext包裝起來的。因此,刪除ChannelHandler過程如下:

  1. 找到handler對應的ChannelHandlerContext
  2. 從鏈表中刪除ChannelHandlerContext
  3. 觸發handlerRemoved事件
    @Override
    public final ChannelPipeline remove(ChannelHandler handler) {
        //先獲取ChannelHandlerContext再刪除
        remove(getContextOrDie(handler));
        return this;
    }
  • getContextOrDie()找到handler對應的ChannelHandlerContext
    private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
        //獲取ChannelHandlerContext,沒有則拋異常
        AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
        if (ctx == null) {
            throw new NoSuchElementException(handler.getClass().getName());
        } else {
            return ctx;
        }
    }


    @Override
    public final ChannelHandlerContext context(ChannelHandler handler) {
        //handler爲空則拋異常
        if (handler == null) {
            throw new NullPointerException("handler");
        }
        //從鏈表頭開始遍歷到尾,直到,直到找到對應的handler對應的ctx
        AbstractChannelHandlerContext ctx = head.next;
        for (;;) {

            if (ctx == null) {
                return null;
            }

            if (ctx.handler() == handler) {
                return ctx;
            }

            ctx = ctx.next;
        }
    }
  • 先刪除,再調用callHandlerRemoved0觸發handlerRemoved事件
    private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
        //斷言,非頭也非尾
        assert ctx != head && ctx != tail;

        synchronized (this) {
            //將ctx從pipeline鏈表中刪除
            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() {
                        //觸發HandlerRemoved事件
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        //觸發HandlerRemoved事件
        callHandlerRemoved0(ctx);
        return ctx;
    }
  • 從鏈表中刪除
    private static void remove0(AbstractChannelHandlerContext ctx) {
        //鏈表刪除,修改兩端的前後節點
        AbstractChannelHandlerContext prev = ctx.prev;
        AbstractChannelHandlerContext next = ctx.next;
        prev.next = next;
        next.prev = prev;
    }

  • 觸發handlerRemoved事件。該動作獲取了handler並調用了回調函數handlerRemoved(),最後修改了handler狀態,標記已經完成了刪除。
    private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
        // Notify the complete removal.
        try {
            //調用ctx的觸發事件方法
            ctx.callHandlerRemoved();
        } catch (Throwable t) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
        }
    }

    final void callHandlerRemoved() throws Exception {
        try {
            // Only call handlerRemoved(...) if we called handlerAdded(...) before.
            if (handlerState == ADD_COMPLETE) {
                //獲取handler並觸發事件回調函數
                handler().handlerRemoved(this);
            }
        } finally {
            // Mark the handler as removed in any case.
            //標記handler已經刪除
            setRemoved();
        }
    }

    final void setRemoved() {
        //修改handler狀態爲完成刪除
        handlerState = REMOVE_COMPLETE;
    }

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