深入netty之Pipeline 設計原理

深入netty之Pipeline 設計原理

上一篇文章中我們已經知道了在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應,它們的組成關係如下:

在這裏插入圖片描述

通過上圖我們可以看到,一個 Channel 包含了一個 ChannelPipeline,而 ChannelPipeline 中又維護了一個由 ChannelHandlerContext 組成的雙向鏈表。這個鏈表的頭是 HeadContext,鏈表的尾是 TailContext,並且每個 ChannelHandlerContext 中又關聯着一個 ChannelHandler

接下來我們從源碼角度去看一下netty中是如何設計Pipeline 的,從AbstractChannel的構造器方法開始

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

AbstractChannel 有一個 pipeline 字段,在構造器中會初始化它爲 DefaultChannelPipeline 的實例
接着我們跟蹤一下 DefaultChannelPipeline 的初始化過程,首先進入到 DefaultChannelPipeline 構造器中:

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

在 DefaultChannelPipeline 構造器中, 首先將與之關聯的 Channel 保存到字段 channel 中。然後實例化兩個ChannelHandlerContext:一個是 HeadContext 實例 head,另一個是 TailContext 實例 tail。接着將 head 和 tail 互相指向, 構成一個雙向鏈表
TailContext 類層次結構:
在這裏插入圖片描述
HeadContext 類層次結構:
在這裏插入圖片描述

從類層次結構圖中可以很清楚地看到,head 實現了 ChannelInboundHandler 接口,而 tail 實現了 ChannelOutboundHandler 接口,並且它們都實現了 ChannelHandlerContext 接口, 因此可以說 head 和 tail 即是一個 ChannelHandler,又是一個 ChannelHandlerContext

ChannelInitializer 的添加

到目前爲止,這個 Pipeline 還並不能實現什麼特殊的功能,因爲我們還 沒有給它添加自定義的 ChannelHandler
通常來說,我們在初始化 Bootstrap,會添加我們自定義的 ChannelHandler, 以客戶端啓動代碼片段來舉例:

Bootstrap bootstrap = new Bootstrap(); 
bootstrap.group(group)
	.channel(NioSocketChannel.class)
	.option(ChannelOption.SO_KEEPALIVE, true) 
	.handler(new ChannelInitializer<SocketChannel>() { 
	@Override 
	protected void initChannel(SocketChannel ch) throws Exception { 
			ChannelPipeline pipeline = ch.pipeline(); 
			pipeline.addLast(new ChatClientHandler(nickName)); 
	}
});

上面代碼的初始化過程,相信大家都不陌生。在調用 handler 時,傳入了一個 ChannelInitializer 對象,它提供了一個 initChannel()方法給我我們初始化 ChannelHandler,這個初始化過程是怎樣的呢?又是在什麼時候添加到 ChannelPipeline 中的呢?
Bootstrap 的 init()方法中添加到 ChannelPipeline 中的,其代碼如下:

void init(Channel channel) throws Exception { 
	ChannelPipeline p = channel.pipeline(); 
	p.addLast(config.handler()); 
	...
}

從上面的代碼可見,Bootstrap 將 config.handler()返回的 ChannelHandler 添加到 Pipeline 中,而 handler()返回的其實就是我們在初始化 Bootstrap 時通過 handler()方法設置的 ChannelInitializer 實例(ChannelInitializer 實現了 ChannelInboundHandlerAdapter),因此這裏其實就是將 ChannelInitializer 插入到了 Pipeline 的末端。此時 Pipeline 的結構如下圖所示
在這裏插入圖片描述

可是我們在客戶端代碼中插入的是一個 ChannelInitializer 實例,爲什麼在 ChannelPipeline 中的雙向鏈表中的元素卻是一個 ChannelHandlerContext 呢?我們需要繼續去源碼中尋找答案
剛纔,我們提到,在 Bootstrap 的 init()方法中會調用 p.addLast()方法,將 ChannelInitializer 插入到鏈表的末端:

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);
            }
        return this;
    }

addLast()方法最終落在DefaultChannelPipeline中,在addLast()方法中首先檢查 ChannelHandler 的名字是否是重複,如果不重複,則調用 newContex()方法爲 Handler 創建一個對應的 DefaultChannelHandlerContext 實例,並將兩者關聯起來(Context 中有一個 handler 屬性保存着對應的 Handler 實例)。
爲了添加一個 handler 到 pipeline 中,必須把此 handler 包裝成 ChannelHandlerContext。

newContext()方法會構建一個DefaultChannelHandlerContext 對象,構造器如下:

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

自定義 ChannelHandler 的添加過程
前面我們已經分析了 ChannelInitializer 是如何插入到 Pipeline 中的,接下來就來探討 ChannelInitializer 在哪裏被調用,ChannelInitializer 的作用,以及我們自定義的 ChannelHandler 是如何插入到 Pipeline 中的

先簡單複習一下 Channel 的註冊過程:
1、首先在 AbstractBootstrap 的 initAndRegister()中,通過 group().register(channel),調用 MultithreadEventLoopGroup 的 register()方法。
2、在 MultithreadEventLoopGroup 的 register()中調用 next()獲取一個可用的 SingleThreadEventLoop,然後調用 它的 register()方法。
3、在 SingleThreadEventLoop 的 register()方法中,通過 channel.unsafe().register(this, promise)方法獲取 channel 的 unsafe()底層 IO 操作對象,然後調用它的 register()。
4、在 AbstractUnsafe 的 register()方法中,調用 register0()方法註冊 Channel 對象。
5、在 register0()方法中,調用 AbstractNioChannel 的 doRegister()方法。
6、doRegister()方法將 Channel 對應的 Java NIO 的 SockerChannel 對象註冊到一個 eventLoop 的 Selector 中,並且將當前 Channel 作爲 attachment

自定義 ChannelHandler 的添加過程,就發生在 步驟四 AbstractUnsafe 的 register0()方法中,在這個方法中調用了 pipeline.fireChannelRegistered()方法,其代碼實現如下:

        private void register0(ChannelPromise promise) {
        ...
                pipeline.fireChannelRegistered();
 		...
        }

fireChannelRegistered()方法代碼實現如下:

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

再看 AbstractChannelHandlerContext 的 invokeChannelRegistered()方法:

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

顯然,這個代碼會從 head 開始遍歷 Pipeline 的雙向鏈表,然後找到第一個屬性 inbound 爲 true 的 ChannelHandlerContext 實例。而ChannelInitializer 實現的是 ChannelInboudHandler,因此它所對應 的 ChannelHandlerContext 的 inbound 屬性就是 true,因此這裏返回就是 ChannelInitializer 實例所對應的 ChannelHandlerContext 對象,如下圖所示:
在這裏插入圖片描述
當獲取到 inbound(ChannelInitializer) 的 Context 後,就調用它的 invokeChannelRegistered()方法:

private void invokeChannelRegistered() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelRegistered(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelRegistered();
        }
    }

我們知道每個 ChannelHandler 都和一個 ChannelHandlerContext 關聯,很明顯這裏 handler()返回的對象就是我們實例化的 ChannelInitializer 對象,接着調用了 ChannelInitializer 的 channelRegistered()方法,繼續看代碼:

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (initChannel(ctx)) {
            ctx.pipeline().fireChannelRegistered();
        } else {
            ctx.fireChannelRegistered();
        }
    }
    private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                remove(ctx);
            }
            return true;
        }
        return false;
    }

注意initChannel()方法中調用了initChannel(© ctx.channel()),這個initChannel(C ch)方法我們也很熟悉,它就是我們在初始化 Bootstrap 時,調用 handler 方法傳入的匿 名內部類所實現的方法:

.handler(new ChannelInitializer<SocketChannel>() { 
		@Override 
		protected void initChannel(SocketChannel ch) throws Exception { 
				ChannelPipeline pipeline = ch.pipeline(); 
				pipeline.addLast(new ChatClientHandler(nickName)); 
		} 
});

因此,當調用這個方法之後, 我們自定義的 ChannelHandler 就插入到了 Pipeline,此時 Pipeline 的狀態如下圖所示:
在這裏插入圖片描述
當添加完成自定義的 ChannelHandler 後,在 finally 代碼塊會刪除自定義的 ChannelInitializer,也就是 remove(ctx)最 終調用 ctx.pipeline().remove(this),因此最後的 Pipeline 的狀態如下:

在這裏插入圖片描述

Pipeline 的事件傳播機制

我們已經知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 兩個 boolean 變量,分別用 於標識 Context 所對應的 handler 的類型,即:
1、inbound 爲 true 是,表示其對應的 ChannelHandler 是 ChannelInboundHandler 的子類。
2、outbound 爲 true 時,表示對應的 ChannelHandler 是 ChannelOutboundHandler 的子類。

這兩個字段到底有什麼作用呢? 這還要從 ChannelPipeline 的事件傳播類型說起。 Netty 中的傳播事件可以分爲兩種:Inbound 事件和 Outbound 事件

在這裏插入圖片描述
從上圖可以看出,inbound 事件和 outbound 事件的流向是不一樣的,inbound 事件的流行是從下至上,而 outbound 剛好相反,是從上到下

inbound 類似於是事件回調(響應請求的事件),而 outbound 類似於主動觸發(發起請求的 事件)

Outbound 事件傳播方式
Outbound 事件都是請求事件(request event),即請求某件事情的發生,然後通過 Outbound 事件進行通知。 Outbound 事件的傳播方向是 tail -> customContext -> head。

我們接下來以 connect 事件爲例,分析一下 Outbound 事件的傳播機制。 首先,當用戶調用了 Bootstrap 的 connect()方法時,就會觸發一個 Connect 請求事件,此調用會觸發如下調用鏈:
在這裏插入圖片描述
繼續跟蹤,我們就發現 AbstractChannel 的 connect()其實由調用了 DefaultChannelPipeline 的 connect()方法:

 @Override
    public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, promise);
    }

可以看到,當 outbound 類型的事件(這裏是 connect 事件)傳遞到 Pipeline 後,它其實是以 tail 爲起點開始傳播的。

而 tail.connect()會調用到 AbstractChannelHandlerContext 的 connect()方法:

public ChannelFuture connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
......
 final AbstractChannelHandlerContext next = findContextOutbound(); 
 EventExecutor executor = next.executor(); 
 next.invokeConnect(remoteAddress, localAddress, promise);
.....
return promise;
}

findContextOutbound()方法是以當前 Context 爲起點,沿着Pipeline 中的 Context 雙向鏈表尋找第一個 outbound 屬性爲 true 的 Context(即關聯 ChannelOutboundHandler 的 Context),然後返回

當找到了一個 outbound 類型的 Context 後,就調用它的 invokeConnect()方法,這個方法中會調用 Context 關聯的 ChannelHandler 的 connect()方法:

    private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            connect(remoteAddress, localAddress, promise);
        }
    }

於是這個調用回到了: Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect 這樣的循環中

直到 connect 事件傳遞到 DefaultChannelPipeline 的雙向鏈表的頭節點,即 head 中。爲什麼會傳遞 到 head 中呢?因爲,head 實現了 ChannelOutboundHandler

因爲 head 本身既是一個 ChannelHandlerContext,又實現了 ChannelOutboundHandler 接口,因此最終 connect()事件是在 head 中被處理。head 的 connect()事件處理邏輯如下:

 public void connect(
                ChannelHandlerContext ctx,
                SocketAddress remoteAddress, SocketAddress localAddress,
                ChannelPromise promise) throws Exception {
            unsafe.connect(remoteAddress, localAddress, promise);
        }

在這裏插入圖片描述
connect事件傳播流程總結:
1.Bootstrap調用connect方法,觸發事件事件被Channel.connect接收處理 Bootstrap.connect() -->Channel.connect()
2.Channel會調用其關聯的Pipeline的connect()方法, Channel.connect()–>Pipeline.connect()
3.Pipeline會從雙向鏈表的tail節點開始處理,調用tail節點的connect()方法Pipeline.connect() -->TailContext.connect()
4.TailContext會尋找下一個Outbound的ChannelHandlerContext,然後調用其invokeConnect方法TailContext.connect() -->ChannelHandlerContext.invokeConnect()
5.ChannelHandlerContext會調用其相關聯的ChannelOutboundHandler的connect()方法,ChannelHandlerContext.invokeConnect()–>ChannelOutboundHandler.connect()
6.這樣一直向下傳遞直到HeadContext,HeadContext同時也實現了ChannelOutboundHandler接口,因此最終connect事件被HeadContext的connect()方法處理

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