2.启动客户端client

2 阅读4分钟

客户端 Client

客户端启动入口一般从 Bootstrap.connect() 开始。和服务端 ServerBootstrap 不同,客户端只有一个 EventLoopGroup,这个 group 同时负责客户端 Channel 的注册、连接、读写等 I/O 事件;服务端才有 parent/boss 和 child/worker 的分工。

...

// 初始化线程组
EventLoopGroup group = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
try {
    Bootstrap b = new Bootstrap();
    // 设置处理线程组
    b.group(group)
        // 设置 Channel 类型
        .channel(NioSocketChannel.class)
        // 设置 ChannelOption
        .option(ChannelOption.TCP_NODELAY, true)
        // 初始化客户端 Channel 的 Pipeline
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline p = ch.pipeline();
                ...
                p.addLast(new EchoClientHandler());
            }
        });

    // 连接服务端
    ChannelFuture f = b.connect(HOST, PORT).sync();

    // 等待直到关闭
    f.channel().closeFuture().sync();
} finally {
    // 优雅关闭
    ...
}

整体流程

客户端连接主线:

Bootstrap.connect
    -> doResolveAndConnect
        -> initAndRegister
            -> 创建 Channel
            -> Bootstrap.init(channel)
            -> EventLoopGroup.register(channel)
        -> doResolveAndConnect0
            -> 地址解析
            -> doConnect
                -> channel.connect
                    -> pipeline.connect
                    -> HeadContext.connect
                    -> Unsafe.connect
                        -> doConnect
                        -> 立即成功或等待 OP_CONNECT

Bootstrap.connect()

Bootstrap.connect() 最终会进入 doResolveAndConnect()

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress,
                                          final SocketAddress localAddress) {
    // 创建 Channel、初始化 Pipeline,并注册到 EventLoop
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();

    // 这里判断的是注册 Future 是否已经完成
    if (regFuture.isDone()) {
        if (!regFuture.isSuccess()) {
            return regFuture;
        }
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        // 注册尚未完成时,添加监听器,等注册成功后再继续连接
        ...
    }
}

这里要注意:initAndRegister() 做了初始化和注册,但 regFuture 表示的是注册结果。只有 Channel 注册到 EventLoop 后,后续的连接动作才会提交到对应的 EventLoop 中执行。

initAndRegister()

initAndRegister() 的职责可以拆成三步:

  1. 创建客户端 Channel,例如 NioSocketChannel
  2. 调用 Bootstrap.init(channel) 初始化客户端 Channel。
  3. 调用 config().group().register(channel),把 Channel 注册到 EventLoop。

客户端的 Bootstrap.init(channel) 和服务端的 ServerBootstrap.init(channel) 不完全一样。客户端没有 childGroupchildHandlerServerBootstrapAcceptor 这些逻辑,主要做下面几件事:

void init(Channel channel) {
    // 设置 ChannelOption
    setChannelOptions(channel, newOptionsArray(), logger);

    // 设置 Attribute
    setAttributes(channel, newAttributesArray());

    ChannelPipeline p = channel.pipeline();

    // 加入用户配置的 handler,通常就是 ChannelInitializer
    p.addLast(config.handler());

    // 执行客户端初始化扩展点
    ...
}

以 NIO 为例,注册阶段最终会把 Channel 绑定到某个 EventLoop,并注册到底层 Selector。

doResolveAndConnect0()

注册完成后,连接流程进入 doResolveAndConnect0()

private ChannelFuture doResolveAndConnect0(final Channel channel,
                                           SocketAddress remoteAddress,
                                           final SocketAddress localAddress,
                                           final ChannelPromise promise) {
    try {
        // disableResolver 为 true 表示禁用地址解析,不会再走 AddressResolver
        if (disableResolver) {
            doConnect(remoteAddress, localAddress, promise);
            return promise;
        }

        // 如果地址已经解析,或者 resolver 不支持该地址类型,就直接连接;
        // 否则先异步解析地址,解析成功后再 doConnect。
        ...
    } catch (Throwable cause) {
        promise.tryFailure(cause);
    }
    return promise;
}

disableResolver == true 表示禁用解析,不是启用解析。禁用后最好传入已经解析好的地址,否则底层连接可能失败。

doConnect()

private static void doConnect(final SocketAddress remoteAddress,
                              final SocketAddress localAddress,
                              final ChannelPromise connectPromise) {
    final Channel channel = connectPromise.channel();

    channel.eventLoop().execute(() -> {
        if (localAddress == null) {
            channel.connect(remoteAddress, connectPromise);
        } else {
            channel.connect(remoteAddress, localAddress, connectPromise);
        }
        connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
    });
}

这里没有直接在当前线程执行 channel.connect(),而是提交到 Channel 绑定的 EventLoop 中执行。这样可以保证连接动作和后续 I/O 操作都在同一个 EventLoop 线程内完成,同时也给 channelRegistered() 这类事件先触发的机会。

Pipeline 中的 connect

channel.connect() 会进入:

ChannelPipeline.connect
    -> AbstractChannelHandlerContext.connect
    -> HeadContext.connect
    -> Unsafe.connect

补充一下 Pipeline 事件传播方向:

出站事件,例如 connectbindwrite,从 Tail -> Head 方向传播,也就是沿 prev 指针移动。

入站事件,例如 channelReadchannelActive,从 Head -> Tail 方向传播,也就是沿 next 指针移动。

public ChannelFuture connect(final SocketAddress remoteAddress,
                             final SocketAddress localAddress,
                             ChannelPromise promise) {
    // 检查参数和 Promise 状态
    ...

    // 从当前节点向前查找下一个能处理 connect 的出站处理器
    final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
    EventExecutor executor = next.executor();

    if (executor.inEventLoop()) {
        if (next.invokeHandler()) {
            promise = ensurePromiseUseCorrectExecutor(promise);
            try {
                final ChannelHandler handler = next.handler();
                final DefaultChannelPipeline.HeadContext headContext = pipeline.head;
                if (handler == headContext) {
                    headContext.connect(next, remoteAddress, localAddress, promise);
                } else if (handler instanceof ChannelDuplexHandler) {
                    ((ChannelDuplexHandler) handler).connect(next, remoteAddress, localAddress, promise);
                } else if (handler instanceof ChannelOutboundHandlerAdapter) {
                    ((ChannelOutboundHandlerAdapter) handler).connect(next, remoteAddress, localAddress, promise);
                } else {
                    ((ChannelOutboundHandler) handler).connect(next, remoteAddress, localAddress, promise);
                }
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            next.connect(remoteAddress, localAddress, promise);
        }
    } else {
        // 如果当前线程不是该 executor 的线程,则提交任务到对应 executor
        ...
    }
    return promise;
}

AbstractNioUnsafe.connect()

真正执行 NIO 连接的是 AbstractNioChannel 内部的 AbstractNioUnsafe.connect(),不是外层 AbstractNioChannel.connect()

public final void connect(final SocketAddress remoteAddress,
                          final SocketAddress localAddress,
                          final ChannelPromise promise) {
    // 检查 Promise 是否可设置、Channel 是否 open
    ...

    try {
        // 防止重复连接
        ...

        boolean wasActive = isActive();

        // 调用具体 Channel 的底层连接逻辑,例如 NioSocketChannel.doConnect()
        if (doConnect(remoteAddress, localAddress)) {
            // 连接立即成功
            fulfillConnectPromise(promise, wasActive);
        } else {
            // 连接尚未完成,等待 OP_CONNECT 事件
            connectPromise = promise;
            requestedRemoteAddress = remoteAddress;

            final int connectTimeoutMillis = config().getConnectTimeoutMillis();
            if (connectTimeoutMillis > 0) {
                connectTimeoutFuture = eventLoop().schedule(new Runnable() {
                    @Override
                    public void run() {
                        // 超时后,如果连接还没完成,就设置失败并关闭 Channel
                        ...
                    }
                }, connectTimeoutMillis, TimeUnit.MILLISECONDS);
            }

            promise.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    // 如果 connect future 被取消,则取消超时定时器并关闭底层 socket
                    ...
                }
            });
        }
    } catch (Throwable t) {
        // 异常处理
        ...
    }
}

这里要注意:connectTimeoutFuture 不是“延时后判断成功并继续往下走”。它只负责超时失败处理。

异步连接真正成功的路径是:

Selector 监听到 OP_CONNECT
    -> NioHandler 处理 connect-ready 事件
    -> unsafe.finishConnect()
    -> doFinishConnect()
    -> fulfillConnectPromise()
    -> 如果 Channel 从 inactive 变为 active,则触发 pipeline.fireChannelActive()

所以客户端连接有两种结果路径:

  1. doConnect() 立即返回 true,说明连接同步完成,直接 fulfillConnectPromise()
  2. doConnect() 返回 false,说明连接异步进行中,注册 OP_CONNECT,等 Selector 通知连接完成后再 finishConnect()