connect和bind过程详解(二)
Connect与Bind对比总结
Netty Connect 和 Bind 过程详解
本文档详细介绍 Netty 客户端的 Connect 过程。
一、Connect 完整调用链路(客户端)
从客户端代码 b.connect(inetHost, inetPort) 开始,整个调用链路如下:
Bootstrap.connect(host, port)
↓
Bootstrap.connect(SocketAddress)
↓
Bootstrap.doResolveAndConnect() // 先 register,再 connect
↓
Bootstrap.doResolveAndConnect0() // 地址解析
↓
Bootstrap.doConnect() // 提交 connect 任务到 EventLoop
↓
AbstractChannel.connect() // 交给 pipeline
↓
DefaultChannelPipeline.connect() // 从 tail 开始
↓
TailContext.connect()
↓
AbstractChannelHandlerContext.findContextOutbound() // 找下一个 Outbound handler
↓
AbstractChannelHandlerContext.invokeConnect() // 调用 handler 的 connect 方法
↓
HeadContext.connect() // 最终到达 head
↓
AbstractNioChannel.AbstractNioUnsafe.connect() // 调用 unsafe
↓
NioSocketChannel.doConnect() // 执行真正的连接
↓
SocketUtils.connect() // JDK 底层 SocketChannel.connect()
二、详细代码流程解析
2.1 入口:Bootstrap.connect()
用户代码:
// 客户端启动代码
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new MyChannelInitializer());
// 【入口】发起连接
ChannelFuture f = b.connect(inetHost, inetPort).sync();
第一步:地址转换
// Bootstrap
public ChannelFuture connect(String inetHost, int inetPort) {
// 将 host 和 port 转换为 SocketAddress
return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
第二步:参数校验
// Bootstrap
public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
// 校验 Bootstrap 配置是否完整(group、channelFactory 等)
validate();
// 进入核心方法
return doResolveAndConnect(remoteAddress, config.localAddress());
}
2.2 核心方法:doResolveAndConnect()
这个方法做两件事:
- initAndRegister():创建 Channel 并注册到 Selector(这部分前面已经详细介绍过)
- doResolveAndConnect0():解析地址并发起连接
// Bootstrap
private ChannelFuture doResolveAndConnect(
final SocketAddress remoteAddress,
final SocketAddress localAddress) {
// 【重要】先完成 Channel 的创建和注册
// 这一步会:创建 NioSocketChannel、初始化 Pipeline、注册到 Selector
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.isDone()) {
// 情况1:register 已经完成(同步完成)
if (!regFuture.isSuccess()) {
return regFuture; // register 失败,直接返回
}
// register 成功,继续 connect
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
// 情况2:register 还未完成(异步完成)
// 添加监听器,等 register 完成后再执行 connect
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
// register 成功后,执行 connect
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
关键点:
- connect 必须在 register 之后执行
- register 可能同步完成,也可能异步完成
- 如果异步,通过监听器等待 register 完成
2.3 地址解析:doResolveAndConnect0()
这个方法负责解析域名(如果需要),然后调用 doConnect()。
// Bootstrap
private ChannelFuture doResolveAndConnect0(
final Channel channel,
SocketAddress remoteAddress,
final SocketAddress localAddress,
final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
// 获取地址解析器(用于 DNS 解析)
final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// 情况1:地址已经解析好了(比如直接传入 IP),直接 connect
doConnect(remoteAddress, localAddress, promise);
return promise;
}
// 情况2:需要解析域名(比如传入的是 "www.example.com")
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
if (resolveFuture.isDone()) {
// 解析已完成(可能是缓存的结果)
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
channel.close();
promise.setFailure(resolveFailureCause);
} else {
// 解析成功,执行 connect
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
// 解析还未完成,添加监听器等待
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 {
// 解析成功后执行 connect
doConnect(future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable cause) {
promise.tryFailure(cause);
}
return promise;
}
关键点:
- 如果传入的是 IP 地址,直接 connect
- 如果传入的是域名,先进行 DNS 解析,再 connect
- 解析过程也可能是异步的
2.4 提交任务:doConnect()
这个方法将 connect 操作提交到 EventLoop 的任务队列中执行。
// Bootstrap
private static void doConnect(
final SocketAddress remoteAddress,
final SocketAddress localAddress,
final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
// 【关键】将 connect 操作提交到 EventLoop 的任务队列
// 这样可以确保 connect 在 EventLoop 线程中执行
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
// 调用 Channel 的 connect 方法
channel.connect(remoteAddress, connectPromise);
} else {
// 如果指定了本地地址,先绑定本地地址
channel.connect(remoteAddress, localAddress, connectPromise);
}
// 添加监听器:如果 connect 失败,自动关闭 channel
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
关键点:
- connect 操作必须在 EventLoop 线程中执行
- 通过
eventLoop().execute()提交任务 - 添加失败监听器,自动清理资源
2.5 交给 Pipeline:AbstractChannel.connect()
// AbstractChannel
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
// 将 connect 操作交给 pipeline 处理
return pipeline.connect(remoteAddress, promise);
}
作用:将操作委托给 pipeline,利用责任链模式处理。
2.6 从 Tail 开始:DefaultChannelPipeline.connect()
// DefaultChannelPipeline
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
// connect 是 Outbound 事件,从 tail 开始传播
return tail.connect(remoteAddress, promise);
}
关键点:
- Outbound 事件从 tail 开始,向 head 方向传播
- 这样用户自定义的 OutboundHandler 可以先处理(比如日志、编码等)
2.7 查找下一个 Handler:AbstractChannelHandlerContext.connect()
// AbstractChannelHandlerContext
@Override
public ChannelFuture connect(
final SocketAddress remoteAddress,
final SocketAddress localAddress,
final ChannelPromise promise) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
if (isNotValidPromise(promise, false)) {
return promise;
}
// 【关键】从当前节点开始,向前查找下一个 Outbound 类型的 handler
final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
// 如果当前线程就是 EventLoop 线程,直接调用
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
// 否则提交到 EventLoop 执行
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
return promise;
}
关键点:
findContextOutbound(MASK_CONNECT):查找下一个支持 connect 操作的 Outbound handler- 从 tail 开始,依次向前查找,最终会找到 head
2.8 调用 Handler:AbstractChannelHandlerContext.invokeConnect()
// AbstractChannelHandlerContext
private void invokeConnect(
SocketAddress remoteAddress,
SocketAddress localAddress,
ChannelPromise promise) {
if (invokeHandler()) {
try {
// 调用当前 handler 的 connect 方法
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
// handler 还未添加完成,继续向前传播
connect(remoteAddress, localAddress, promise);
}
}
作用:调用 handler 的 connect 方法,让 handler 有机会处理或拦截 connect 操作。
2.9 到达 Head:HeadContext.connect()
经过 pipeline 的传播,最终会到达 head。
// HeadContext
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress,
SocketAddress localAddress,
ChannelPromise promise) {
// head 负责调用 unsafe 执行真正的底层操作
unsafe.connect(remoteAddress, localAddress, promise);
}
关键点:
- head 是 pipeline 的第一个节点,也是最后一个处理 Outbound 事件的节点
- head 负责调用 unsafe 执行底层操作
2.10 Unsafe 处理:AbstractNioUnsafe.connect()
这是 connect 的核心逻辑所在。
// AbstractNioChannel.AbstractNioUnsafe
@Override
public final void connect(
final SocketAddress remoteAddress,
final SocketAddress localAddress,
final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
try {
if (connectPromise != null) {
// 已经有一个 connect 正在进行中,抛出异常
throw new ConnectionPendingException();
}
boolean wasActive = isActive();
// 【核心】执行真正的 connect 操作
if (doConnect(remoteAddress, localAddress)) {
// 情况1:connect 立即成功(比如连接本地地址)
fulfillConnectPromise(promise, wasActive);
} else {
// 情况2:connect 正在进行中(TCP 三次握手需要时间)
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// 设置连接超时定时器
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
// 添加取消监听器
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}
关键点:
- 防止重复连接:检查是否已有 connect 正在进行
- 执行 doConnect():调用底层的连接方法
- 处理两种结果:
- 立即成功:直接完成 Promise
- 正在进行:设置超时定时器,等待连接完成
- 超时处理:使用 EventLoop 的定时任务功能
2.11 底层连接:NioSocketChannel.doConnect()
这是真正执行 JDK 底层连接的地方。
// NioSocketChannel
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
// 如果指定了本地地址,先绑定
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
// 【核心】调用 JDK 的 SocketChannel.connect()
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
// 连接未立即完成,注册 OP_CONNECT 事件
// NioEventLoop 会监听这个事件,当连接完成时会收到通知
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected; // 返回是否立即连接成功
} finally {
if (!success) {
doClose(); // 连接失败,关闭 channel
}
}
}
关键点:
- 可选的本地地址绑定:如果指定了 localAddress,先绑定
- 调用 JDK 底层方法:
SocketUtils.connect(javaChannel(), remoteAddress) - 返回值含义:
true:连接立即成功false:连接正在进行中(非阻塞模式下的正常情况)
- 注册 OP_CONNECT:如果连接未立即完成,注册 OP_CONNECT 事件,等待连接完成
2.12 JDK 底层调用:SocketUtils.connect()
// SocketUtils
public static boolean connect(final SocketChannel socketChannel, final SocketAddress remoteAddress)
throws IOException {
try {
// 调用 JDK 的 SocketChannel.connect() 方法
return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws IOException {
return socketChannel.connect(remoteAddress);
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
作用:
- 封装 JDK 的
SocketChannel.connect()方法 - 使用
AccessController处理安全权限
JDK SocketChannel.connect() 的行为:
- 阻塞模式:会阻塞直到连接成功或失败
- 非阻塞模式(Netty 使用的):
- 立即返回
true:连接立即成功(比如连接本地地址) - 立即返回
false:连接正在进行中,需要等待 OP_CONNECT 事件
- 立即返回
三、Connect 未立即完成的后续处理
当 doConnect() 返回 false 时,连接正在进行中,后续流程如下:
3.1 注册 OP_CONNECT 事件
// 在 NioSocketChannel.doConnect() 中
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
3.2 NioEventLoop 监听事件
// NioEventLoop.run() 方法会不断循环
protected void run() {
for (;;) {
// 1. select 操作,等待事件就绪
select(wakenUp.getAndSet(false));
// 2. 处理就绪的事件(包括 OP_CONNECT)
processSelectedKeys();
// 3. 处理任务队列
runAllTasks();
}
}
3.3 处理 OP_CONNECT 事件
// NioEventLoop.processSelectedKey()
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
// 移除 OP_CONNECT,避免重复触发
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
// 调用 unsafe.finishConnect() 完成连接
unsafe.finishConnect();
}
3.4 完成连接:finishConnect()
// AbstractNioChannel.AbstractNioUnsafe
@Override
public final void finishConnect() {
try {
boolean wasActive = isActive();
// 调用 JDK 的 finishConnect() 完成连接
doFinishConnect();
// 完成 Promise,触发监听器
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
// 取消超时定时器
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
}
}
3.5 触发 channelActive 事件
// AbstractNioChannel.AbstractNioUnsafe.fulfillConnectPromise()
private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
// ...
// 设置 Promise 为成功
promise.trySuccess();
// 如果之前不是 active,现在变成 active 了
if (!wasActive && isActive()) {
// 触发 channelActive 事件(Inbound 事件,从 head 开始传播)
pipeline().fireChannelActive();
}
}
3.6 开始监听 OP_READ
// DefaultChannelPipeline.HeadContext.channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 向后传播 channelActive 事件
ctx.fireChannelActive();
// 开始读取数据(注册 OP_READ 事件)
readIfIsAutoRead();
}
四、完整流程图
用户代码: bootstrap.connect("localhost", 8080)
↓
Bootstrap.connect(String, int)
↓
Bootstrap.connect(SocketAddress)
↓
Bootstrap.doResolveAndConnect()
├─→ initAndRegister() [创建 Channel、注册到 Selector]
└─→ doResolveAndConnect0()
↓
Bootstrap.doConnect() [提交任务到 EventLoop]
↓
AbstractChannel.connect()
↓
DefaultChannelPipeline.connect() [从 tail 开始]
↓
TailContext.connect()
↓
findContextOutbound() [查找下一个 Outbound handler]
↓
自定义 OutboundHandler.connect() [如果有]
↓
HeadContext.connect()
↓
AbstractNioUnsafe.connect()
↓
NioSocketChannel.doConnect()
↓
SocketUtils.connect() [JDK SocketChannel.connect()]
↓
┌────────┴────────┐
↓ ↓
立即成功 正在进行中
↓ ↓
fulfillConnectPromise 注册 OP_CONNECT
↓ ↓
触发 channelActive NioEventLoop 监听
↓ ↓
注册 OP_READ 收到 OP_CONNECT 事件
↓
finishConnect()
↓
fulfillConnectPromise
↓
触发 channelActive
↓
注册 OP_READ
五、关键点总结
5.1 Connect 的前置条件
- 必须先 register:Channel 必须先注册到 Selector,才能进行 connect
- 在 EventLoop 线程中执行:connect 操作必须在 EventLoop 线程中执行
5.2 Connect 的两种结果
| 结果 | 返回值 | 场景 | 后续处理 |
|---|---|---|---|
| 立即成功 | true | 连接本地地址 | 直接完成 Promise,触发 channelActive |
| 正在进行中 | false | 连接远程地址(常见) | 注册 OP_CONNECT,等待事件,调用 finishConnect() |
5.3 Pipeline 的传播方向
- Outbound 事件(connect、bind、write):tail → head
- Inbound 事件(channelActive、channelRead):head → tail
5.4 异步处理机制
- 提交任务到 EventLoop:
eventLoop().execute() - 注册感兴趣的事件:
selectionKey().interestOps(OP_CONNECT) - EventLoop 监听事件:
select()+processSelectedKeys() - 事件就绪后处理:
finishConnect() - 触发回调:
fulfillConnectPromise()→channelActive
5.5 超时处理
// 使用 EventLoop 的定时任务功能
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
// 超时后,尝试将 Promise 设置为失败
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
六、常见问题
Q1:为什么 connect 要在 EventLoop 线程中执行?
A:
- 线程安全:避免多线程并发操作 Channel
- 事件顺序:保证事件按提交顺序执行
- 简化模型:所有 IO 操作都在同一个线程中,无需加锁
Q2:为什么 connect 可能立即成功?
A:
- 连接本地地址(localhost、127.0.0.1)时,不需要网络传输,可以立即完成
- 某些操作系统优化,对于本地连接会立即返回成功
Q3:如果 connect 超时会怎样?
A:
- 定时器触发,调用
connectPromise.tryFailure() - 如果 Promise 还未完成,设置为失败状态
- 关闭 Channel
- 用户通过
ChannelFuture可以获取到超时异常
Q4:connect 失败后会自动重试吗?
A:
- Netty 本身不会自动重试
- 需要用户自己实现重试逻辑,通常通过监听
ChannelFuture实现
ChannelFuture future = bootstrap.connect("localhost", 8080);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) {
if (!f.isSuccess()) {
// 连接失败,延迟后重试
f.channel().eventLoop().schedule(() -> {
bootstrap.connect("localhost", 8080);
}, 5, TimeUnit.SECONDS);
}
}
});
七、总结
Connect 过程的核心要点:
- 调用链路长但清晰:从 Bootstrap 到 Pipeline 到 Unsafe 到 JDK
- 异步处理是常态:通过 EventLoop + Selector + interestOps 实现
- Pipeline 是核心:所有操作都通过 Pipeline 传播,支持用户自定义处理
- 两种结果处理:立即成功和正在进行中,分别有不同的处理流程
- 完善的异常处理:超时、取消、失败都有相应的处理机制
理解 connect 过程,就理解了 Netty 异步 IO 的核心机制!
11万+



被折叠的 条评论
为什么被折叠?



