Netty源碼------Pipeline詳細分析

Netty源碼------Pipeline詳細分析

目錄

Netty源碼------Pipeline詳細分析

1、Channel 與ChannelPipeline

2、再探ChannelPipeline 的初始化:

3、ChannelInitializer 的添加

4、自定義ChannelHandler 的添加過程

5、ChannelHandler 默認命名規則

6、Pipeline 的事件傳播機制

6.1 Outbound 事件傳播方式

6.2 Inbound 事件傳播方式

6.3 Pipeline 事件傳播小結

7、Handler 的各種姿勢

8、Channel 的生命週期


1、Channel 與ChannelPipeline

  相信大家都已經知道,在Netty 中每個Channel 都有且僅有一個ChannelPipeline 與之對應,它們的組成關係如下:

  通過上圖我們可以看到, 一個Channel 包含了一個ChannelPipeline , 而ChannelPipeline 中又維護了一個由ChannelHandlerContext 組成的雙向鏈表。這個鏈表的頭是HeadContext,鏈表的尾是TailContext,並且每個ChannelHandlerContext 中又關聯着一個ChannelHandler。圖示給了我們一個對ChannelPipeline 的直觀認識,但是實際上Netty 實現的Channel 是否真的是這樣的呢?我們繼續用源碼說話。在前我們已經知道了一個Channel 的初始化的基本過程,下面我們再回顧一下。下面的代碼是AbstractChannel 構造器:

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

  AbstractChannel 有一個pipeline 字段,在構造器中會初始化它爲DefaultChannelPipeline 的實例。這裏的代碼就印證了一點:每個Channel 都有一個ChannelPipeline。接着我們跟蹤一下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 互相指向, 構成一個雙向鏈表。

  特別注意的是:我們在開始的示意圖中head 和tail 並沒有包含ChannelHandler,這是因爲HeadContext 和TailContext繼承於AbstractChannelHandlerContext 的同時也實現了ChannelHandler 接口了,因此它們有Context 和Handler的雙重屬性。

2、再探ChannelPipeline 的初始化:

  前面的學習我們已經對ChannelPipeline 的初始化有了一個大致的瞭解,不過當時重點沒有關注ChannelPipeline,因此沒有深入地分析它的初始化過程。那麼下面我們就來看一下具體的ChannelPipeline 的初始化都做了哪些工作吧。先回顧一下,在實例化一個Channel 時,會伴隨着一個ChannelPipeline 的實例化,並且此Channel 會與這個ChannelPipeline相互關聯,這一點可以通過NioSocketChannel 的父類AbstractChannel 的構造器予以佐證:

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

  當實例化一個NioSocketChannel 是,其pipeline 字段就是我們新創建的DefaultChannelPipeline 對象。可以看到,在DefaultChannelPipeline 的構造方法中,將傳入的channel 賦值給字段this.channel,接着又實例化了兩個特殊的字段:tail 與head,這兩個字段是一個雙向鏈表的頭和尾。其實在DefaultChannelPipeline 中,維護了一個以AbstractChannelHandlerContext 爲節點的雙向鏈表,這個鏈表是Netty 實現Pipeline 機制的關鍵。再回顧一下head和tail 的類層次結構:

  從類層次結構圖中可以很清楚地看到,head 實現了ChannelInboundHandler與ChannelOutboundHandler,而tail 實現了ChannelOutboundHandler 接口,並且它們都實現了ChannelHandlerContext 接口, 因此可以說head 和tail 即是一個ChannelHandler,又是一個ChannelHandlerContext。接着看HeadContext與TailContext 構造器中的代碼:

HeadContext(DefaultChannelPipeline pipeline) {
            super(pipeline, (EventExecutor)null, DefaultChannelPipeline.HEAD_NAME, false, true);
            this.unsafe = pipeline.channel().unsafe();
            this.setAddComplete();
}
TailContext(DefaultChannelPipeline pipeline) {
            super(pipeline, (EventExecutor)null, DefaultChannelPipeline.TAIL_NAME, true, false);
            this.setAddComplete();
}

  我們可以看到,鏈表中head 是一個ChannelOutboundHandler,而tail 則是一個ChannelInboundHandler。它調用了父類AbstractChannelHandlerContext 的構造器,並傳入參數inbound = false,outbound = true。而TailContext 的構造器與HeadContext 的相反,它調用了父類AbstractChannelHandlerContext 的構造器,並傳入參數inbound = true,outbound = false。即header 是一個OutBoundHandler,而tail 是一個InBoundHandler。

3、ChannelInitializer 的添加

  前面我們已經分析過Channel 的組成,其中我們瞭解到,最開始的時候ChannelPipeline 中含有兩個ChannelHandlerContext(同時也是ChannelHandler),但是這個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。最後將這個匿名的Handler保存到AbstractBootstrap中。那麼這個初始化過程是怎樣的呢?下面我們來揭開它的神祕面紗。

  ChannelInitializer 實現了ChannelHandler,那麼它是在什麼時候添加到ChannelPipeline 中的呢?通過代碼跟蹤,我們發現它是在Bootstrap 的init()方法中添加到ChannelPipeline 中的,其代碼如下(以客戶端爲例):

 

void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        p.addLast(new ChannelHandler[]{this.config.handler()});
     。。。。。。
}
//AbstractBootstrapConfig
public final ChannelHandler handler() {
  return this.bootstrap.handler();
}
//AbstractBootstrap
final ChannelHandler handler() {
        return this.handler;
}

  從上面的代碼可見,將handler()返回的ChannelHandler 添加到Pipeline 中,而handler()返回的其實就是我們在初始化Bootstrap 時通過handler()方法設置的ChannelInitializer 實例,因此這裏就是將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 = this.newContext(group, this.filterName(name, handler), handler);
       this.addLast0(newCtx);
 }
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, this.childExecutor(group), name, handler);
}

 

  addLast()有很多重載的方法,我們只需關注這個比較重要的方法就行。上面的addLast()方法中,首先檢查ChannelHandler 的名字是否是重複,如果不重複,則調用newContex()方法爲這個Handler 創建一個對應的DefaultChannelHandlerContext 實例,並與之關聯起來(Context 中有一個handler 屬性保存着對應的Handler 實例)。爲了添加一個handler 到pipeline 中,必須把此handler 包裝成ChannelHandlerContext。因此在上面的代碼中我們可以看到新實例化了一個newCtx 對象,並將handler 作爲參數傳遞到構造方法中。那麼我們來看一下實例化的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");
        } else {
            this.handler = handler;
        }
    }

 

  在DefaultChannelHandlerContext 的構造器中,調用了兩個很有意思的方法:isInbound()與isOutbound(),這兩個方法是做什麼的呢?從源碼中可以看到,當一個handler 實現了ChannelInboundHandler 接口,則isInbound 返回true;類似地,當一個handler 實現了ChannelOutboundHandler 接口,則isOutbound 就返回true。而這兩個boolean 變量會傳遞到父類AbstractChannelHandlerContext 中,並初始化父類的兩個字段:inbound 與outbound。那麼這裏的ChannelInitializer 所對應的DefaultChannelHandlerContext 的inbound 與outbound 字段分別是什麼呢? 那就看一下ChannelInitializer 到底實現了哪個接口不就行了?如下是ChannelInitializer 的類層次結構圖:

  從類圖中可以清楚地看到,ChannelInitializer 僅僅實現了ChannelInboundHandler 接口,因此這裏實例化的DefaultChannelHandlerContext 的inbound = true,outbound = false。兜了一圈,不就是inbound 和outbound 兩個字段嘛,爲什麼需要這麼大費周折地分析一番?其實這兩個字段關係到pipeline 的事件的流向與分類,因此是十分關鍵的,不過我在這裏先賣個關子, 後面我們再來詳細分析這兩個字段所起的作用。至此, 我們暫時先記住一個結論:ChannelInitializer 所對應的DefaultChannelHandlerContext 的inbound =true,outbound = false。當創建好Context 之後,就將這個Context 插入到Pipeline 的雙向鏈表中

 

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

   添加完ChannelInitializer的Pipeline現在是長這樣的:

 4、自定義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. 在AbstractUnsafe 的register0()方法中,調用AbstractNioChannel 的doRegister()方法。
  6. AbstractNioChannel 的doRegister()方法調用javaChannel().register(eventLoop().selector, 0, this)將Channel對應的Java NIO 的SockerChannel 對象註冊到一個eventLoop 的Selector 中,並且將當前Channel 作爲attachment。

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

 

private void register0(ChannelPromise promise) {
        boolean firstRegistration = this.neverRegistered;
        AbstractChannel.this.doRegister();
        this.neverRegistered = false;
        AbstractChannel.this.registered = true;
        AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
        this.safeSetSuccess(promise);
        AbstractChannel.this.pipeline.fireChannelRegistered();
}
public final ChannelPipeline fireChannelRegistered() {
        AbstractChannelHandlerContext.invokeChannelRegistered(this.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() {
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

 

  很顯然,這個代碼會從head 開始遍歷Pipeline 的雙向鏈表,然後 findContextInbound()  找到第一個屬性inbound 爲true 的ChannelHandlerContext 實例。看代碼:

 

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            DefaultChannelPipeline.this.invokeHandlerAddedIfNeeded();
            ctx.fireChannelRegistered();
}
public ChannelHandlerContext fireChannelRegistered() {
        invokeChannelRegistered(this.findContextInbound());
        return this;
}

 

  想起來了沒?我們在前面分析ChannelInitializer 時,花了大量的篇幅來分析了inbound和outbound 屬性,現在這裏就用上了。回想一下,ChannelInitializer 實現了ChannelInboudHandler,因此它所對應的ChannelHandlerContext 的inbound 屬性就是true,因此這裏返回就是ChannelInitializer 實例所對應的ChannelHandlerContext 對象,如下圖所示:

  當獲取到inbound 的Context 後,就調用它的invokeChannelRegistered()方法:

 

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

 

  我們已經知道,每個ChannelHandler 都和一個ChannelHandlerContext 關聯,我們可以通過ChannelHandlerContext獲取到對應的ChannelHandler。因此很明顯,這裏handler()返回的對象其實就是一開始我們實例化的ChannelInitializer 對象,並接着調用了ChannelInitializer 的channelRegistered()方法。看到這裏, 應該會覺得有點眼熟了。ChannelInitializer 的channelRegistered()這個方法我們在一開始的時候已經接觸到了,但是我們並沒有深入地分析這個方法的調用過程。下面我們來看這個方法中到底有什麼玄機,繼續看代碼:

 

public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
  protected abstract void initChannel(C ch) throws Exception;
    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (initChannel(ctx)) {
            ctx.pipeline().fireChannelRegistered();
            removeState(ctx);
        } else {
            ctx.fireChannelRegistered();
        }
    }private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
        if (initMap.add(ctx)) { // Guard against re-entrance.
            try {
                initChannel((C) ctx.channel());
            } catch (Throwable cause) {
                exceptionCaught(ctx, cause);
            } finally {
                ChannelPipeline pipeline = ctx.pipeline();
                if (pipeline.context(this) != null) {
                    pipeline.remove(this);
                }
            }
            return true;
        }
        return false;
    }
}

 

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

protected void initChannel(SocketChannel ch) throws Exception {
      ChannelPipeline pipeline = ch.pipeline();
      pipeline.addLast("handler", new MyClient());
}

  因此,當調用這個方法之後, 我們自定義的ChannelHandler 就插入到了Pipeline,此時Pipeline 的狀態如下圖所示:

 

  當添加完成自定義的ChannelHandler 後,在finally 代碼塊會刪除自定義的ChannelInitializer,也就是remove(ctx)最終調用ctx.pipeline().remove(this),因此最後的Pipeline 的狀態如下:(這裏圖有誤!

  至此,自定義ChannelHandler 的添加過程也分析得差不多了。

5、ChannelHandler 默認命名規則

  不知道大家注意到沒有,pipeline.addXXX 都有一個重載的方法,例如addLast()它有一個重載的版本是:ChannelPipeline addLast(String name, ChannelHandler handler);第一個參數指定添加的handler 的名字(更準確地說是ChannelHandlerContext 的名字,說成handler 的名字更便於理解)。那麼handler 的名字有什麼用呢?如果我們不設置name,那麼handler 默認的名字是怎樣呢?帶着這些疑問,我們依舊還是去源碼中找到答案。還是以addLast()方法爲例:

public final ChannelPipeline addLast(String name, ChannelHandler handler) {
        return addLast(null, name, handler);
    }

  這個方法會調用重載的addLast()方法:

 

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

 

  第一個參數被設置爲null,我們不用關心它。第二參數就是這個handler 的名字。看代碼可知,在添加一個handler之前,需要調用checkMultiplicity()方法來確定新添加的handler 名字是否與已添加的handler 名字重複。

  如果我們調用的是如下的addLast()方法:ChannelPipeline addLast(ChannelHandler... handlers);那麼Netty 就會調用generateName()方法爲新添加的handler 自動生成一個默認的名字:

 

private String filterName(String name, ChannelHandler handler) {
        if (name == null) {
            return generateName(handler);
        }
        checkDuplicateName(name);
        return name;
    }
private String generateName(ChannelHandler handler) {
    Map<Class<?>, String> cache = nameCaches.get();
    Class<?> handlerType = handler.getClass();
    String name = cache.get(handlerType);
    if (name == null) {
        name = generateName0(handlerType);
        cache.put(handlerType, name);
    }
    if (context0(name) != null) {
        String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
        for (int i = 1;; i ++) {
            String newName = baseName + i;
            if (context0(newName) == null) {
                name = newName;
                break;
            }
        }
    }
    return name;
}

  而generateName()方法會接着調用generateName0()方法來實際生成一個新的handler 名字

private static String generateName0(Class<?> handlerType) {
        return StringUtil.simpleClassName(handlerType) + "#0";
}

  默認命名的規則很簡單,就是用反射獲取handler 的simpleName 加上"#0",因此我們自定義ChatClientHandler 的名字就是"ChatClientHandler#0"。

6、Pipeline 的事件傳播機制

  前面章節中,我們已經知道AbstractChannelHandlerContext 中有inbound 和outbound 兩個boolean 變量,分別用於標識Context 所對應的handler 的類型,即:

  1. inbound 爲true 是,表示其對應的ChannelHandler 是ChannelInboundHandler 的子類。
  2. outbound 爲true 時,表示對應的ChannelHandler 是ChannelOutboundHandler 的子類。

  這裏大家肯定還有很多疑惑,不知道這兩個字段到底有什麼作用? 這還要從ChannelPipeline 的事件傳播類型說起。Netty 中的傳播事件可以分爲兩種:Inbound 事件和Outbound 事件。如下是從Netty 官網針對這兩個事件的說明:

  從上圖可以看出,inbound 事件和outbound 事件的流向是不一樣的,inbound 事件的流行是從下至上,而outbound剛好相反,是從上到下。並且inbound 的傳遞方式是通過調用相應的ChannelHandlerContext.fireIN_EVT()方法,而outbound 方法的的傳遞方式是通過調用ChannelHandlerContext.OUT_EVT()方法。例如:ChannelHandlerContext的fireChannelRegistered()調用會發送一個ChannelRegistered 的inbound 給下一個ChannelHandlerContext,而ChannelHandlerContext 的bind()方法調用時會發送一個bind 的outbound 事件給下一個ChannelHandlerContext。

    Inbound 事件傳播方法有:

 

public interface ChannelInboundHandler extends ChannelHandler {
    void channelRegistered(ChannelHandlerContext var1) throws Exception;

    void channelUnregistered(ChannelHandlerContext var1) throws Exception;

    void channelActive(ChannelHandlerContext var1) throws Exception;

    void channelInactive(ChannelHandlerContext var1) throws Exception;

    void channelRead(ChannelHandlerContext var1, Object var2) throws Exception;

    void channelReadComplete(ChannelHandlerContext var1) throws Exception;

    void userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;

    void channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;

    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
}

 

  Outbound 事件傳播方法有:

 

public interface ChannelOutboundHandler extends ChannelHandler {
    void bind(ChannelHandlerContext var1, SocketAddress var2, ChannelPromise var3) throws Exception;

    void connect(ChannelHandlerContext var1, SocketAddress var2, SocketAddress var3, ChannelPromise var4) throws Exception;

    void disconnect(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;

    void close(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;

    void deregister(ChannelHandlerContext var1, ChannelPromise var2) throws Exception;

    void read(ChannelHandlerContext var1) throws Exception;

    void write(ChannelHandlerContext var1, Object var2, ChannelPromise var3) throws Exception;

    void flush(ChannelHandlerContext var1) throws Exception;
}

  大家應該發現了規律:inbound 類似於是事件回調(響應請求的事件),而outbound 類似於主動觸發(發起請求的事件)。注意,如果我們捕獲了一個事件,並且想讓這個事件繼續傳遞下去,那麼需要調用Context 對應的傳播方法 fireXXX,例如:

 

public class MyInboundHandler extends ChannelInboundHandlerAdapter {
  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    System.out.println("連接成功");
    ctx.fireChannelActive();
  }
}

6.1 Outbound 事件傳播方式

  Outbound 事件都是請求事件(request event),即請求某件事情的發生,然後通過Outbound 事件進行通知。Outbound 事件的傳播方向是tail -> customContext -> head。我們接下來以connect 事件爲例,分析一下Outbound 事件的傳播機制。首先,當用戶調用了Bootstrap 的connect()方法時,就會觸發一個Connect 請求事件,我們就發現AbstractChannel 的connect()其實由調用了DefaultChannelPipeline 的connect()方法:

public ChannelFuture connect(SocketAddress remoteAddress) {
    return pipeline.connect(remoteAddress);
}

  而pipeline.connect()方法的實現如下:

public final ChannelFuture connect(SocketAddress remoteAddress) {
        return tail.connect(remoteAddress);
    }

  可以看到,當outbound 事件(這裏是connect 事件)傳遞到Pipeline 後,它其實是以tail 爲起點開始傳播的。而tail.connect()其實調用的是AbstractChannelHandlerContext 的connect()方法

 

public ChannelFuture connect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

        final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);return promise;
    }

 

  findContextOutbound()方法顧名思義,它的作用是以當前Context 爲起點,向Pipeline 中的Context 雙向鏈表的前端尋找第一個outbound 屬性爲true 的Context(即關聯ChannelOutboundHandler 的Context),然後返回。findContextOutbound()方法代碼實現如下:

 

private AbstractChannelHandlerContext findContextOutbound(int mask) {
        AbstractChannelHandlerContext ctx = this;
        do {
            ctx = ctx.prev;
        } while ((ctx.executionMask & mask) == 0);
        return ctx;
    }

 

  當我們找到了一個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);
        }
    }

 

  如果用戶沒有重寫ChannelHandler 的connect()方法,那麼會調用ChannelOutboundHandlerAdapter 的connect()實現

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

  我們看到,ChannelOutboundHandlerAdapter 的connect()僅僅調用了ctx.connect(),而這個調用又回到了:Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect這樣的循環中,直到connect 事件傳遞到DefaultChannelPipeline 的雙向鏈表的頭節點,即head 中。爲什麼會傳遞到head 中呢?回想一下,head 實現了ChannelOutboundHandler,因此它的outbound 屬性是true。因爲head 本身既是一個ChannelHandlerContext,又實現了ChannelOutboundHandler 接口,因此當connect()消息傳遞到head 後,會將消息轉遞到對應的ChannelHandler 中處理,而head 的handler()方法返回的就是head 本身:

public ChannelHandler handler() {
      return this;
}

  因此最終connect()事件是在head 中被處理。head 的connect()事件處理邏輯如下:

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

  到這裏, 整個connect()請求事件就結束了。下圖中描述了整個connect()請求事件的處理過程:

  我們僅僅以connect()請求事件爲例,分析了outbound 事件的傳播過程,但是其實所有的outbound 的事件傳播都遵循着一樣的傳播規律,小夥伴們可以試着分析一下其他的outbound 事件,體會一下它們的傳播過程。

6.2 Inbound 事件傳播方式

  Inbound 事件和Outbound 事件的處理過程是類似的,只是傳播方向不同。Inbound 事件是一個通知事件,即某件事已經發生了,然後通過Inbound 事件進行通知。Inbound 通常發生在Channel的狀態的改變或IO 事件就緒。Inbound 的特點是它傳播方向是head -> customContext -> tail。上面我們分析了connect()這個Outbound 事件,那麼接着分析connect()事件後會發生什麼Inbound 事件,並最終找到Outbound 和Inbound 事件之間的聯繫。當connect()這個Outbound 傳播到unsafe 後,其實是在AbstractNioUnsafe的connect()方法中進行處理的:

 

public final void connect(
                final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
       boolean wasActive = isActive();
       if (doConnect(remoteAddress, localAddress)) {
            fulfillConnectPromise(promise, wasActive);
       } else {
       }
}

 

  在AbstractNioUnsafe 的connect()方法中,首先調用doConnect()方法進行實際上的Socket 連接,當連接上後會調用fulfillConnectPromise()方法

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
            if (!wasActive && active) {
                pipeline().fireChannelActive();
            }
        }

  我們看到,在fulfillConnectPromise()中,會通過調用pipeline().fireChannelActive()方法將通道激活的消息(即Socket 連接成功)發送出去。而這裏,當調用pipeline.fireXXX 後,就是Inbound 事件的起點。因此當調用pipeline().fireChannelActive()後,就產生了一個ChannelActive Inbound 事件,我們就從這裏開始看看這個Inbound事件是怎麼傳播的?

public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

  果然, 在fireChannelActive()方法中,調用的是head.invokeChannelActive(),因此可以證明Inbound 事件在Pipeline中傳輸的起點是head。那麼,在head.invokeChannelActive()中又做了什麼呢?

 

static void invokeChannelActive(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
    }

 

  接下去的調用流程是:

private void invokeChannelActive() {
        if (this.invokeHandler()) {
            try {
                ((ChannelInboundHandler)this.handler()).channelActive(this);
            } catch (Throwable var2) {
                this.notifyHandlerException(var2);
            }
        } else {
            this.fireChannelActive();
        }

}
public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();
            this.readIfIsAutoRead();
        }
public ChannelHandlerContext fireChannelActive() {
        AbstractChannelHandlerContext next = this.findContextInbound();
        invokeChannelActive(next);
        return this;
    }

 

  上面的代碼應該很熟悉了。回想一下在Outbound 事件(例如connect()事件)的傳輸過程中時,我們也有類似的操作:

  1. 首先調用findContextInbound(),從Pipeline 的雙向鏈表中中找到第一個屬性inbound 爲true 的Context,然後將其返回。
  2. 調用Context 的invokeChannelActive()方法.

  invokeChannelActive()方法源碼如下:

 

private void invokeChannelActive() {
        if (invokeHandler()) {
            try {
                ((ChannelInboundHandler) handler()).channelActive(this);
            } catch (Throwable t) {
                notifyHandlerException(t);
            }
        } else {
            fireChannelActive();
        }
    }

 

  這個方法和Outbound 的對應方法(如:invokeConnect()方法)如出一轍。與Outbound 一樣,如果用戶沒有重寫channelActive() 方法,那就會調用ChannelInboundHandlerAdapter 的channelActive()方法:

public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }

  同樣地, 在ChannelInboundHandlerAdapter 的channelActive()中,僅僅調用了ctx.fireChannelActive()方法,因此就會進入Context.fireChannelActive() -> Connect.findContextInbound() -> nextContext.invokeChannelActive() ->nextHandler.channelActive() -> nextContext.fireChannelActive()這樣的循環中。同理,tail 本身既實現了ChannelInboundHandler 接口,又實現了ChannelHandlerContext 接口,因此當channelActive()消息傳遞到tail 後,會將消息轉遞到對應的ChannelHandler 中處理,而tail 的handler()返回的就是tail 本身:

public ChannelHandler handler() {
            return this;
        }

  因此channelActive Inbound 事件最終是在tail 中處理的,我們看一下它的處理方法:

public void channelActive(ChannelHandlerContext ctx) throws Exception {
        }

  TailContext 的channelActive()方法是空的。如果大家自行查看TailContext 的Inbound 處理方法時就會發現,它們的實現都是空的。可見,如果是Inbound,當用戶沒有實現自定義的處理器時,那麼默認是不處理的。下圖描述了Inbound事件的傳輸過程:

6.3 Pipeline 事件傳播小結

  Outbound 事件總結:

  • Outbound 事件是請求事件(由connect()發起一個請求,並最終由unsafe 處理這個請求)。
  • Outbound 事件的發起者是Channel。
  • Outbound 事件的處理者是unsafe。
  • Outbound 事件在Pipeline 中的傳輸方向是tail -> head。
  • 在ChannelHandler 中處理事件時,如果這個Handler 不是最後一個Handler,則需要調用ctx 的方法(如:ctx.connect()方法)將此事件繼續傳播下去。如果不這樣做,那麼此事件的傳播會提前終止。
  • Outbound 事件流:Context.OUT_EVT() -> Connect.findContextOutbound() -> nextContext.invokeOUT_EVT()-> nextHandler.OUT_EVT() -> nextContext.OUT_EVT()

  

Inbound 事件總結:

  • Inbound 事件是通知事件,當某件事情已經就緒後,通知上層。
  • Inbound 事件發起者是unsafe。
  • Inbound 事件的處理者是Channel,如果用戶沒有實現自定義的處理方法,那麼Inbound 事件默認的處理者是TailContext,並且其處理方法是空實現。Inbound 事件在Pipeline 中傳輸方向是head -> tail。
  • 在ChannelHandler 中處理事件時,如果這個Handler 不是最後一個Handler,則需要調用ctx.fireIN_EVT()事件(如:ctx.fireChannelActive()方法)將此事件繼續傳播下去。如果不這樣做,那麼此事件的傳播會提前終止。
  • Outbound 事件流:Context.fireIN_EVT() -> Connect.findContextInbound() -> nextContext.invokeIN_EVT() ->nextHandler.IN_EVT() -> nextContext.fireIN_EVT().

  outbound 和inbound 事件設計上十分相似,並且Context 與Handler 直接的調用關係也容易混淆,因此我們在閱讀這裏的源碼時,需要特別的注意。

7、Handler 的各種姿勢

  ChannelHandlerContext

  每個ChannelHandler 被添加到ChannelPipeline 後,都會創建一個ChannelHandlerContext 並與之創建的ChannelHandler 關聯綁定。ChannelHandlerContext 允許ChannelHandler 與其他的ChannelHandler 實現進行交互。ChannelHandlerContext 不會改變添加到其中的ChannelHandler,因此它是安全的。下圖描述了ChannelHandlerContext、ChannelHandler、ChannelPipeline 的關係:

 

8、Channel 的生命週期:

  Netty 有一個簡單但強大的狀態模型,並完美映射到ChannelInboundHandler 的各個方法。下面是Channel 生命週期中四個不同的狀態:

  1. channelUnregistered() Channel已創建,還未註冊到一個EventLoop上
  2. channelRegistered() Channel已經註冊到一個EventLoop上
  3. channelActive() Channel是活躍狀態(連接到某個遠端),可以收發數據
  4. channelInactive() Channel未連接到遠端

  一個Channel 正常的生命週期如下圖所示。隨着狀態發生變化相應的事件產生。這些事件被轉發到ChannelPipeline中的ChannelHandler 來觸發相應的操作。

 

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