【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)
相关文章
相关标签/搜索