【Netty】(5)源碼 Bootstrap

【Netty】5 源碼 Bootstrap

上一篇講了AbstractBootstrap,爲這篇作了個鋪墊。java

1、概述

Bootstrap 是 Netty 提供的一個便利的工廠類, 咱們能夠經過它來完成 Netty 的客戶端或服務器端的 Netty 初始化.
Bootstrap: 用於客戶端,只須要一個單獨的Channel,來與服務端進行數據交互,對應server端的子Channel。
做用職責:EventLoop初始化,channel的註冊過程 ,關於pipeline的初始化,handler的添加過程,客戶端鏈接分析。ios

Netty客戶端源碼部分bootstrap

EventLoopGroup group = new NioEventLoopGroup();
         try {
             Bootstrap b = new Bootstrap();
             b.group(group) // 註冊線程池
              .channel(NioSocketChannel.class) // 使用NioSocketChannel來做爲鏈接用的channel類
              .handler(new ChannelInitializer<SocketChannel>() { // 綁定鏈接初始化器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                                     //這裏放入自定義助手類
                                     ch.pipeline().addLast(new EchoClientHandler());
                                 }
                             });
         
             ChannelFuture cf = b.connect(host, port).sync(); // 異步鏈接服務器
             cf.channel().closeFuture().sync(); // 異步等待關閉鏈接channel
 
         } finally {
             group.shutdownGracefully().sync(); // 釋放線程池資源
         }
     }

從上面的客戶端代碼雖然簡單, 可是卻展現了 Netty 客戶端初始化時所需的全部內容:segmentfault

1. EventLoopGroup: 不管是服務器端仍是客戶端, 都必須指定 EventLoopGroup. 在這個例子中, 
   指定了 NioEventLoopGroup, 表示一個 NIO   的EventLoopGroup.
2. ChannelType: 指定 Channel 的類型. 由於是客戶端, 所以使用了 NioSocketChannel.
3. Handler: 設置數據的處理器.
4. 這裏的option,提供了一系列的TCP參數

下面咱們深刻代碼, 看一下客戶端經過 Bootstrap 啓動後, 都作了哪些工做.promise

2、源碼分析


一、group(group)

/**
  * 直接調用父類AbstractBootstrap的方法
  */
public B group(EventLoopGroup group) {
    if (group == null) {
        throw new NullPointerException("group");
    }
    if (this.group != null) {
        throw new IllegalStateException("group set already");
    }
    this.group = group;
    return self();
}

直接調用父類的方法 ,說明該EventLoopGroup,做爲客戶端 Connector 線程,負責註冊監聽鏈接操做位,用於判斷異步鏈接結果。服務器


二、channel(NioServerSocketChannel.class)

在 Netty 中, Channel是一個Socket的抽象, 它爲用戶提供了關於 Socket 狀態(是不是鏈接仍是斷開) 以及對 Socket 的讀寫等操做. 每當 Netty 創建了一個鏈接後, 都會有一個對應的 Channel 實例。網絡

2.1源碼

/**
 * 一樣也是直接調用父類AbstractBootstrap的方法
 */
    public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        }
        return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
    }

咱們再來看下ReflectiveChannelFactory類異步

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

        private final Class<? extends T> clazz;

        /**
         * 經過構造函數 傳入 clazz
         */
        public ReflectiveChannelFactory(Class<? extends T> clazz) {
            if (clazz == null) {
                throw new NullPointerException("clazz");
            }
            this.clazz = clazz;
        }

        /**
         * 只用這一個方法 經過傳入不一樣的Channel.class 建立不一樣的Channel 對象。
         * newChannel() 何時調用呢 仔細追源碼 發現是在綁定 IP 和 端口的 doResolveAndConnect方法裏會調用
         */
        @Override
        public T newChannel() {
            try {
                return clazz.getConstructor().newInstance();
            } catch (Throwable t) {
                throw new ChannelException("Unable to create Channel from class " + clazz, t);
            }
        }

在看channelFactory(new ReflectiveChannelFactory (channelClass)) 方法 socket

/**
     * 建立好Channel後,返回對象Bootstrap自己
     */
    @Deprecated
    public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        if (channelFactory == null) {
            throw new NullPointerException("channelFactory");
        }
        if (this.channelFactory != null) {
            throw new IllegalStateException("channelFactory set already");
        }
        this.channelFactory = channelFactory;
        return self();
    }

所以對於咱們這個例子中的客戶端的 Bootstrap 而言, 生成的的 Channel 實例就是 NioSocketChannel。ide

2.2 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 鏈接.

三、handler(ChannelHandler handler)

Netty 的一個強大和靈活之處就是基於 Pipeline 的自定義 handler 機制. 基於此, 咱們能夠像添加插件同樣自由組合各類各樣的 handler 來完成業務邏輯. 例如咱們須要處理 HTTP 數據, 那麼就能夠在 pipeline 前添加一個 Http 的編解碼的 Handler, 而後接着添加咱們本身的業務邏輯的 handler, 這樣網絡上的數據流就向經過一個管道同樣, 從不一樣的 handler 中流過並進行編解碼, 最終在到達咱們自定義的 handler 中。

/**
 * 一樣也是 直接調用父類 AbstractBootstrap 的方法
 */
public B handler(ChannelHandler handler) {
    if (handler == null) {
        throw new NullPointerException("handler");
    }
    this.handler = handler;
    return self();
}

不過咱們看到代碼 通常都是這樣寫的

.handler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new EchoClientHandler());
        }
     })

那是由於Bootstrap.handler 方法接收一個 ChannelHandler, 而咱們傳遞的是一個 派生於 ChannelInitializer 的匿名類, 它正好也實現了 ChannelHandler 接口. 咱們來看一下, ChannelInitializer 類部分代碼:

/**
     * ChannelInboundHandlerAdapter 父類的父類 最終會繼承 ChannelHandler 
     * 那麼ChannelInitializer 也就是 ChannelHandler的 子類
     */
    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 中刪除. 上面的分析過程, 能夠用以下圖片展現:
一開始, ChannelPipeline 中只有三個 handler, head, tail 和咱們添加的 ChannelInitializer.

接着 initChannel 方法調用後, 添加了自定義的 handler

最後將 ChannelInitializer 刪除


### 四、ChannelPipeline對象
/**
     * 咱們在initChannel抽象方法的實現方法中 經過 SocketChannel得到 ChannelPipeline對象
     */
    ChannelPipeline p = ch.pipeline();
    p.addLast(newEchoClientHandler());

在實例化一個 Channel 時, 會伴隨着一個 ChannelPipeline 的實例化, 而且此 Channel 會與這個 ChannelPipeline 相互關聯, 這一點能夠經過NioSocketChannel 的父類 AbstractChannel 的構造器:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    unsafe = newUnsafe();
    //這個能夠看出
    pipeline = new DefaultChannelPipeline(this);
}

當實例化一個 Channel(這裏以 EchoClient 爲例, 那麼 Channel 就是 NioSocketChannel), 其 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 爲節點的雙向鏈表, 這個鏈表是 Netty 實現 Pipeline 機制的關鍵。

五、.connect(host, port)

通過上面的各類分析後, 咱們大體瞭解了 Netty 初始化時, 所作的工做, 接下來 分析一下客戶端是如何發起 TCP 鏈接的。

/**
     * 一、 這裏 終因而Bootstrap 本身的方法了。 傳入IP 地址 和 端口號
     */
    public ChannelFuture connect(String inetHost, int inetPort) {
        //經過InetSocketAddress 構造函數 綁定 IP地址+端口號
        return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }

   /**
     * 二、上面調用該方法 ,該方法在調用 doResolveAndConnect方法
     */
    public ChannelFuture connect(SocketAddress remoteAddress) {
        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }
        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

   /**
     * 三、這步 實例化 Channer
     */
    private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
      
        //注意 這裏 initAndRegister()方法就是實例化 Channer 的方法 上面說過 真正獲取Channer 對象 是在這步獲取的
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();

           // 這裏省略的 很大一部分邏輯判斷的代碼
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());   
    }

    /**
     * 3.1 這裏 就開始 調 newChannel() 方法 也就建立了 Channel 對象
     */
    final ChannelFuture initAndRegister() {
        Channel channel = null;
                channel = channelFactory.newChannel();
        return regFuture;
    }

    /**
     * 四、在看doResolveAndConnect0方法
     *    這一步仍是對一些 參數數據 進行校驗  省略了校驗代碼
     */
    private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
           // 獲取 當前 EventLoop線程
            final EventLoop eventLoop = channel.eventLoop();
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
                   //這一步 纔是 鏈接的關鍵
                    doConnect(resolveFuture.getNow(), localAddress, promise);
           
        return promise;
    }

接下來看重要的方法,在 connect 中, 會進行一些參數檢查後, 最終調用的是 doConnect 方法,有關doConnect以後接下來源碼,等本身對Netty瞭解更細緻以後 ,再來寫吧。

這裏推薦一個博主,有關Netty源碼分析的蠻好的:源碼之下無祕密 ── 作最好的 Netty 源碼分析教程




``` 若是一我的充滿快樂,正面的思想,那麼好的人事物就會和他共鳴,並且被他吸引過來。一樣,一我的老帶悲傷,倒黴的事情也會跟過來。
——在本身心情低落的時候,告誡本身不要把負能量帶給別人。(大校13)
相關文章
相關標籤/搜索