connect和bind过程详解(一)
Connect与Bind对比总结
Netty Bind 过程详解(服务端启动)
一、Bind 完整调用链路
从服务端代码 serverBootstrap.bind(port) 开始,整个调用链路如下:
ServerBootstrap.bind(port)
↓
ServerBootstrap.bind(SocketAddress)
↓
AbstractBootstrap.doBind() // 先 register,再 bind
↓
AbstractBootstrap.initAndRegister() // 创建 Channel 并注册
↓
AbstractBootstrap.doBind0() // 提交 bind 任务到 EventLoop
↓
AbstractChannel.bind() // 交给 pipeline
↓
DefaultChannelPipeline.bind() // 从 tail 开始
↓
TailContext.bind()
↓
AbstractChannelHandlerContext.findContextOutbound() // 找下一个 Outbound handler
↓
AbstractChannelHandlerContext.invokeBind() // 调用 handler 的 bind 方法
↓
HeadContext.bind() // 最终到达 head
↓
AbstractChannel.AbstractUnsafe.bind() // 调用 unsafe
↓
NioServerSocketChannel.doBind() // 执行真正的绑定
↓
ServerSocketChannel.bind() // JDK 底层绑定端口
二、详细代码流程解析
2.1 入口:ServerBootstrap.bind()
用户代码:
// 服务端启动代码
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new MyChannelInitializer());
// 【入口】绑定端口
ChannelFuture f = b.bind(7397).sync();
第一步:端口转换
// AbstractBootstrap
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
第二步:地址校验
// AbstractBootstrap
public ChannelFuture bind(SocketAddress localAddress) {
validate(); // 校验配置
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
2.2 核心方法:doBind()
这个方法做两件事:
- initAndRegister():创建 ServerSocketChannel 并注册到 Selector
- doBind0():绑定端口
// AbstractBootstrap
private ChannelFuture doBind(final SocketAddress localAddress) {
// 【重要】先完成 Channel 的创建和注册
// 这一步会:创建 NioServerSocketChannel、初始化 Pipeline、注册到 Selector
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// 情况1:register 已经完成(同步完成)
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 情况2:register 还未完成(异步完成)
// 添加监听器,等 register 完成后再执行 bind
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();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
关键点:
- bind 必须在 register 之后执行
- register 可能同步完成,也可能异步完成
- 如果异步,通过监听器等待 register 完成
2.3 提交任务:doBind0()
这个方法将 bind 操作提交到 EventLoop 的任务队列中执行。
// AbstractBootstrap
private static void doBind0(
final ChannelFuture regFuture,
final Channel channel,
final SocketAddress localAddress,
final ChannelPromise promise) {
// 【关键】将 bind 操作提交到 EventLoop 的任务队列
// 这样可以确保 bind 在 EventLoop 线程中执行
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
// 调用 Channel 的 bind 方法
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
关键点:
- bind 操作必须在 EventLoop 线程中执行
- 通过
eventLoop().execute()提交任务 - 添加失败监听器,自动清理资源
2.4 交给 Pipeline:AbstractChannel.bind()
// AbstractChannel
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
// 将 bind 操作交给 pipeline 处理
return pipeline.bind(localAddress, promise);
}
作用:将操作委托给 pipeline,利用责任链模式处理。
2.5 从 Tail 开始:DefaultChannelPipeline.bind()
// DefaultChannelPipeline
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
// bind 是 Outbound 事件,从 tail 开始传播
return tail.bind(localAddress, promise);
}
关键点:
- Outbound 事件从 tail 开始,向 head 方向传播
- 这样用户自定义的 OutboundHandler 可以先处理
2.6 查找下一个 Handler:AbstractChannelHandlerContext.bind()
// AbstractChannelHandlerContext
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (isNotValidPromise(promise, false)) {
return promise;
}
// 【关键】从当前节点开始,向前查找下一个 Outbound 类型的 handler
final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
2.7 调用 Handler:AbstractChannelHandlerContext.invokeBind()
// AbstractChannelHandlerContext
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
// 调用当前 handler 的 bind 方法
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
2.8 到达 Head:HeadContext.bind()
经过 pipeline 的传播,最终会到达 head。
// HeadContext
@Override
public void bind(
ChannelHandlerContext ctx,
SocketAddress localAddress,
ChannelPromise promise) {
// head 负责调用 unsafe 执行真正的底层操作
unsafe.bind(localAddress, promise);
}
2.9 Unsafe 处理:AbstractUnsafe.bind()
这是 bind 的核心逻辑所在。
// AbstractChannel.AbstractUnsafe
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean wasActive = isActive();
try {
// 【核心】执行真正的 bind 操作
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// bind 成功后,如果 channel 变为 active 状态
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
// 触发 channelActive 事件
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
关键点:
- 执行 doBind():调用底层的绑定方法
- 触发 channelActive:绑定成功后,触发 channelActive 事件
- 完成 Promise:设置 Promise 为成功状态
2.10 底层绑定:NioServerSocketChannel.doBind()
这是真正执行 JDK 底层绑定的地方。
// NioServerSocketChannel
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
// JDK 7+ 的绑定方式
javaChannel().bind(localAddress, config.getBacklog());
} else {
// JDK 6 的绑定方式
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
关键点:
- 调用 JDK 底层方法:
ServerSocketChannel.bind(localAddress, backlog) - backlog 参数:TCP 连接队列的大小(通过
SO_BACKLOG选项设置) - 同步操作:bind 是同步的,会立即完成
2.11 JDK 底层调用:ServerSocketChannel.bind()
// JDK ServerSocketChannel
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
synchronized (lock) {
// 检查状态
if (!isOpen())
throw new ClosedChannelException();
if (isBound())
throw new AlreadyBoundException();
// 绑定端口
InetSocketAddress isa = (local == null) ?
new InetSocketAddress(0) : Net.checkAddress(local);
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkListen(isa.getPort());
// 调用底层 native 方法绑定
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
Net.bind(fd, isa.getAddress(), isa.getPort());
Net.listen(fd, backlog < 1 ? 50 : backlog);
synchronized (stateLock) {
localAddress = Net.localAddress(fd);
}
}
return this;
}
作用:
- 调用操作系统的 bind 和 listen 系统调用
- 绑定端口并开始监听连接请求
三、Bind 完成后的处理
3.1 触发 channelActive 事件
// AbstractUnsafe.bind() 中
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
3.2 开始监听 OP_ACCEPT
// DefaultChannelPipeline.HeadContext.channelActive()
@Override
public void channelActive(ChannelHandlerContext ctx) {
// 向后传播 channelActive 事件
ctx.fireChannelActive();
// 开始读取(对于 ServerSocketChannel 就是监听 OP_ACCEPT)
readIfIsAutoRead();
}
3.3 注册 OP_ACCEPT 事件
// AbstractNioChannel.doBeginRead()
@Override
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
// 对于 NioServerSocketChannel,readInterestOp 是 OP_ACCEPT
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
3.4 NioEventLoop 监听 OP_ACCEPT
// NioEventLoop.run() 方法会不断循环
protected void run() {
for (;;) {
// 1. select 操作,等待事件就绪
select(wakenUp.getAndSet(false));
// 2. 处理就绪的事件(包括 OP_ACCEPT)
processSelectedKeys();
// 3. 处理任务队列
runAllTasks();
}
}
3.5 处理 OP_ACCEPT 事件
// NioEventLoop.processSelectedKey()
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
// 调用 unsafe.read() 接受新连接
unsafe.read();
}
3.6 接受新连接:NioMessageUnsafe.read()
// AbstractNioMessageChannel.NioMessageUnsafe
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
// 【核心】接受新连接,创建 NioSocketChannel
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 触发 channelRead 事件,传递新连接
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
四、完整流程图
用户代码: serverBootstrap.bind(7397)
↓
ServerBootstrap.bind(int)
↓
AbstractBootstrap.bind(SocketAddress)
↓
AbstractBootstrap.doBind()
├─→ initAndRegister() [创建 NioServerSocketChannel、注册到 Selector]
└─→ doBind0()
↓
AbstractBootstrap.doBind0() [提交任务到 EventLoop]
↓
AbstractChannel.bind()
↓
DefaultChannelPipeline.bind() [从 tail 开始]
↓
TailContext.bind()
↓
findContextOutbound() [查找下一个 Outbound handler]
↓
自定义 OutboundHandler.bind() [如果有]
↓
HeadContext.bind()
↓
AbstractUnsafe.bind()
↓
NioServerSocketChannel.doBind()
↓
ServerSocketChannel.bind() [JDK 底层绑定端口]
↓
绑定成功
↓
触发 channelActive 事件
↓
注册 OP_ACCEPT 事件
↓
NioEventLoop 监听 OP_ACCEPT
↓
收到客户端连接请求
↓
doReadMessages() [接受连接,创建 NioSocketChannel]
↓
触发 channelRead 事件 [传递新连接]
↓
ServerBootstrapAcceptor 处理新连接
↓
将新连接注册到 workerGroup
五、关键点总结
5.1 Bind 的前置条件
- 必须先 register:ServerSocketChannel 必须先注册到 Selector,才能进行 bind
- 在 EventLoop 线程中执行:bind 操作必须在 EventLoop 线程中执行
5.2 Bind 的特点
| 对比项 | Bind(服务端) | Connect(客户端) |
|---|---|---|
| Channel 类型 | NioServerSocketChannel | NioSocketChannel |
| JDK 底层操作 | ServerSocketChannel.bind() | SocketChannel.connect() |
| 是否异步 | 否(同步完成) | 是(可能异步) |
| 监听的事件 | OP_ACCEPT | OP_CONNECT → OP_READ |
| 完成后的操作 | 开始接受连接 | finishConnect() |
5.3 Pipeline 的传播方向
- Outbound 事件(bind、connect、write):tail → head
- Inbound 事件(channelActive、channelRead):head → tail
5.4 Bind 后的事件流
- bind 完成 → 触发
channelActive事件 - channelActive → 调用
readIfIsAutoRead() - readIfIsAutoRead() → 注册
OP_ACCEPT事件 - OP_ACCEPT 就绪 → 调用
unsafe.read() - unsafe.read() → 调用
doReadMessages()接受新连接 - doReadMessages() → 创建
NioSocketChannel - 触发 channelRead → 传递新连接给
ServerBootstrapAcceptor - ServerBootstrapAcceptor → 将新连接注册到
workerGroup
六、Bind 与 Connect 的对比
6.1 相同点
- 都必须先 register:Channel 必须先注册到 Selector
- 都在 EventLoop 线程中执行:保证线程安全
- 都通过 Pipeline 传播:从 tail 到 head
- 都是 Outbound 事件:用户主动发起的操作
6.2 不同点
-
同步 vs 异步:
- bind 是同步的,立即完成
- connect 可能是异步的,需要等待 OP_CONNECT 事件
-
监听的事件:
- bind 后监听 OP_ACCEPT(接受新连接)
- connect 后监听 OP_READ(读取数据)
-
Channel 类型:
- bind 使用 NioServerSocketChannel
- connect 使用 NioSocketChannel
-
后续处理:
- bind 后接受新连接,创建 NioSocketChannel
- connect 后开始读写数据
七、ServerBootstrapAcceptor 详解
7.1 什么是 ServerBootstrapAcceptor?
ServerBootstrapAcceptor 是一个特殊的 ChannelInboundHandler,它的作用是:
- 接收 boss 线程接受的新连接(NioSocketChannel)
- 为新连接配置 childHandler 和 childOptions
- 将新连接注册到 workerGroup
7.2 ServerBootstrapAcceptor 的添加时机
在 ServerBootstrap.init(channel) 方法中添加:
// ServerBootstrap.init()
@Override
void init(Channel channel) throws Exception {
// ...省略其他代码
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
// ...省略代码
// 【关键】添加 ServerBootstrapAcceptor
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler,
currentChildOptions, currentChildAttrs));
}
});
}
});
}
7.3 ServerBootstrapAcceptor 的工作流程
// ServerBootstrap.ServerBootstrapAcceptor
private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {
private final EventLoopGroup childGroup;
private final ChannelHandler childHandler;
private final Entry<ChannelOption<?>, Object>[] childOptions;
private final Entry<AttributeKey<?>, Object>[] childAttrs;
ServerBootstrapAcceptor(
final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
this.childGroup = childGroup;
this.childHandler = childHandler;
this.childOptions = childOptions;
this.childAttrs = childAttrs;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// msg 就是新接受的 NioSocketChannel
final Channel child = (Channel) msg;
// 1. 为新连接添加 childHandler
child.pipeline().addLast(childHandler);
// 2. 设置新连接的 options
setChannelOptions(child, childOptions, logger);
// 3. 设置新连接的 attributes
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
// 4. 【关键】将新连接注册到 workerGroup
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
}
八、完整的服务端启动流程
1. 创建 bossGroup 和 workerGroup
↓
2. 创建 ServerBootstrap 并配置
↓
3. 调用 bind(port)
↓
4. initAndRegister()
├─ 创建 NioServerSocketChannel
├─ 初始化 Pipeline(添加 ServerBootstrapAcceptor)
└─ 注册到 bossGroup 的某个 EventLoop
↓
5. doBind0()
└─ 绑定端口
↓
6. 触发 channelActive
└─ 注册 OP_ACCEPT 事件
↓
7. bossGroup 的 EventLoop 监听 OP_ACCEPT
↓
8. 客户端发起连接
↓
9. OP_ACCEPT 事件就绪
↓
10. doReadMessages()
└─ 接受连接,创建 NioSocketChannel
↓
11. 触发 channelRead 事件
└─ 传递新连接给 ServerBootstrapAcceptor
↓
12. ServerBootstrapAcceptor 处理
├─ 为新连接添加 childHandler
├─ 设置 childOptions 和 childAttrs
└─ 将新连接注册到 workerGroup
↓
13. workerGroup 的 EventLoop 处理新连接的 IO
九、常见问题
Q1:为什么 bind 是同步的,而 connect 可能是异步的?
A:
- Bind:只是在本地绑定端口,不涉及网络通信,操作系统可以立即完成
- Connect:需要进行 TCP 三次握手,涉及网络传输,需要时间
Q2:SO_BACKLOG 参数的作用是什么?
A:
- SO_BACKLOG 指定了 TCP 连接队列的大小
- 当服务端来不及处理新连接时,新连接会暂存在这个队列中
- 如果队列满了,新的连接请求会被拒绝
示例:
serverBootstrap.option(ChannelOption.SO_BACKLOG, 128);
Q3:bossGroup 和 workerGroup 的区别是什么?
A:
- bossGroup:负责接受新连接(处理 OP_ACCEPT 事件)
- workerGroup:负责处理已建立连接的 IO 操作(处理 OP_READ、OP_WRITE 事件)
类比:
- bossGroup 像餐厅的迎宾员,负责迎接客人
- workerGroup 像餐厅的服务员,负责为客人提供服务
Q4:为什么 bossGroup 通常只需要 1 个线程?
A:
- 一个服务端通常只监听一个端口
- 接受连接的操作很快,一个线程足够
- 真正耗时的 IO 操作由 workerGroup 处理
示例:
// bossGroup 只需要 1 个线程
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// workerGroup 根据 CPU 核心数设置
EventLoopGroup workerGroup = new NioEventLoopGroup();
十、总结
Bind 过程的核心要点:
- 调用链路清晰:从 ServerBootstrap 到 Pipeline 到 Unsafe 到 JDK
- 同步操作:bind 是同步的,会立即完成
- Pipeline 传播:所有操作都通过 Pipeline 传播(tail → head)
- 触发 channelActive:bind 成功后触发 channelActive,开始监听 OP_ACCEPT
- 接受新连接:通过 ServerBootstrapAcceptor 将新连接注册到 workerGroup
- 主从 Reactor 模式:bossGroup 负责接受连接,workerGroup 负责处理 IO
Netty Connect 与 Bind 对比总结
一、快速对比表
| 对比项 | Connect(客户端) | Bind(服务端) |
|---|---|---|
| 使用场景 | 客户端连接服务端 | 服务端绑定端口 |
| Channel 类型 | NioSocketChannel | NioServerSocketChannel |
| JDK 底层操作 | SocketChannel.connect() | ServerSocketChannel.bind() |
| 是否异步 | 是(可能异步) | 否(同步完成) |
| 返回值含义 | true=立即成功, false=正在进行 | 无返回值(同步) |
| 监听的事件 | OP_CONNECT → OP_READ | OP_ACCEPT |
| 完成后的操作 | finishConnect() → 读写数据 | 接受新连接 |
| EventLoopGroup | 单个 workerGroup | bossGroup + workerGroup |
| Pipeline 传播 | tail → head(Outbound) | tail → head(Outbound) |
| 超时处理 | 有(可配置连接超时) | 无(立即完成) |
二、调用链路对比
2.1 Connect 调用链路
Bootstrap.connect(host, port)
↓
doResolveAndConnect()
├─→ initAndRegister() [创建 NioSocketChannel]
└─→ doResolveAndConnect0() [DNS 解析]
↓
doConnect() [提交任务]
↓
pipeline.connect() [tail → head]
↓
unsafe.connect()
↓
NioSocketChannel.doConnect()
↓
SocketChannel.connect() [JDK]
↓
┌────────┴────────┐
↓ ↓
立即成功 正在进行中
↓ ↓
完成Promise 注册 OP_CONNECT
↓ ↓
channelActive 等待事件就绪
↓ ↓
注册 OP_READ finishConnect()
↓
完成Promise
↓
channelActive
↓
注册 OP_READ
2.2 Bind 调用链路
ServerBootstrap.bind(port)
↓
doBind()
├─→ initAndRegister() [创建 NioServerSocketChannel]
└─→ doBind0() [提交任务]
↓
pipeline.bind() [tail → head]
↓
unsafe.bind()
↓
NioServerSocketChannel.doBind()
↓
ServerSocketChannel.bind() [JDK]
↓
绑定成功(同步)
↓
完成Promise
↓
channelActive
↓
注册 OP_ACCEPT
↓
等待客户端连接
↓
OP_ACCEPT 就绪
↓
doReadMessages() [接受连接]
↓
创建 NioSocketChannel
↓
channelRead 事件
↓
ServerBootstrapAcceptor
↓
注册到 workerGroup
三、相同点
3.1 都必须先 Register
无论是 connect 还是 bind,都必须先完成 register 操作:
// 都会先调用 initAndRegister()
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 等待 register 完成后,再执行 connect 或 bind
if (regFuture.isDone()) {
// register 已完成
doXxx(channel, ...);
} else {
// register 还未完成,添加监听器
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
doXxx(channel, ...);
}
});
}
3.2 都在 EventLoop 线程中执行
// Connect
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.connect(remoteAddress, connectPromise);
}
});
// Bind
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
channel.bind(localAddress, promise);
}
});
3.3 都通过 Pipeline 传播(Outbound 事件)
// 都是从 tail 开始,向 head 传播
pipeline.connect(remoteAddress, promise); // Connect
pipeline.bind(localAddress, promise); // Bind
// 传播路径:tail → 自定义 OutboundHandler → head
3.4 都由 Head 调用 Unsafe
// HeadContext
@Override
public void connect(...) {
unsafe.connect(remoteAddress, localAddress, promise);
}
@Override
public void bind(...) {
unsafe.bind(localAddress, promise);
}
3.5 都会触发 channelActive 事件
// 完成后都会触发 channelActive
if (!wasActive && isActive()) {
pipeline.fireChannelActive();
}
四、不同点详解
4.1 同步 vs 异步
Connect(异步):
// 调用 JDK 的 connect,可能返回 false
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
// 连接正在进行中,注册 OP_CONNECT 事件
selectionKey().interestOps(SelectionKey.OP_CONNECT);
// 等待 NioEventLoop 监听到 OP_CONNECT 事件
// 然后调用 finishConnect() 完成连接
}
Bind(同步):
// 调用 JDK 的 bind,立即完成
javaChannel().bind(localAddress, config.getBacklog());
// 没有返回值,绑定立即完成
4.2 监听的事件不同
Connect 后监听 OP_READ:
// 连接完成后,开始监听 OP_READ,准备读取数据
selectionKey().interestOps(SelectionKey.OP_READ);
Bind 后监听 OP_ACCEPT:
// 绑定完成后,开始监听 OP_ACCEPT,准备接受新连接
selectionKey().interestOps(SelectionKey.OP_ACCEPT);
4.3 后续处理不同
Connect 后的处理:
// 1. 连接完成
finishConnect();
// 2. 触发 channelActive
pipeline.fireChannelActive();
// 3. 开始读写数据
// 用户的 handler 可以开始发送和接收数据
Bind 后的处理:
// 1. 绑定完成
doBind(localAddress);
// 2. 触发 channelActive
pipeline.fireChannelActive();
// 3. 开始接受新连接
// 当有客户端连接时:
doReadMessages(readBuf); // 接受连接,创建 NioSocketChannel
pipeline.fireChannelRead(child); // 传递给 ServerBootstrapAcceptor
childGroup.register(child); // 注册到 workerGroup
4.4 EventLoopGroup 的使用
Connect(客户端):
// 只需要一个 EventLoopGroup
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(workerGroup) // 只设置一个 group
.channel(NioSocketChannel.class);
Bind(服务端):
// 需要两个 EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接受连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理 IO
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) // 设置两个 group
.channel(NioServerSocketChannel.class);
五、代码示例对比
5.1 客户端 Connect 示例
public class NettyClient {
public static void main(String[] args) throws Exception {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.AUTO_READ, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyClientHandler());
}
});
// 【Connect】连接服务端
ChannelFuture f = b.connect("localhost", 8080).sync();
System.out.println("客户端连接成功");
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
5.2 服务端 Bind 示例
public class NettyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyServerHandler());
}
});
// 【Bind】绑定端口
ChannelFuture f = b.bind(8080).sync();
System.out.println("服务端启动成功,监听端口:8080");
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
六、常见问题对比
Q1:为什么 Connect 可能是异步的,而 Bind 是同步的?
A:
-
Connect:需要进行 TCP 三次握手,涉及网络传输,需要时间
客户端 ---SYN---> 服务端 客户端 <--SYN+ACK--- 服务端 客户端 ---ACK---> 服务端 -
Bind:只是在本地绑定端口,不涉及网络通信,操作系统可以立即完成
Q2:为什么服务端需要两个 EventLoopGroup?
A:
-
bossGroup:专门负责接受新连接(处理 OP_ACCEPT)
- 通常只需要 1 个线程
- 接受连接的操作很快
-
workerGroup:负责处理已建立连接的 IO 操作
- 需要多个线程(根据 CPU 核心数)
- IO 操作比较耗时
类比:
- bossGroup = 餐厅的迎宾员(迎接客人)
- workerGroup = 餐厅的服务员(为客人服务)
Q3:Connect 超时了怎么办?
A:
Bootstrap b = new Bootstrap();
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); // 设置连接超时 5 秒
ChannelFuture future = b.connect("localhost", 8080);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) {
if (!f.isSuccess()) {
System.out.println("连接失败:" + f.cause());
// 可以实现重试逻辑
}
}
});
Q4:Bind 失败了怎么办?
A:
try {
ChannelFuture f = serverBootstrap.bind(8080).sync();
} catch (Exception e) {
// 可能的原因:
// 1. 端口已被占用
// 2. 没有权限绑定该端口(如 80 端口需要 root 权限)
// 3. 地址不可用
System.out.println("绑定失败:" + e.getMessage());
// 可以尝试绑定其他端口
serverBootstrap.bind(8081).sync();
}
七、性能优化建议
7.1 客户端优化
Bootstrap b = new Bootstrap();
b.group(workerGroup)
.channel(NioSocketChannel.class)
// 1. 启用 TCP_NODELAY,禁用 Nagle 算法,减少延迟
.option(ChannelOption.TCP_NODELAY, true)
// 2. 启用 SO_KEEPALIVE,保持长连接
.option(ChannelOption.SO_KEEPALIVE, true)
// 3. 设置连接超时
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
// 4. 设置发送缓冲区大小
.option(ChannelOption.SO_SNDBUF, 1024 * 64)
// 5. 设置接收缓冲区大小
.option(ChannelOption.SO_RCVBUF, 1024 * 64);
7.2 服务端优化
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 1. 设置 TCP 连接队列大小
.option(ChannelOption.SO_BACKLOG, 1024)
// 2. 启用地址重用(服务重启时快速绑定端口)
.option(ChannelOption.SO_REUSEADDR, true)
// 3. 为子 Channel 启用 TCP_NODELAY
.childOption(ChannelOption.TCP_NODELAY, true)
// 4. 为子 Channel 启用 SO_KEEPALIVE
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 5. 设置子 Channel 的缓冲区大小
.childOption(ChannelOption.SO_SNDBUF, 1024 * 64)
.childOption(ChannelOption.SO_RCVBUF, 1024 * 64);
八、总结
8.1 Connect 的核心要点
- 异步操作:可能需要等待 OP_CONNECT 事件
- 单 EventLoopGroup:只需要 workerGroup
- 连接完成后:开始读写数据
- 超时处理:可配置连接超时时间
- 重试机制:需要用户自己实现
8.2 Bind 的核心要点
- 同步操作:立即完成,无需等待
- 双 EventLoopGroup:bossGroup + workerGroup
- 绑定完成后:开始接受新连接
- 主从 Reactor:bossGroup 接受连接,workerGroup 处理 IO
- ServerBootstrapAcceptor:负责将新连接注册到 workerGroup
8.3 共同点
- 都必须先 register
- 都在 EventLoop 线程中执行
- 都通过 Pipeline 传播(Outbound 事件)
- 都由 Head 调用 Unsafe
- 都会触发 channelActive 事件
2万+



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



