[Netty源码] 客户端启动流程 (三)

1.Bootstrap

Bootstrap是Netty框架引导客户端的入口, 引导过程中需要开发人员对其进行一些配置

  • EventLoopGroup
  • Channel
  • ChannelOption
  • ChannelAttr
  • ChannelHandler

Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(new NioEventLoopGroup());
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.remoteAddress("127.0.0.1", 30888);
final MessageChannel channel = new MessageChannel();
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
  @Override
  protected void initChannel(SocketChannel ch) throws Exception {
    //配置Pipeline
  }
});
ChannelFuture channelFuture = bootstrap.connect().sync();
channelFuture.channel().closeFuture().sync();
bootstrap.group().shutdownGracefully();

使用了Nio的网络模型, 使用了30888端口号, 并使用ChannelInitializer类型的ChannleHandler作为ChannelHandler的初始化器。

ChannelInitializer的作用是当客户端连接成功后初始化ChannelPipeline

在这里插入图片描述

Bootstrap是继承自AbstractBootstrap类


public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
    @SuppressWarnings("unchecked")
    private static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
    @SuppressWarnings("unchecked")
    private static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];

    volatile EventLoopGroup group;
    @SuppressWarnings("deprecation")
    private volatile ChannelFactory<? extends C> channelFactory;
    private volatile SocketAddress localAddress;

    // The order in which ChannelOptions are applied is important they may depend on each other for validation
    // purposes.
    private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
    private volatile ChannelHandler handler;

    //构造函数与定义的方法
}

定义在AbstractBootstrap抽象类中的方法应该是Bootstrap与ServerBootstrap都能使用到的方法

2.客户端启动流程

在这里插入图片描述

2.1 connect方法

Bootstap.connect()

    public ChannelFuture connect(SocketAddress remoteAddress) {
        ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

validate() 检验参数的触发器是否存在, 如果不存在抛出异常

在这里插入图片描述

Bootstrap.doResolveAndConnect()

在这里插入图片描述

AbstractBootstrap.initAndRegister()

  1. 使用channelFactory.newChannel()创建channel对象,channelFactory是在bootstrap.channel()方法中初始创建

  2. 调用子类的init()方法初始channel对象,这一过程会调用bootstrap.handler()方法,将配置的channelHandler添加到channel的pipeline中。

  3. 配置channelOption与attributes到channel对象

  4. 将初始完成的Channel对象注册到EventLoopGroup中

在这里插入图片描述

  1. 通过channelFactory反射创建NioSocketChannel
  2. 设置一些初始化参数
  3. 将channel注册如EventLoopGroup

SingleThreadEventLoop.register()

在这里插入图片描述

AbstractUnsafe.register()

在这里插入图片描述

  1. 保持eventLoop
  2. register0()实现: 实现注册selector
2.2 doResolveAndConnect0

在这里插入图片描述

  1. 使用EventLoop创建一个地址解析器,异常时将结束方法调用

  2. 判断当前地址是否已经解析过,解析过的地址就直接调用doConnect()方法

  3. 没有解析过的地址先进行解析,再用解析后的地址调用doConnect()方法

    private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            final EventLoop eventLoop = channel.eventLoop();
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);

            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                // Resolver has no idea about what to do with the specified remote address or it's resolved already.
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }

            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);

            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();

                if (resolveFailureCause != null) {
                    // Failed to resolve immediately
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    // Succeeded to resolve immediately; cached? (or did a blocking lookup)
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }

            // Wait until the name resolution is finished.
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        channel.close();
                        promise.setFailure(future.cause());
                    } else {
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        } catch (Throwable cause) {
            promise.tryFailure(cause);
        }
        return promise;
    }

最后将由我们在Bootstrap类型中赋值时的NioSocketChannel.doConnect()执行真正的连接操作, 而这一过程又是由ChannelPipeline的connect事件完成

所以最后NioSocketChannel.doConnect()方法的责任则是调用java.nio.channels.SocketChannel.connect()完成。

3.AbstractBootstrap抽象方法

在这里插入图片描述

  1. group 配置EventLoopGroup,为服务端时将指定为parent,时间主子Reactor模型
  2. self 返回当前类型,也就是自己
  3. channel 配置Channel,并初始化channelFactory
  4. channelFactory 和配置Channel相同,通过配置这一项可以覆盖channel的配置
  5. localAddress 配置本地地址
  6. option 配置channel的选项
  7. attr 配置Channel的属性

AbstractBootstrap中常用的能用方法,这些方法在服务端也会用到。

4.总结

Nio模式下客户端的引导通过在Bootstrap上配置的NioSocketChannel、NioEventLoopGroup、ChannelInitializer等预先配置信息,在调用Bootstrap.connect()方法后,在其内部通过channelFactory创建channel,调用initAndRegister()配置option与attr并注册到eventLoopGroup中,再通过解析出的远程地址连接远程服务,连接的过程是通过channel.pipeline中的事件传递完成,最后由配置在Bootstarp中的channel(也就是NioSocketChannel)实际做出连接的动作。是否连接成功可以通过Bootstrap.connect()返回的ChannelFuture类型实例进行判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值