netty源碼分析(19)- 添加ChannelHandler過程

上一節學習了pipeline初始化的過程。初始化了HeadContextTailContext,並構建了pipeline雙向鏈表,每個節點存儲ChannelHandlerContext

本節研究添加ChannelHandler的過程。在學習之前先整理一些之前學到的內容。

  • 服務端channel初始化channle的過程中,bossGroup服務端channelpipeline添加了一個特殊的ChannelHandler:ServerBootstrapAcceptor,如下這幅圖。
  • 客戶端channel所在的workGroup處理所有childHandler,這裏的Handler屬於客戶端channel

  • 因此在bossGroup在初始化channel的過程中,會調用addLast()增加handler,促發HandlerAdded事件,而引導配置的時候觸發的是ChannelInitializer#handlerAdded()

入口

在引導的過程當中,重寫了ChannelInitializer#initChannel(),其中獲取了pipeline並調用了addLast()方法,同事將ChannelHandler作爲參數傳入addLast方法中。

class DataServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {

        ch.pipeline()
                .addLast(new DefaultEventExecutorGroup(2),
                        new DataServerHandler(),
                        new DataServerOutHandler2(),
                        new DataServerOutHandler(),
                        new DataServerHandler2());
    }
}
  • DefaultChannelPipeline#addLast()
    @Override
    public final ChannelPipeline addLast(ChannelHandler... handlers) {
        return addLast(null, handlers);
    }

    @Override
    public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
        if (handlers == null) {
            throw new NullPointerException("handlers");
        }
        //循環添加
        for (ChannelHandler h: handlers) {
            if (h == null) {
                break;
            }
            addLast(executor, null, h);
        }

        return this;
    }  
具體的addLast方法做了以下幾件事
  1. 檢查Handler是否重複添加
  2. 創建數節點ChannelHandlerContext
  3. 添加數據節點
  4. 觸發handlerAdded事件,執行對應Handler的回調函數
    @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            //檢查是否重複添加
            checkMultiplicity(handler);

            //group:異步線程
            //創建數據節點
            //filterName:從頭節點開始遍歷檢查名字是否重複,如果沒有傳入name則生成一個name
            newCtx = newContext(group, filterName(name, handler), handler);
            //添加數據節點
            addLast0(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;
            }

            //觸發事件回調函數:handlerAdded
            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        //觸發事件回調函數:handlerAdded
        callHandlerAdded0(newCtx);
        return this;
    }
  • checkMultiplicity(handler);檢查Handler是否重複添加
    邏輯主要時判斷是否有@Sharable註解,從而判斷是否允許從夫,其次通過成員變量added屬性判斷是否被添加過。如果重複的話則排除異常。
    private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            //判斷是否是共享 同時  是否時被添加過的
            if (!h.isSharable() && h.added) {
                throw new ChannelPipelineException(
                        h.getClass().getName() +
                        " is not a @Sharable handler, so can't be added or removed multiple times.");
            }
            h.added = true;
        }
    }

    public boolean isSharable() {
        /**
         * Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a
         * {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different
         * {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of
         * {@link Thread}s are quite limited anyway.
         *
         * See <a href="https://github.com/netty/netty/issues/2289">#2289</a>.
         */
        Class<?> clazz = getClass();
        Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
        Boolean sharable = cache.get(clazz);
        if (sharable == null) {
            sharable = clazz.isAnnotationPresent(Sharable.class);
            cache.put(clazz, sharable);
        }
        return sharable;
    }

  • newCtx = newContext(group, filterName(name, handler), handler);,創建數據節點
    實例化DefaultChannelHandlerContext的過程中將handler作爲成員變量持有,並且標記了handlerinbound還是outbound,並將一些重要的組件pipeline,executor保存起來。
    private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
        //實例化DefaultChannelHandlerContext
        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
    }
    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");
        }
        //持有handler
        this.handler = handler;
    }

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

指的注意的是childExecutor,該方法處理添加handler時,傳入的EventExecutorGroup。該Executor可用於用戶在回調方法中處理異步計算。

    private EventExecutor childExecutor(EventExecutorGroup group) {
        if (group == null) {
            return null;
        }
        Boolean pinEventExecutor = channel.config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
        if (pinEventExecutor != null && !pinEventExecutor) {
            return group.next();
        }
        //存儲異步線程的容器:管理異步線程
        Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
        if (childExecutors == null) {
            // Use size of 4 as most people only use one extra EventExecutor.
            //新建一個Map存儲異步線程
            childExecutors = this.childExecutors = new IdentityHashMap<EventExecutorGroup, EventExecutor>(4);
        }
        // Pin one of the child executors once and remember it so that the same child executor
        // is used to fire events for the same channel.
        //存儲異步線程
        EventExecutor childExecutor = childExecutors.get(group);
        if (childExecutor == null) {
            childExecutor = group.next();
            childExecutors.put(group, childExecutor);
        }
        //返回異步線程
        return childExecutor;
    }

用戶配置異步線程,處理異步計算。


class DataServerInitializer extends ChannelInitializer<SocketChannel> {

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

        ch.pipeline()
                //配置異步線程處理異步計算
                .addLast(new DefaultEventExecutorGroup(2),
                        new DataServerHandler(),
                        new DataServerOutHandler2(),
                        new DataServerOutHandler(),
                        new DataServerHandler2());
    }
}

class DataServerHandler extends SimpleChannelInboundHandler<Object> {
    public DataServerHandler() {
        super(false);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(MessageFormat.format("---> receive client msg = \"{0}\"", byteBuf.toString(CharsetUtil.UTF_8)));

        System.out.println("<--- send to client msg = server msg");
        ByteBuf serverMsg = Unpooled.copiedBuffer("server msg", CharsetUtil.UTF_8);

        ctx.write(serverMsg);

        ctx.executor().execute(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(20);
                    System.out.println("io async end ");
                } catch (InterruptedException e) {
                    //ignore
                }
            }
        });

        System.out.println("<--- send to client channel msg = server channel msg");
        ByteBuf serverChannelMsg = Unpooled.copiedBuffer("server channel msg", CharsetUtil.UTF_8);
        //tail.write
        ctx.channel().write(serverChannelMsg);

        System.out.println("<--- send to client msg = server msg2");
        ByteBuf serverMsg2 = Unpooled.copiedBuffer("server msg2", CharsetUtil.UTF_8);
        ctx.write(serverMsg2);

        ctx.fireChannelRead(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }
}

結合初始化NioEventLoopGroup的過程實際上這裏保存的executor就是ThreadPerTaskExecutor,因此同樣時新增了一個EventLoop數組並初始化一個chooser,當group.next()的時候調用chooser選擇一個EventLoop執行操作。

    protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        this(nThreads, threadFactory == null ? null : new ThreadPerTaskExecutor(threadFactory), args);
    }
  • 添加數據節點addLast0(newCtx);
    其邏輯主要是鏈表的操作:將新的數據節點添加到尾節點(TailContext)之前
    private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;
        newCtx.prev = prev;
        newCtx.next = tail;
        prev.next = newCtx;
        tail.prev = newCtx;
    }

  • callHandlerAdded0(newCtx);:觸發handlerAdded事件
    private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
            //調用回調函數
            ctx.callHandlerAdded();
        } catch (Throwable t) {
           //省略代碼
    }

    final void callHandlerAdded() throws Exception {
        // 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.
        if (setAddComplete()) {
            //獲取handler調用handlerAdded觸發事件
            handler().handlerAdded(this);
        }
    }
整理以下整個添加handler的過程
  • bossGroup初始化的時候將對服務端channelpipeline進行了添加ChannelInitializer的操作,促發用戶initChannel回調方法,添加具體的handler,之後刪除ChannelInitializer

  • 其中bossGroup添加具體的handler,包括兩個:引導調用handler()配置的,另一個是ServerBootstrapAcceptor

  • 當新連接接入時候,會創建客戶端channel並初始化,當處理讀取事件的時候觸發ServerBootstrap.ServerBootstrapAcceptor#channelRead(),調用如下代碼

            //添加childHandler
            child.pipeline().addLast(childHandler);

childHandler也是引導配置的時候調用childHandler()方法,傳入ChannelInitializer實例作爲參數。因此同樣的邏輯。不一樣的是,childHandler的處理邏輯已經交給了childGroup進行處理了,脫離了bossGroup,查看如下代碼。

                 //選擇NioEventLoop並註冊Selector
                childGroup.register(child)
                        .addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            forceClose(child, future.cause());
                        }
                    }
                });
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章