客户端 Client
客户端启动入口一般从 Bootstrap.connect() 开始。和服务端 ServerBootstrap 不同,客户端只有一个 EventLoopGroup,这个 group 同时负责客户端 Channel 的注册、连接、读写等 I/O 事件;服务端才有 parent/boss 和 child/worker 的分工。
java
...
// 初始化线程组
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 {
// 优雅关闭
...
}
整体流程
客户端连接主线:
text
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()。
java
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() 的职责可以拆成三步:
- 创建客户端 Channel,例如
NioSocketChannel。 - 调用
Bootstrap.init(channel)初始化客户端 Channel。 - 调用
config().group().register(channel),把 Channel 注册到 EventLoop。
客户端的 Bootstrap.init(channel) 和服务端的 ServerBootstrap.init(channel) 不完全一样。客户端没有 childGroup、childHandler、ServerBootstrapAcceptor 这些逻辑,主要做下面几件事:
java
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()。
java
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()
java
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() 会进入:
text
ChannelPipeline.connect
-> AbstractChannelHandlerContext.connect
-> HeadContext.connect
-> Unsafe.connect
补充一下 Pipeline 事件传播方向:
出站事件 ,例如 connect、bind、write,从 Tail -> Head 方向传播,也就是沿 prev 指针移动。
入站事件 ,例如 channelRead、channelActive,从 Head -> Tail 方向传播,也就是沿 next 指针移动。
java
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()。
java
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 不是"延时后判断成功并继续往下走"。它只负责超时失败处理。
异步连接真正成功的路径是:
text
Selector 监听到 OP_CONNECT
-> NioHandler 处理 connect-ready 事件
-> unsafe.finishConnect()
-> doFinishConnect()
-> fulfillConnectPromise()
-> 如果 Channel 从 inactive 变为 active,则触发 pipeline.fireChannelActive()
所以客户端连接有两种结果路径:
doConnect()立即返回true,说明连接同步完成,直接fulfillConnectPromise()。doConnect()返回false,说明连接异步进行中,注册OP_CONNECT,等 Selector 通知连接完成后再finishConnect()。