Netty源碼分析之Bootstrap啓動過程分析

Bootstrap

 Netty的源碼紛繁複雜,Bootstrap/ServerBootstrap 類入手,分析Netty程序的初始化和啓動的流程.
 Bootstrap是Netty提供的一個便利的工廠類, 我們可以通過它來完成Netty的客戶端或服務器端的Netty初始化. 下面以Netty源碼例子中的Echo服務器作爲例子, 從客戶端和服務器端分別分析一下Netty 的程序是如何啓動的.

客戶端部分

連接源碼

 首先,從Netty官網中的ehco例子源碼EchoClient.java 的客戶端部分的啓動代碼:

private static void doConnect0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

    // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
    // the pipeline in its channelRegistered() implementation.
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                if (localAddress == null) {
                    channel.connect(remoteAddress, promise);
                } else {
                    channel.connect(remoteAddress, localAddress, promise);
                }
                promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

從上面的客戶端代碼雖然簡單, 但是卻展示了 Netty 客戶端初始化時所需的所有內容:

  • EventLoopGroup: 不論是服務器端還是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 指定了 NioEventLoopGroup, 表示一個 NIO 的EventLoopGroup.
  • ChannelType: 指定 Channel 的類型. 因爲是客戶端, 因此使NioSocketChannel.
  • Handler: 設置數據的處理器.
    下面深入代碼, 看一下客戶端通過 Bootstrap 啓動後, 都做了哪些工作.

NioSocketChannel 的初始化過程

 在Netty中,Channel是一個Socket的抽象,它爲用戶提供了關於Socket狀態(是否是連接還是斷開)以及對Socket的讀寫等操作. 每當Netty建立了一個連接後, 都會有一個對應的Channel實例. NioSocketChannel的類層次結構如下圖(1):

圖1:NioSocketChannel的類層次結構

 Channel 的初始化過程分析如下。

ChannelFactory和Channel 類型的確定

 除了TCP協議以外, Netty還支持很多其他的連接協議, 並且每種協議還有 NIO(異步IO) 和OIO(Old-IO, 即傳統的阻塞 IO) 版本的區別. 不同協議不同的阻塞類型的連接都有不同的Channel類型與之對應下面是一些常用的Channel類型:

  • NioSocketChannel, 代表異步的客戶端 TCP Socket 連接.
  • NioServerSocketChannel, 異步的服務器端 TCP Socket 連接.
  • NioDatagramChannel, 異步的 UDP 連接
  • NioSctpChannel, 異步的客戶端 Sctp 連接.
  • NioSctpServerChannel, 異步的 Sctp 服務器端連接.
  • OioSocketChannel, 同步的客戶端 TCP Socket 連接.
  • OioServerSocketChannel, 同步的服務器端 TCP Socket 連接.
  • OioDatagramChannel, 同步的 UDP 連接
  • OioSctpChannel, 同步的 Sctp 服務器端連接.
  • OioSctpServerChannel, 同步的客戶端 TCP Socket 連接.

 那麼如何設置所需要的Channel的類型的呢? 答案是channel() 方法的調用. 回想一下在客戶端連接代碼的初始化Bootstrap中, 會調用channel()方法, 傳入NioSocketChannel.class, 這個方法其實就是初BootstrapChannelFactory:

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new BootstrapChannelFactory<C>(channelClass));
}

 而BootstrapChannelFactory實現了ChannelFactory接口,它提供了唯一的方法,即 newChannel.ChannelFactory,顧名思義,就是產生Channel的工廠類.進到BootstrapChannelFactory.newChannel中,我們看到其實現代碼如下:

@Override
public T newChannel() {
    // 刪除 try 塊
    return clazz.newInstance();
}

根據上面代碼的提示, 我們就可以確定:

  • Bootstrap 中的 ChannelFactory 的實現是 BootstrapChannelFactory
  • 生成的 Channel 的具體類型是 NioSocketChannel.

 Channel的實例化過程, 其實就是調用的ChannelFactory#newChannel方法, 而實例化的Channel的具體的類型又是和在初始化Bootstrap時傳入的channel() 方法的參數相關. 因此對於我們這個例子中的客戶端的Bootstrap而言, 生成的Channel實例就是NioSocketChannel.

Channel 實例化

 前面已經知道了如何確定一個Channel的類型, 並且瞭解到Channel是通過工廠方法ChannelFactory#newChannel()來實例化的,那麼ChannelFactory#newChannel()方法在哪裏調用呢?繼續跟蹤, 我們發現其調用鏈是:

Bootstrap.connect->Bootstrap.doConnect ->AbstractBootstrap.initAndRegister

 在AbstractBootstrap.initAndRegister中就調用channelFactory().newChannel()來獲取一個新的 NioSocketChannel 實例, 其源碼如下:

final ChannelFuture initAndRegister() {
    // 去掉非關鍵代碼
    final Channel channel = channelFactory().newChannel();
    init(channel);
    ChannelFuture regFuture = group().register(channel);
}

 在newChannel中,通過類對象的newInstance來獲取一個新Channel實例,因而會調用NioSocketChannel的默認構造器.NioSocketChannel默認構造器代碼如下:

public NioSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

這裏的代碼比較關鍵, 我們看到,在這個構造器中,會調用newSocket來打開一個新的Java NIO SocketChannel:

private static SocketChannel newSocket(SelectorProvider provider) {
    ...
    return provider.openSocketChannel();
}

 接着會調用父類, 即 AbstractNioByteChannel 的構造器:

AbstractNioByteChannel(Channel parent, SelectableChannel ch)

 並傳入參數 parent 爲 null, ch爲剛纔使用newSocket 創建的 Java NIO SocketChannel, 因此生成的 NioSocketChannel 的 parent channel 是空的.

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);
}

 接着會繼續調用父類AbstractNioChannel的構造器,並傳入了參數 readInterestOp = SelectionKey.OP_READ:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    // 省略 try 塊
    // 配置 Java NIO SocketChannel 爲非阻塞的.
    ch.configureBlocking(false);
}

 然後繼續調用父類 AbstractChannel 的構造器:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    unsafe = newUnsafe();
    pipeline = new DefaultChannelPipeline(this);
}

 到這裏, 一個完整的 NioSocketChannel 就初始化完成了, 我們可以稍微總結一下構造一個 NioSocketChannel 所需要做的工作:

  • 調用 NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打開一個新的 Java NIO SocketChannel
  • AbstractChannel(Channel parent) 中初始化 AbstractChannel 的屬性:
  • parent 屬性置爲 null
  • unsafe 通過newUnsafe() 實例化一個 unsafe 對象, 它的類型是 AbstractNioByteChannel.NioByteUnsafe 內部類
  • pipeline 是 new DefaultChannelPipeline(this) 新創建的實例. 這裏體現了:Each channel has its own pipeline and it is created automatically when a new channel is created.
  • AbstractNioChannel 中的屬性:
  • SelectableChannel ch 被設置爲 Java SocketChannel, 即 NioSocketChannel#newSocket 返回的 Java NIO SocketChannel.
  • readInterestOp 被設置爲 SelectionKey.OP_READ
  • SelectableChannel ch 被配置爲非阻塞的 ch.configureBlocking(false)
  • NioSocketChannel 中的屬性:
  • SocketChannelConfig config = new NioSocketChannelConfig(this, socket.socket())

關於unsafe字段的初始化

 我們簡單地提到了, 在實例化 NioSocketChannel 的過程中,會在父類 AbstractChannel的構造器中, 調用 newUnsafe() 來獲取一個unsafe實例. 那麼 unsafe 是怎麼初始化的呢? 它的作用是什麼?其實 unsafe 特別關鍵, 它封裝了對 Java 底層 Socket 的操作, 因此實際上是溝通 Netty 上層和 Java 底層的重要的橋樑那麼就來看一下 Unsafe 接口所提供的方法吧:

interface Unsafe {
    SocketAddress localAddress();
    SocketAddress remoteAddress();
    void register(EventLoop eventLoop, ChannelPromise promise);
    void bind(SocketAddress localAddress, ChannelPromise promise);
   void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
    void disconnect(ChannelPromise promise);
    void close(ChannelPromise promise);
    void closeForcibly();
    void deregister(ChannelPromise promise);
    void beginRead();
    void write(Object msg, ChannelPromise promise);
    void flush();
    ChannelPromise voidPromise();
    ChannelOutboundBuffer outboundBuffer();
}

 一看便知, 這些方法其實都會對應到相關的 Java 底層的 Socket 的操作.回到 AbstractChannel 的構造方法中, 在這裏調用了 newUnsafe() 獲取一個新的 unsafe 對象, 而 newUnsafe 方法在 NioSocketChannel 中被重寫了:

@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioSocketChannelUnsafe();
}

 NioSocketChannel.newUnsafe方法會返回一個NioSocketChannelUnsafe 實例. 從這裏我們就可以確定了, 在實例化的NioSocketChannel中的unsafe字段,其實是一個NioSocketChannelUnsafe的實例.

關於 pipeline 的初始化

 上面我們分析了一個Channel(在這個例子中是 NioSocketChannel) 的大體初始化過程, 但是我們漏掉了一個關鍵的部分, 即 ChannelPipeline 的初始化. 根據 Each channel has its own pipeline and it is created automatically when a new channel is created., 我們知道, 在實例化一個 Channel 時, 必然伴隨着實例化一個 ChannelPipeline. 而我們確實在 AbstractChannel 的構造器看到了 pipeline 字段被初始化爲 DefaultChannelPipeline 的實例. 那麼我們就來看一下, DefaultChannelPipeline 構造器做了哪些工作吧:

public DefaultChannelPipeline(AbstractChannel channel) {
    if (channel == null) {
        throw new NullPointerException("channel");
    }
    this.channel = channel;

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

 我們調用 DefaultChannelPipeline的構造器, 傳入了一個channel, 而這個 channel其實就是我們實例化的NioSocketChannel,DefaultChannelPipeline會將這個NioSocketChannel對象保存在channel字段中.DefaultChannelPipeline中,還有兩個特殊的字段,即head和tail:
而這兩個字段是一個雙向鏈表的頭和尾.其實在DefaultChannelPipeline中,維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表:

final class DefaultChannelPipeline implements ChannelPipeline {
   ....

    final AbstractChannel channel;

    //以 AbstractChannelHandlerContext 爲節點的雙向鏈表
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    .....
    }

這個鏈表是Netty 實現Pipeline機制的關鍵. HeadContext 的繼承層次結構如下圖2所示:

圖2:HeadContext 的繼承層次結構

TailContext 的繼承層次結構如下所示:

圖3:TailContext 的繼承層次結構

 我們可以看到,鏈表中head是一個ChannelOutboundHandler,而tail則是一個ChannelInboundHandler. 接着看一下HeadContext 的構造器:

HeadContext(DefaultChannelPipeline pipeline) {
    super(pipeline, null, HEAD_NAME, false, true);
    unsafe = pipeline.channel().unsafe();
}

 它調用了父類 AbstractChannelHandlerContext 的構造器, 並傳入參數 inbound = false, outbound = true.TailContext 的構造器與 HeadContext 的相反,它調用了父類 AbstractChannelHandlerContext 的構造器, 並傳入參數inbound=true,outbound=false.即 header 是一個 outboundHandler, 而 tail 是一個inboundHandler, 關於這一點, 大家要特別注意, 因爲在分析到 Netty Pipeline 時,我們會反覆用到inbound和outbound這兩個屬性.

關於 EventLoop 初始化

 回到最開始的EchoClient.java代碼中, 我們在一開始就實例化NioEventLoopGroup對象, 因此我們就從它的構造器中追蹤一NioEventLoopGroup 的初始化過程.首先來看一下NioEventLoopGroup的類繼承層次如下圖4:

圖4:NioEventLoopGroup的類繼承層次

 NioEventLoopGroup 有幾個重載的構造器, 不過內容都沒有什麼區別, 最終都是調用的父類MultithreadEventLoopGroup構造器:

 protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
    }

 其中有一點有意思的地方是, 如果我們傳入的線程數 nThreads 是0, 那麼 Netty 會爲我們設置默認的線程數 DEFAULT_EVENT_LOOP_THREADS, 而這個默認的線程數是怎麼確定的呢?其實很簡單, 在靜態代碼塊中, 會首先確定DEFAULT_EVENT_LOOP_THREADS 的值:

static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
}

 Netty 會首先從系統屬性中獲取 “io.netty.eventLoopThreads” 的值, 如果我們沒有設置它的話, 那麼就返回默認值: 處理器核心數 * 2.回到MultithreadEventLoopGroup構造器中, 這個構造器會繼續調用父類 MultithreadEventExecutorGroup 的構造器:

protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    // 去掉了參數檢查, 異常處理 等代碼.
    children = new SingleThreadEventExecutor[nThreads];
    if (isPowerOfTwo(children.length)) {
        chooser = new PowerOfTwoEventExecutorChooser();
    } else {
        chooser = new GenericEventExecutorChooser();
    }

    for (int i = 0; i < nThreads; i ++) {
        children[i] = newChild(threadFactory, args);
    }
}

 根據代碼, 我們就很清楚 MultithreadEventExecutorGroup 中的處理邏輯了:

  • 創建一個大小爲 nThreads 的 SingleThreadEventExecutor 數組
  • 根據 nThreads 的大小, 創建不同的 Chooser, 即如果 nThreads 是 2 的冪, 則使用 PowerOfTwoEventExecutorChooser, 反之使用 GenericEventExecutorChooser. 不論使用哪個 Chooser, 它們的功能都是一樣的, 即從 children 數組中選出一個合適的 EventExecutor 實例.
  • 調用 newChhild 方法初始化 children 數組.

 根據上面的代碼, 我們知道, MultithreadEventExecutorGroup 內部維護了一個 EventExecutor 數組, Netty 的 EventLoopGroup 的實現機制其實就建立在 MultithreadEventExecutorGroup 之上. 每當 Netty 需要一個 EventLoop 時, 會調用 next() 方法獲取一個可用的 EventLoop.上面代碼的最後一部分是 newChild 方法, 這個是一個抽象方法, 它的任務是實例化 EventLoop 對象. 我們跟蹤一下它的代碼, 可以發現, 這個方法在 NioEventLoopGroup 類中實現了, 其內容很簡單:

@Override
protected EventExecutor newChild(
        ThreadFactory threadFactory, Object... args) throws Exception {
    return new NioEventLoop(this, threadFactory, (SelectorProvider) args[0]);
}

 其實就是實例化一個 NioEventLoop 對象, 然後返回它.最後總結一下整個 EventLoopGroup 的初始化過程吧:

  • EventLoopGroup(其實是MultithreadEventExecutorGroup) 內部維護一個類型爲 EventExecutor children 數組, 其大小是 nThreads, 這樣就構成了一個線程池
  • 如果我們在實例化 NioEventLoopGroup 時, 如果指定線程池大小, 則 nThreads 就是指定的值, 反之是處理器核心數 * 2
  • MultithreadEventExecutorGroup 中會調用 newChild 抽象方法來初始化 children 數組
  • 抽象方法 newChild 是在 NioEventLoopGroup 中實現的, 它返回一個 NioEventLoop 實例.
  • NioEventLoop 屬性:
  • SelectorProvider provider 屬性: NioEventLoopGroup 構造器中通過 SelectorProvider.provider() 獲取一個 SelectorProvider
  • Selector selector 屬性: NioEventLoop 構造器中通過調用通過 selector = provider.openSelector() 獲取一個 selector 對象.

channel 的註冊過程

 在前面的分析中, 我們提到, channel 會在 Bootstrap.initAndRegister 中進行初始化, 但是這個方法還會將初始化好的 Channel 註冊到 EventGroup 中. 接下來我們就來分析一下 Channel 註冊的過程.
回顧一下 AbstractBootstrap.initAndRegister 方法:

final ChannelFuture initAndRegister() {
    // 去掉非關鍵代碼
    final Channel channel = channelFactory().newChannel();
    init(channel);
    ChannelFuture regFuture = group().register(channel);
}

 當Channel 初始化後, 會緊接着調用 group().register() 方法來註冊 Channel, 我們繼續跟蹤的話, 會發現其調用鏈如下:
AbstractBootstrap.initAndRegister -> MultithreadEventLoopGroup.register -> SingleThreadEventLoop.register -> AbstractUnsafe.register
通過跟蹤調用鏈, 最終我們發現是調用到了 unsafe 的 register 方法, 那麼接下來我們就仔細看一下 AbstractUnsafe.register 方法中到底做了什麼:

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 省略條件判斷和錯誤處理
    AbstractChannel.this.eventLoop = eventLoop;
    register0(promise);
}

 首先,將eventLoop賦值給Channel 的eventLoop屬性,而我們知道這個 eventLoop 對象其實是 MultithreadEventLoopGroup.next()方法獲取的, 根據我們前面 關於EventLoop初始化小節中, 我們可以確定next()方法返回的 eventLoop對象是 NioEventLoop 實例.register 方法接着調用了 register0 方法:

private void register0(ChannelPromise promise) {
    boolean firstRegistration = neverRegistered;
    doRegister();
    neverRegistered = false;
    registered = true;
    safeSetSuccess(promise);
    pipeline.fireChannelRegistered();
    // Only fire a channelActive if the channel has never been registered. This prevents firing
    // multiple channel actives if the channel is deregistered and re-registered.
    if (firstRegistration && isActive()) {
        pipeline.fireChannelActive();
    }
}

 register0 又調用了 AbstractNioChannel.doRegister:

@Override
protected void doRegister() throws Exception {
    // 省略錯誤處理
    selectionKey = javaChannel().register(eventLoop().selector, 0, this);
}

 javaChannel() 這個方法在前面我們已經知道了, 它返回的是一個 Java NIO SocketChannel, 這裏我們將這個 SocketChannel 註冊到與 eventLoop 關聯的 selector 上了.
我們總結一下 Channel 的註冊過程:

  • 首先在AbstractBootstrap.initAndRegister中,通過 group().register(channel), 調用 MultithreadEventLoopGroup.register 方法
  • 在MultithreadEventLoopGroup.register 中, 通過 next() 獲取一個可用的 SingleThreadEventLoop, 然後調用它的 register
  • 在 SingleThreadEventLoop.register 中, 通過 channel.unsafe().register(this, promise) 來獲取 channel 的 unsafe() 底層操作對象, 然後調用它的 register.
  • 在 AbstractUnsafe.register 方法中, 調用 register0 方法註冊 Channel
  • 在 AbstractUnsafe.register0 中, 調用 AbstractNioChannel.doRegister 方法
  • AbstractNioChannel.doRegister 方法通過 javaChannel().register(eventLoop().selector, 0, this) 將 Channel 對應的 Java NIO SockerChannel 註冊到一個 eventLoop 的 Selector 中, 並且將當前 Channel 作爲 attachment.

 總的來說, Channel 註冊過程所做的工作就是將 Channel 與對應的 EventLoop 關聯, 因此這也體現了, 在 Netty 中, 每個 Channel 都會關聯一個特定的 EventLoop, 並且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的; 當關聯好 Channel 和 EventLoop 後, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 註冊到指定的 selector 中. 通過這兩步, 就完成了 Netty Channel 的註冊過程.

handler 的添加過程

 Netty 的一個強大和靈活之處就是基於 Pipeline 的自定義 handler 機制. 基於此, 我們可以像添加插件一樣自由組合各種各樣的 handler 來完成業務邏輯. 例如我們需要處理 HTTP 數據, 那麼就可以在 pipeline 前添加一個 Http 的編解碼的 Handler, 然後接着添加我們自己的業務邏輯的 handler, 這樣網絡上的數據流就向通過一個管道一樣, 從不同的 handler 中流過並進行編解碼, 最終在到達我們自定義的 handler 中.
 既然說到這裏, 有些讀者朋友肯定會好奇, 既然這個 pipeline 機制是這麼的強大, 那麼它是怎麼實現的呢? 不過我這裏不打算詳細展開 Netty 的 ChannelPipeline 的實現機制(具體的細節會在後續的章節中展示), 我在這一小節中, 從簡單的入手, 展示一下我們自定義的 handler 是如何以及何時添加到 ChannelPipeline 中的.首先讓我們看一下如下的代碼片段:

...
.handler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         if (sslCtx != null) {
             p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
         }
         //p.addLast(new LoggingHandler(LogLevel.INFO));
         p.addLast(new EchoClientHandler());
     }
 });

 這個代碼片段就是實現了 handler 的添加功能. 我們看到, Bootstrap.handler 方法接收一個 ChannelHandler, 而我們傳遞的是一個派生於ChannelInitializer 的匿名類, 它正好也實現了 ChannelHandler 接口. 我們來看一下, ChannelInitializer 類內到底有什麼玄機:

@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);
    protected abstract void initChannel(C ch) throws Exception;

    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        initChannel((C) ctx.channel());
        ctx.pipeline().remove(this);
        ctx.fireChannelRegistered();
    }
    ...
}

 ChannelInitializer 是一個抽象類, 它有一個抽象的方法 initChannel, 我們正是實現了這個方法, 並在這個方法中添加的自定義的 handler 的. 那麼 initChannel 是哪裏被調用的呢? 答案是 ChannelInitializer.channelRegistered 方法中.
 來關注一下 channelRegistered 方法. 從上面的源碼中, 我們可以看到, 在 channelRegistered 方法中, 會調用 initChannel 方法, 將自定義的handler添加到 ChannelPipeline中, 然後調用 ctx.pipeline().remove(this) 將自己從 ChannelPipeline 中刪除. 上面的分析過程, 可以用如下圖5展示:
這裏寫圖片描述

圖5:添加ChannelInitializer

一開始, ChannelPipeline 中只有三個 handler, head, tail 和我們添加的 ChannelInitializer.接着 initChannel 方法調用後, 添加了自定義的 handler:
這裏寫圖片描述
圖6:自定義的 handler

最後將 ChannelInitializer 刪除:
這裏寫圖片描述
圖7: 刪除ChannelInitializer

 分析到這裏, 我們已經簡單瞭解了自定義的 handler 是如何添加到 ChannelPipeline 中的, 不過限於主題與篇幅的原因, 我沒有在這裏詳細展開 ChannelPipeline 的底層機制, 後面再對這個問題進行深入的探討.

客戶端連接分析

 經過上面的各種分析後, 我們大致瞭解了 Netty 初始化時, 所做的工作, 那麼接下來我們就直奔主題, 分析一下客戶端是如何發起 TCP 連接的.首先, 客戶端通過調用 Bootstrapconnect 方法進行連接.在 connect 中, 會進行一些參數檢查後, 最終調用的是 doConnect0 方法, 其實現如下:

private static void doConnect0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

    // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
    // the pipeline in its channelRegistered() implementation.
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                if (localAddress == null) {
                    channel.connect(remoteAddress, promise);
                } else {
                    channel.connect(remoteAddress, localAddress, promise);
                }
                promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

 在 doConnect0 中, 會在 event loop 線程中調用 Channel 的connect方法, 而這個Channel的具體類型是什麼呢? 我們在Channel初始化這一小節中已經分析過了, 這裏 channel 的類型就是 NioSocketChannel.進行跟蹤到channel.connect中,我們發現它調用的是 DefaultChannelPipeline#connect, 而, pipeline 的 connect 代碼如下:

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

 而tail字段,我們已經分析過了, 是一個TailContext的實例, 而TailContext又是 AbstractChannelHandlerContext 的子類, 並且沒有實現 connect 方法, 因此這裏調用的其實是 AbstractChannelHandlerContext.connect, 我們看一下這個方法的實現:

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

    // 刪除的參數檢查的代碼
    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeConnect(remoteAddress, localAddress, promise);
    } else {
        safeExecute(executor, new OneTimeTask() {
            @Override
            public void run() {
                next.invokeConnect(remoteAddress, localAddress, promise);
            }
        }, promise, null);
    }

    return promise;
}

 上面的代碼中有一個關鍵的地方,即 final AbstractChannelHandlerContext next = findContextOutbound(), 這裏調用findContextOutbound方法, 從 DefaultChannelPipeline內的雙向鏈表的tail開始, 不斷向前尋找第一個 outbound 爲 true 的AbstractChannelHandlerContext, 然後調用它的 invokeConnect 方法, 其代碼如下:

private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    // 忽略 try 塊
    ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
}

 還記得在pipeline的初始化過程中提到, 在 DefaultChannelPipeline 的構造器中, 會實例化兩個對象: head 和 tail, 並形成了雙向鏈表的頭和尾. head是HeadContext的實例,它實現了ChannelOutboundHandler接口, 並且它的 outbound 字段爲 true. 因此在 findContextOutbound 中, 找到的 AbstractChannelHandlerContext 對象其實就是 head. 進而在 invokeConnect 方法中, 我們向上轉換爲 ChannelOutboundHandler 就是沒問題的了.而又因爲 HeadContext 重寫了 connect 方法, 因此實際上調用的是 HeadContext.connect. 我們接着跟蹤到 HeadContext.connect, 其代碼如下:

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

 這個 connect 方法很簡單, 僅僅調用了 unsafe 的 connect 方法. 而 unsafe 又是什麼呢?回顧一下 HeadContext 的構造器, 我們發現 unsafe 是pipeline.channel().unsafe() 返回的, 而 Channel 的 unsafe 字段, 在這個例子中, 我們已經知道了, 其實是 AbstractNioByteChannel.NioByteUnsafe 內部類. 兜兜轉轉了一大圈, 我們找到了創建 Socket 連接的關鍵代碼.進行跟蹤 NioByteUnsafe -> AbstractNioUnsafe.connect:

@Override
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 的實現如上代碼所示, 在這個 connect 方法中, 調用了 doConnect 方法, 注意, 這個方法並不是 AbstractNioUnsafe 的方法, 而是 AbstractNioChannel 的抽象方法. doConnect 方法是在 NioSocketChannel 中實現的, 因此進入到 NioSocketChannel.doConnect 中:

@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
    if (localAddress != null) {
        javaChannel().socket().bind(localAddress);
    }

    boolean success = false;
    try {
        boolean connected = javaChannel().connect(remoteAddress);
        if (!connected) {
            selectionKey().interestOps(SelectionKey.OP_CONNECT);
        }
        success = true;
        return connected;
    } finally {
        if (!success) {
            doClose();
        }
    }
}

 上面的代碼不用多說, 首先是獲取 Java NIO SocketChannel, 即我們已經分析過的, 從 NioSocketChannel.newSocket 返回的 SocketChannel 對象; 然後是調用 SocketChannel.connect 方法完成 Java NIO 層面上的 Socket 的連接.最後, 上面的代碼流程可以用如下時序圖直觀地展示:
這裏寫圖片描述

圖8:SocketChannel的連接過程

服務器端部分

在分析客戶端的代碼時, 我們已經對 Bootstrap 啓動 Netty 有了一個大致的認識, 那麼接下來分析服務器端時, 就會相對簡單一些了.首先還是來看一下服務器端的啓動代碼:

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 和客戶端的代碼相比, 沒有很大的差別, 基本上也是進行了如下幾個部分的初始化:

  • EventLoopGroup: 不論是服務器端還是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 指定了 NioEventLoopGroup, 表示一個 NIO 的EventLoopGroup, 不過服務器端需要指定兩個 EventLoopGroup, 一個是 bossGroup, 用於處理客戶端的連接請求; 另一個是 workerGroup, 用於處理與各個客戶端連接的 IO 操作.
  • ChannelType: 指定 Channel 的類型. 因爲是服務器端, 因此使用了 NioServerSocketChannel.
  • Handler: 設置數據的處理器.

Channel 的初始化過程

我們在分析客戶端的Channel初始化過程時, 已經提到, Channel 是對 Java 底層 Socket 連接的抽象, 並且知道了客戶端的Channel的具體類型是NioSocketChannel, 那麼自然的, 服務器端的 Channel 類型就是 NioServerSocketChannel了.那麼接下來我們按照分析客戶端的流程對服務器端的代碼也同樣地分析一遍, 這樣也方便我們對比一下服務器端和客戶端有哪些不一樣的地方.

Channel 類型的確定

 同樣的分析套路, 我們已經知道了, 在客戶端中,Channel 的類型其實是在初始化時, 通過Bootstrap.channel()方法設置的, 服務器端自然也不例外.在服務器端, 我們調用了 ServerBootstarap.channel(NioServerSocketChannel.class), 傳遞了一個 NioServerSocketChannel Class 對象. 這樣的話, 按照和分析客戶端代碼一樣的流程, 我們就可以確定, NioServerSocketChannel 的實例化是通過 BootstrapChannelFactory工廠類來完成的, 而 BootstrapChannelFactory中的 clazz字段被設置爲了NioServerSocketChannel.class, 因此當調用 BootstrapChannelFactory.newChannel() 時:

@Override
public T newChannel() {
    // 刪除 try 塊
    return clazz.newInstance();
}

就獲取到了一個 NioServerSocketChannel 的實例.最後我們也來總結一下:

  • ServerBootstrap 中的 ChannelFactory 的實現是 BootstrapChannelFactory
  • 生成的 Channel 的具體類型是 NioServerSocketChannel.
    Channel 的實例化過程, 其實就是調用的 ChannelFactory.newChannel 方法, 而實例化的 Channel 的具體的類型又是和在初始化 ServerBootstrap 時傳入的 channel() 方法的參數相關. 因此對於我們這個例子中的服務器端的 ServerBootstrap 而言, 生成的的 Channel 實例就是 NioServerSocketChannel.
NioServerSocketChannel的實例化過程

首先還是來看一下 NioServerSocketChannel 的實例化過程.下面NioServerSocketChannel 的類層次結構圖:

圖9:NioServerSocketChannel 的類層次結構圖

 首先, 我們來看一下它的默認的構造器. 和 NioSocketChannel 類似, 構造器都是調用了 newSocket 來打開一個 Java 的 NIO Socket, 不過需要注意的是, 客戶端的 newSocket 調用的是 openSocketChannel, 而服務器端的 newSocket 調用的是 openServerSocketChannel. 顧名思義, 一個是客戶端的 Java SocketChannel, 一個是服務器端的 Java ServerSocketChannel.

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    return provider.openServerSocketChannel();
}

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

接下來會調用重載的構造器:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

 這個構造其中, 調用父類構造器時, 傳入的參數是 SelectionKey.OP_ACCEPT. 作爲對比, 我們回想一下, 在客戶端的 Channel 初始化時, 傳入的參數是 SelectionKey.OP_READ. 有 Java NIO Socket 開發經驗的朋友就知道了, Java NIO 是一種 Reactor 模式, 我們通過 selector 來實現 I/O 的多路複用複用. 在一開始時, 服務器端需要監聽客戶端的連接請求, 因此在這裏我們設置了 SelectionKey.OP_ACCEPT, 即通知 selector 我們對客戶端的連接請求感興趣.接着和客戶端的分析一下, 會逐級地調用父類的構造器
NioServerSocketChannel <- AbstractNioMessageChannel <- AbstractNioChannel <- AbstractChannel.
 同樣的, 在 AbstractChannel 中會實例化一個 unsafe 和 pipeline:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    unsafe = newUnsafe();
    pipeline = new DefaultChannelPipeline(this);
}

 不過,這裏有一點需要注意的是, 客戶端的unsafe是一個 AbstractNioByteChannel.NioByteUnsafe的實例,而在服務器端時,因爲 AbstractNioMessageChannel重寫newUnsafe 方法:

@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

因此在服務器端, unsafe 字段其實是一個AbstractNioMessageChannel#AbstractNioUnsafe 的實例.我們來總結一下, 在 NioServerSocketChannsl 實例化過程中, 所需要做的工作:

  • 調用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打開一個新的 Java NIO ServerSocketChannel
  • AbstractChannel(Channel parent) 中初始化 AbstractChannel 的屬性:
  • parent 屬性置爲 null
  • unsafe 通過newUnsafe() 實例化一個 unsafe 對象, 它的類型是 AbstractNioMessageChannel#AbstractNioUnsafe 內部類
  • pipeline 是 new DefaultChannelPipeline(this) 新創建的實例.
  • AbstractNioChannel 中的屬性:
  • SelectableChannel ch 被設置爲 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel.
  • readInterestOp 被設置爲 SelectionKey.OP_ACCEPT
  • SelectableChannel ch 被配置爲非阻塞的 ch.configureBlocking(false)
  • NioServerSocketChannel 中的屬性:
  • ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())

ChannelPipeline 初始化

服務器端和客戶端的 ChannelPipeline 的初始化一致, 因此就不再單獨分析了.

Channel 的註冊

服務器端和客戶端的 Channel 的註冊過程一致, 因此就不再單獨分析了.

關於 bossGroup 與 workerGroup

在客戶端的時候, 我們只提供了一個 EventLoopGroup 對象, 而在服務器端的初始化時, 我們設置了兩個 EventLoopGroup, 一個是 bossGroup, 另一個是 workerGroup. 那麼這兩個 EventLoopGroup 都是幹什麼用的呢? 其實呢, bossGroup 是用於服務端 的 accept 的, 即用於處理客戶端的連接請求. 我們可以把 Netty 比作一個飯店, bossGroup 就像一個像一個前臺接待, 當客戶來到飯店吃時, 接待員就會引導顧客就坐, 爲顧客端茶送水等. 而 workerGroup, 其實就是實際上幹活的啦, 它們負責客戶端連接通道的 IO 操作: 當接待員 招待好顧客後, 就可以稍做休息, 而此時後廚裏的廚師們(workerGroup)就開始忙碌地準備飯菜了.
關於 bossGroup與 workerGroup的關係, 我們可以用如下圖來展示:
這裏寫圖片描述

圖10:bossGroup 與 workerGroup 的關係

 首先, 服務器端 bossGroup 不斷地監聽是否有客戶端的連接, 當發現有一個新的客戶端連接到來時, bossGroup 就會爲此連接初始化各項資源, 然後從 workerGroup 中選出一個 EventLoop 綁定到此客戶端連接中. 那麼接下來的服務器與客戶端的交互過程就全部在此分配的 EventLoop 中了.
 在ServerBootstrap 初始化時, 調用了 b.group(bossGroup, workerGroup) 設置了兩個 EventLoopGroup, 我們跟蹤進去看一下:

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    ...
    this.childGroup = childGroup;
    return this;
}

 顯然, 這個方法初始化了兩個字段, 一個是 group = parentGroup, 它是在 super.group(parentGroup) 中初始化的, 另一個是 childGroup = childGroup. 接着我們啓動程序調用了 b.bind 方法來監聽一個本地端口. bind 方法會觸發如下的調用鏈:

AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister

AbstractBootstrap.initAndRegister 是我們的老朋友了, 我們在分析客戶端程序時, 和它打過很多交到了, 我們再來回顧一下這個方法吧:

final ChannelFuture initAndRegister() {
    final Channel channel = channelFactory().newChannel();
    ... 省略異常判斷
    init(channel);
    ChannelFuture regFuture = group().register(channel);
    return regFuture;
}

 這裏 group()方法返回的是上面我們提到的bossGroup,而這裏的channel我們也已經分析過了,它是一個是一個NioServerSocketChannsl實例, 因此我們可以道,group().register(channel)將bossGroup和NioServerSocketChannsl關聯起來了.那麼 workerGroup 是在哪裏與 NioSocketChannel關聯的呢?我們繼續看 init(channel) 方法:

@Override
void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

 init 方法在 ServerBootstrap 中重寫了, 從上面的代碼片段中我們看到, 它爲 pipeline 中添加了一個ChannelInitializer, 而這個ChannelInitializer中添加了一個關鍵的ServerBootstrapAcceptor handler. 關於 handler 的添加與初始化的過程, 現在關注一下 ServerBootstrapAcceptor 類.
ServerBootstrapAcceptor 中重寫了 channelRead 方法, 其主要代碼如下:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    ...
    childGroup.register(child).addListener(...);
}

 ServerBootstrapAcceptor 中的 childGroup 是構造此對象是傳入的 currentChildGroup, 即我們的 workerGroup, 而 Channel 是一個 NioSocketChannel 的實例, 因此這裏的 childGroup.register 就是將 workerGroup 中的摸個 EventLoop 和 NioSocketChannel 關聯了. 既然這樣, 那麼現在的問題是, ServerBootstrapAcceptor.channelRead 方法是怎麼被調用的呢? 其實當一個 client 連接到 server 時, Java 底層的 NIO ServerSocketChannel 會有一個 SelectionKey.OP_ACCEPT 就緒事件, 接着就會調用到 NioServerSocketChannel.doReadMessages:

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = javaChannel().accept();
    ... 省略異常處理
    buf.add(new NioSocketChannel(this, ch));
    return 1;
}

 在 doReadMessages 中, 通過 javaChannel().accept() 獲取到客戶端新連接的 SocketChannel, 接着就實例化一個 NioSocketChannel, 並且傳入 NioServerSocketChannel對象(即 this),由此可知,我們創建的這NioSocketChannel 的父 Channel 就是 NioServerSocketChannel 實例 .接下來就經由 Netty 的 ChannelPipeline 機制, 將讀取事件逐級發送到各個 handler 中, 於是就會觸發前面我們提到的 ServerBootstrapAcceptor.channelRead 方法啦.

handler的添加過程

 服務器端的 handler 的添加過程和客戶端的有點區別, 和EventLoopGroup一樣, 服務器端的 handler 也有兩個, 一個是通過handler()方法設置 handler 字段, 另一個是通過 childHandler()設置 childHandler字段. 通過前面的 bossGroup和workerGroup 的分析, 其實我們在這裏可以大膽地猜測: handler 字段與 accept 過程有關, 即這個 handler 負責處理客戶端的連接請求; 而 childHandler 就是負責和客戶端的連接的 IO 交互.那麼實際上是不是這樣的呢? 來, 我們繼續通過代碼證明.
 在 關於 bossGroup 與 workerGroup 小節中, 我們提到, ServerBootstrap 重寫了 init 方法, 在這個方法中添加了 handler:

@Override
void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

 上面代碼的 initChannel 方法中, 首先通過 handler() 方法獲取一個 handler, 如果獲取的 handler 不爲空,則添加到 pipeline 中. 然後接着, 添加了一個 ServerBootstrapAcceptor 實例. 那麼這裏 handler() 方法返回的是哪個對象呢? 其實它返回的是 handler 字段, 而這個字段就是我們在服務器端的啓動代碼中設置的:

b.group(bossGroup, workerGroup)
 ...
 .handler(new LoggingHandler(LogLevel.INFO))

 那麼這個時候, pipeline 中的handler 情況如下:
這裏寫圖片描述

圖11: pipeline 中的 handler

 根據我們原來分析客戶端的經驗, 我們指定, 當 channel 綁定到 eventLoop 後(在這裏是 NioServerSocketChannel 綁定到 bossGroup)中時, 會在 pipeline 中發出 fireChannelRegistered 事件, 接着就會觸發 ChannelInitializer.initChannel 方法的調用.
 因此在綁定完成後, 此時的 pipeline 的內容如下:
這裏寫圖片描述
圖12: pipeline 的內容

 前面我們在分析 bossGroup 和 workerGroup 時, 已經知道了在 ServerBootstrapAcceptor.channelRead 中會爲新建的 Channel 設置 handler 並註冊到一個 eventLoop 中, 即:

@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    ...
    childGroup.register(child).addListener(...);
}

 而這裏的 childHandler 就是我們在服務器端啓動代碼中設置的 handler:

b.group(bossGroup, workerGroup)
 ...
 .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
         if (sslCtx != null) {
             p.addLast(sslCtx.newHandler(ch.alloc()));
         }
         //p.addLast(new LoggingHandler(LogLevel.INFO));
         p.addLast(new EchoServerHandler());
     }
 });

 後續的步驟就沒有什麼好說的了, 當這個客戶端連接 Channel 註冊後, 就會觸發 ChannelInitializer.initChannel 方法的調用, 此後的客戶端的 ChannelPipeline狀態如下:
這裏寫圖片描述

圖13: 客戶端的 ChannelPipeline狀態

 最後我們來總結一下服務器端的 handler 與 childHandler 的區別與聯繫:

  • 在服務器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.
  • 當有新的客戶端連接請求時, ServerBootstrapAcceptor.channelRead 中負責新建此連接的 NioSocketChannel 並添加 childHandler 到 NioSocketChannel 對應的 pipeline 中, 並將此 channel 綁定到 workerGroup 中的某個 eventLoop 中.
  • handler 是在 accept 階段起作用, 它處理客戶端的連接請求.
  • childHandler 是在客戶端連接建立以後起作用, 它負責客戶端連接的 IO 交互.
  • 在服務器 NioServerSocketChannel 的 pipeline 中添加的是 handler 與 ServerBootstrapAcceptor.

下面我們用一幅圖來總結一下服務器端的 handler 添加流程:
這裏寫圖片描述

圖14: Server 端 handler 與 childHandler 圖示

總結

 這篇文章是一個簡述性質的, 即這裏會涉及到Netty各個功能模塊, 但是我只是簡單地提了一下, 而沒有深入地探索它們內部的實現機理. 之所以這樣做:

  1. 第一, 是因爲如果一上來就從細節分析, 那麼未免會陷入各種瑣碎的細節中難以自拔;
  2. 第二, 我想給讀者展示一個一個完整的 Netty 的運行流程, 讓讀者從一個整體上對 Netty 有一個感性的認識.

 此篇文章涉及的模塊比較多, 面比較廣, 因此寫起來難免有一點跳躍, 並且我感覺寫着寫着見見有點不知所云, 邏輯混亂了接下來的幾篇文章, 我會根據 Netty 的各個模塊深入分析一下, 希望以後的文章能夠組織的調理更加清晰一些.

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