Netty事件流程分析補充

pipeline責任鏈流程分析

責任鏈創建流程
  • 入口程序
// 責任鏈的創建是在Channel的初始化的時候進行的
// AbstractChannel.java
protected AbstractChannel(Channel parent) {
  // 如果當前爲服務端的channel,則parent=null
  this.parent = parent;
  // 創建channelId
  id = newId();
  // 使用NioServerSocketChannel父類的AbstractNioMessageChannel下的NioMessageUnsafe
  // 使用NioSocketChannel父類的AbstractNioByteChannel下的AbstractNioUnsafe
  unsafe = newUnsafe();
  // 創建channel的責任鏈,DefaultChannelPipeline
  pipeline = newChannelPipeline();
}

// 創建默認的責任鏈實例對象
 protected DefaultChannelPipeline newChannelPipeline() {
   return new DefaultChannelPipeline(this);
 }
  • Channel的類關係圖
    在這裏插入圖片描述

通過源碼與類圖可知,不論是服務端Channel的創建還是客戶端Channel的創建,其默認的pipeline均通過AbstractChannel的構造方法來初始化一個責任鏈實例,且默認爲DefaultChannelPipeline,接下來我們來看下責任鏈創建的流程

  • 創建源碼分析
// DefaultChannelPipeline.java
protected DefaultChannelPipeline(Channel channel) {
  // 當前的責任鏈保存對應的channel信息
  this.channel = ObjectUtil.checkNotNull(channel, "channel");
  
  // channel在整個責任鏈處理正常返回的成功結果對象Future
  succeededFuture = new SucceededChannelFuture(channel, null);
  
  // 對channel在整個責任鏈處理添加監聽,負責異常的捕獲
  voidPromise =  new VoidChannelPromise(channel, true);

 // 創建上下文對象,每個上下文對象都包含當前pipeline實例對象
  tail = new TailContext(this);
  head = new HeadContext(this);

  // 在邏輯結構上通過雙端鏈表的方式存儲上文對象
  head.next = tail;
  tail.prev = head;
}


// 對於HeadContext與TailContext特殊上下文的創建
// 上下文創建
// AbstractChannelHandlerContext.java
HeadContext(DefaultChannelPipeline pipeline) {
  super(pipeline, null, HEAD_NAME, HeadContext.class);
  
  // 根據channel的類型來區分,服務端爲NioMessageUnsafe
  // 客戶端爲NioSocketChannelUnsafe
  unsafe = pipeline.channel().unsafe();

  // 保證handler調用方法的順序,可以理解爲handler執行的生命週期,通過狀態機來控制生命週期
  setAddComplete();
}

 TailContext(DefaultChannelPipeline pipeline) {
   super(pipeline, null, TAIL_NAME, TailContext.class);
   setAddComplete();
 }

// 對於Netty責任鏈使用的EventLoop是屬於有序的執行器,爲了保證handlerAdd與handlerRemove的執行存在先後關係,通過以下的狀態機來控制,即handler方法執行的生命週期保證,如果EventLoop不保證有序的話,只需要通過ADD_COMPLETE或者REMOVE_COMPLETE來告知方法是否被調用即可
// 初始化狀態,創建責任鏈的時候上下文的handlerState默認爲初始化,表示handlerAdd/handlerRemove均沒有被調用
private static final int INIT = 0;
// handlerAdded即將被調用(實際還沒有調用,準備就緒,可以被調用),一般是在不需要保證有序的情況下
private static final int ADD_PENDING = 1;
// handlerAdd已經被調用
private static final int ADD_COMPLETE = 2;
// handlerRemoved已經被調用
private static final int REMOVE_COMPLETE = 3;

相比其他的handler的上下文創建,HeadContext與TailContext在構建方法中多了一個動作setAddComplete(),主要目的是由於雙端鏈表的head與tail都是在初始化channel的時候構建而不是通過addLast或者是addFirst的方式構建,爲了保證handler方法執行的有序性,於是在構建上下文的時候多添加一個步驟,接下來我們可以看到普通的handler添加方式,會在addLast中也調用上述setAddComplete()相應的方法執行.

  • 創建流程
    在這裏插入圖片描述
添加handler流程
  • 程序入口代碼
// 獲取當前的責任鏈pipeline
Pipeline pipeline = channel.pipeline();
// 添加handler,這裏以特殊的initHandler添加爲準來說明,摘錄啓動類的init方法
pipeline.addLast(new ChannelInitializer<Channel>() {
  // ChannelInitializer是一個特殊的入站事件,添加到channel中的pipeline中
  //  一旦channel已經註冊到EventLoop中就會觸發執行
  @Override
  public void initChannel(final Channel ch) {
    final ChannelPipeline pipeline = ch.pipeline();
    // 獲取服務端channel的handler處理類
    ChannelHandler handler = config.handler();
    if (handler != null) {
      pipeline.addLast(handler);
    }

    ch.eventLoop().execute(new Runnable() {
      @Override
      public void run() {
        pipeline.addLast(new ServerBootstrapAcceptor(
          ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
      }
    });
  }
});
  • addLast()源代碼
// DefaultChannelPipeline.java
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
  final AbstractChannelHandlerContext newCtx;
  synchronized (this) {
    checkMultiplicity(handler);
		// 創建一個上下文對象,默認爲DefaultChannelHandlerContext
    newCtx = newContext(group, filterName(name, handler), handler);
		
    // 將上下文對象添加到責任鏈尾部
    addLast0(newCtx);

    if (!registered) {
      // channel還沒有註冊,設置當前handler處於等待狀態
      newCtx.setAddPending();
      // 將其添加到等待鏈表的尾部中
      callHandlerCallbackLater(newCtx, true);
      return this;
    }

    EventExecutor executor = newCtx.executor();
    if (!executor.inEventLoop()) {
      // 當前線程持有的eventloop非獨佔,需要將其添加到任務隊列中
      callHandlerAddedInEventLoop(newCtx, executor);
      return this;
    }
  }
  // 核心方法
  callHandlerAdded0(newCtx);
  return this;
}

// callHandlerAdded0下會執行方法
void callHandlerAdded0(){
  ctx.callHandlerAdded();
}

// AbstractChannelContext.java
final void callHandlerAdded() throws Exception {
  // 可以看到在執行handlerAdd方法之前會調用setAddComplete方法
  if (setAddComplete()) {
    handler().handlerAdded(this);
  }
}

// 最終會當channel完成註冊的時候會調用handlerAdd方法,而ChannelInitial的handlerAdd方法如下:
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  if (ctx.channel().isRegistered()) {
    // 調用initChannel方法
    if (initChannel(ctx)) {
      // 完成channel的初始化鏈會將當前實例移除
      removeState(ctx);
    }
  }
}

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
  if (initMap.add(ctx)) { // Guard against re-entrance.
    try {
      // 模板(鉤子hook)方法,也就是我們上述的入口程序添加initHandler重載的initChannel方法
      initChannel((C) ctx.channel());
    } catch (Throwable cause) {
      exceptionCaught(ctx, cause);
    } finally {
      // 最後會在pipeline鏈中刪除當前實例
      ChannelPipeline pipeline = ctx.pipeline();
      if (pipeline.context(this) != null) {
         pipeline.remove(this);
      }
    }
    return true;
  }
  return false;
}
  • 添加handler的流程
    在這裏插入圖片描述
    通過上述流程圖可知,初始化initHandler在channel註冊之後責任鏈pipeline會將initHandler從中移除並將用戶添加的handler添加到責)
責任鏈銷燬流程

在查看責任鏈銷燬的源代碼之前,我們是否應該先思考是什麼樣的動作行爲會銷燬pipeline責任鏈,如果想不出來,我們換另一個思路,責任鏈是什麼時候創建的,根據上述的分析,責任鏈pipeline是創建channel的時候創建的,那麼我們是否可以推測銷燬時機是不是在channel銷燬的時候對應的pipeline也將會銷燬呢?那麼channel是在什麼時候銷燬的呢,我們可以考慮一個已熟知的數據庫連接connection創建與銷燬,什麼時候需要銷燬connection,自然是調用close()方法的時候,這個時候connection要麼等待被JVM回收要麼就是存放到回收資源池中,對此關於責任鏈的銷燬分析如下

  • 入口程序
// 服務端channel銷燬,也就是服務端channel調用close()關閉服務
// 對於客戶端channel,自然是斷開與服務端的連接
// channel的關閉是屬於事件觸發,於是我們直接定位到事件輪詢器下的方法processSelectedKey,該方法負責處理就緒事件
// 對於NIO的api,每個socket的就緒事件都存儲在SelectionKey中,如果channel銷燬,當前的SelectionKey也將會在銷燬之前取消事件監聽
// NioEventLoop.javas
void processSelectedKey(){
  if (!k.isValid()) {
    // ...
    unsafe.close(unsafe.voidPromise());
  }
  try{
    
  }catch (CancelledKeyException ignored) {
    unsafe.close(unsafe.voidPromise());
  }
}
  • 源碼分析
// 通過代碼定位最終會執行以下代碼塊
// AbstractChannel.java
try {
  // 調用java餓的socket進行關閉
  doClose0(promise);
} finally {
  // Call invokeLater so closeAndDeregister is executed in the EventLoop again!
  invokeLater(new Runnable() {
    @Override
    public void run() {
      if (outboundBuffer != null) {
        // Fail all the queued messages
        outboundBuffer.failFlushed(cause, notify);
        outboundBuffer.close(closeCause);
      }
      // 將channel事件傳播到責任鏈中
      fireChannelInactiveAndDeregister(wasActive);
    }
  });
}
  • 銷燬流程
    在這裏插入圖片描述

IO事件流程分析

監聽連接事件

基於上一篇的Netty事件流程分析中的事件輪詢說明可知,服務端監聽的事件變化以及事件轉發處理都在EventLoop.run方法中,對此,我們詳細看下Netty的服務端是如何接收並處理客戶端的連接事件以及對應的事件流程是如何的.

  • Netty框架下的核心事件輪詢run方法源代碼
// 僅貼出部分核心代碼
// NioServerSocketChannel.java
run(){
  // 服務端不斷輪詢監聽事件
  for (;;) {
    
    // 執行select操作
    if (!hasTasks()) {
      strategy = select(curDeadlineNanos);
    }
    
    // 處理就緒事件
    processSelectedKeys();
    
    // 處理任務隊列中的任務
    ranTasks = runAllTasks();
  }
}

// 既然關注ACCEPT事件,這個時候我們需要知道服務端Channel在創建註冊並綁定的時候初始化handler並將Acceptor添加到handler中,對此我們追蹤下bind方法,最後查閱代碼到init方法,其代碼如下:
void init(Channel channel){
  // 在先前分析可知,這裏已經完成了channel的創建,且此時channel爲NioServerSocketChannel事件
  ChannelPipeline p = channel.pipeline();
  p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(final Channel ch) {
      final ChannelPipeline pipeline = ch.pipeline();
      // 獲取服務端channel的handler處理類
      ChannelHandler handler = config.handler();
      if (handler != null) {
        pipeline.addLast(handler);
      }

      ch.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
          pipeline.addLast(new ServerBootstrapAcceptor(
            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
      });
    }
  });
}
  • ServerBootstrapAcceptor監聽連接的源碼

在上一篇的事件分析中,我們對服務端的一個綁定事件進行了分析(包括服務端channel的創建/初始化與註冊,客戶端的channel基本與服務端一致,這裏也不再詳細說明),最終的監聽連接的事件將會調用unsafe.read()方法並且會將事件通過責任鏈pipeline傳播到channelRead方法下,對此,我們關注Acceptor處理連接可以通過查看handler實現的channelRead()方法即可.

// ServerBootstrapAcceptor.java
public void channelRead(ChannelHandlerContext ctx, Object msg) {
  final Channel child = (Channel) msg;
  // 將處理客戶端channel的handler添加到責任鏈pipeline中
  child.pipeline().addLast(childHandler);

  setChannelOptions(child, childOptions, logger);
  setAttributes(child, childAttrs);

  try {
    // 客戶端channel註冊到EventLoop,註冊流程與之前服務端註冊流程基本一致,這裏不再詳述
    childGroup.register(child).addListener(new ChannelFutureListener() {
      @Override
      public void operationComplete(ChannelFuture future) throws Exception {
        if (!future.isSuccess()) {
          forceClose(child, future.cause());
        }
      }
    });
  } catch (Throwable t) {
    forceClose(child, t);
  }
}
  • 監聽連接的事件流程

通過上述源碼以及責任鏈添加handler的流程可知,當服務端channel完成之後,pipeline鏈將會是head -> handlers -> ServerBootstrapAcceptor -> tail,因而我們根據現有的線索以及上述源碼,對監聽連接事件流程繪製如下:
在這裏插入圖片描述

請求讀取事件
  • 入口程序
// NioEventLoop.run()方法
// 在這裏關注的讀寫事件是NioSocketChannel
void processSelectedKey(){
  if ((readyOps & SelectionKey.OP_WRITE) != 0) {
     ch.unsafe().forceFlush();
  }

  // 對於處理請求,我們需要關注NioSocketChannel處理讀取事件流程
  if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
     unsafe.read();
  }
}
  • NioSocketChannel的類圖組件

在這裏插入圖片描述

  • 讀取事件實現
// 根據上述代碼以及類圖可知,NioSocketChannel使用初始化safe實現類爲NioSocketChannelUnsafe
// 於是查看其的源碼實現,但是方法NioSocketChannelUnsafe並沒有read方法,而是在NioByteUnsafe類中,因而找到對應的read方法,摘錄部分核心代碼如下:

void read() {
  try{
      do{
      // 從socket中讀取數據
      doReadBytes(byteBuff);

      // 傳播讀取事件到責任鏈中
      pipeline.fireChannelRead(byteBuf);
    }while(continueReading())
      
      // 傳播讀取完成事件到責任鏈中
    	pipeline.fireChannelReadComplete();
    	if(close){
      	closeOnRead(pipeline);
    	}
  }catch (Throwable t) {
    //傳播事件異常以及userEventTriggered
    handleReadException(pipeline, byteBuf, t, close, allocHandle);
  } finally {
    if (!readPending && !config.isAutoRead()) {
      // 取消讀取操作
      removeReadOp();
    }
  }
}

  • 請求讀取流程示意圖

在這裏插入圖片描述

數據寫出事件
  • 入口程序代碼

對於寫出操作,主要在開發者實現Channel的channelRead或者channelReadCompleted方法下手動調用方法執行寫出操作,即入口程序代碼如下:

// 寫出操作的觸發點是在某個handler下的channelRead方法下手動執行write或者writeAndFlush方法
void handlerRead(AbstractHandlerContext ctx, Object msg){
  // 執行寫操作的流程說明責任鏈執行當前入站事件handler已經是最後一個,從當前handler的上下文對象開始執行出站事件
  ctx.writeAndFlush(msg);
}

可以看到,寫出操作是調用上下文對象的寫出操作,基於這個線索,先來查看上下文對象的類圖設計關係.

  • 上下文對象類設計圖

在添加handler的時候,我們通過源碼看到addLast方法內部的實現中,默認上下文對象爲DefaultChannelHandlerContext,於是對應的類圖關係如下:

在這裏插入圖片描述

通過上述的類圖可知,上下文對象有一個父類AbstractChannelHandlerContext來實現通用的方法,同時上下文對象具備出入站事件,因此我們可以在handler中對接收到的上下文對象ctx手動處理出站或入站事件的傳播,對此當我們調用ctx.writeAndFlush()方法的時候也將會觸發對應的一個handler觸發事件(通過源碼分析是屬於出站事件)

  • 源碼分析
// AbstractChannelHandlerContext.java
void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
  if (invokeHandler()) {
    // 負責將寫出的數據存儲到OutboundBuffer緩衝區
    invokeWrite0(msg, promise);
    // 執行刷新操作
    invokeFlush0();
  } else {
    writeAndFlush(msg, promise);
  }
}

// 通過責任鏈傳播寫出事件,
private void invokeWrite0(Object msg, ChannelPromise promise) {
  try {
    ((ChannelOutboundHandler) handler()).write(this, msg, promise);
  } catch (Throwable t) {
    notifyOutboundHandlerException(t, promise);
  }
}

// 通過責任鏈傳播刷新事件
private void invokeFlush0() {
  try {
    ((ChannelOutboundHandler) handler()).flush(this);
  } catch (Throwable t) {
    notifyHandlerException(t);
  }
}

// 根據責任鏈執行流程可知,最終會執行headContext下write以及flush的方法
// HeadContext.java
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
  unsafe.write(msg, promise);
}

@Override
public void flush(ChannelHandlerContext ctx) {
  unsafe.flush();
}

// 最後會調用AbstractUnsafe的write以及flush方法(這裏就不貼出代碼.直接查看流程圖)
  • 寫出事件執行流程圖
    在這裏插入圖片描述

Channel與Handler生命週期小結

Channel的生命週期

結合上一篇的事件流程分析,channel主要經歷創建 - 初始化 - 註冊 - 事件接收處理 - 銷燬過程,其經歷的流程如下:

在這裏插入圖片描述

  • Channel生命週期

在這裏插入圖片描述

Handler的生命週期
  • handler類設計圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-oZ8Xlki2-1588041582625)(https://raw.githubusercontent.com/xiaokunliu/xiaokunliu.github.io/feature/writing/websites/zimages/netty/event2/handler_class.jpg)]

  • Handler生命週期

根據上述類圖可知,ChannelHandler定義了handlerAdd以及handlerRemoved並且結合上述責任鏈的流程分析可知,handler是通過上下文對象來傳播事件並回調方法,並且上下文對象通過handlerState以及channel事件流程來保證上述方法執行的先後順序,從而保證handler的執行生命週期

在這裏插入圖片描述

Handler方法回調與生命週期聯繫

最後,根據前面分析的事件流程以及上述的Channel生命週期,對Channel與handler執行回調方法作一個小結,如下:

在這裏插入圖片描述

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