NioEventLoop 事件检测与触发机制总结
需要区分两种情况:
-
服务端 ServerSocketChannel(监听新连接)
- 当有新连接到达时,触发
pipeline.fireChannelRead(socketChannel) - 注意:传递的是
SocketChannel对象,不是ByteBuf
- 当有新连接到达时,触发
-
客户端/服务端的 SocketChannel(数据传输)
- 当有数据可读时,触发
pipeline.fireChannelRead(byteBuf) - 这里传递的才是
ByteBuf对象
- 当有数据可读时,触发
完整的理解框架
1. Register 之后发生了什么?
// Channel 注册到 EventLoop
channel.register(eventLoop);
↓
// EventLoop 的 run() 方法开始执行(如果还没启动)
NioEventLoop.run() {
for (;;) { // 无限循环
// 1. 等待事件(阻塞或非阻塞)
select(wakenUp.getAndSet(false));
// 2. 处理 I/O 事件
processSelectedKeys();
// 3. 处理任务队列
runAllTasks();
}
}
关键点:
run()方法是一个无限循环- 只要 EventLoop 不关闭,就会一直运行
- 每次循环都会检测事件、处理事件、执行任务
2. processSelectedKeys() 检测所有就绪的事件
private void processSelectedKeys() {
// 遍历所有就绪的 SelectionKey
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
final Object a = k.attachment(); // 获取 Channel
if (a instanceof AbstractNioChannel) {
// 处理这个 Channel 的事件
processSelectedKey(k, (AbstractNioChannel) a);
}
}
}
关键点:
- 检测的是所有注册到这个 EventLoop 的 Channel
- 每个就绪的 Channel 都会被处理
3. 根据事件类型触发不同的操作
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
int readyOps = k.readyOps(); // 获取就绪的操作类型
// 情况 1:OP_CONNECT(客户端连接完成)
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
unsafe.finishConnect();
// 最终触发:pipeline.fireChannelActive()
}
// 情况 2:OP_WRITE(可写)
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
// 情况 3:OP_ACCEPT(服务端接受新连接)
// 情况 4:OP_READ(有数据可读)
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
unsafe.read(); // ← 这里会触发 fireChannelRead
}
}
详细区分:不同 Channel 类型的处理
场景 1:服务端 ServerSocketChannel(OP_ACCEPT)
代码流程
// NioServerSocketChannel.NioServerSocketChannelUnsafe
public void read() {
List<Object> readBuf = new ArrayList<>();
try {
do {
// 接受新连接,返回 SocketChannel
int localRead = doReadMessages(readBuf);
if (localRead == 0) break;
} while (allocHandle.continueReading());
// 遍历所有新接受的连接
for (int i = 0; i < readBuf.size(); i++) {
// 触发 channelRead 事件
// 注意:msg 是 NioSocketChannel,不是 ByteBuf!
pipeline.fireChannelRead(readBuf.get(i)); // ← 传递的是 SocketChannel
}
// 触发 channelReadComplete 事件
pipeline.fireChannelReadComplete();
} catch (Throwable t) {
pipeline.fireExceptionCaught(t);
}
}
实际示例
// 服务端的 Handler
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// msg 是 NioSocketChannel(新连接)
SocketChannel child = (SocketChannel) msg;
System.out.println("接受新连接: " + child.remoteAddress());
// 为新连接配置 Pipeline
child.pipeline().addLast(new ChildHandler());
// 继续传播
ctx.fireChannelRead(msg);
}
}
关键点:
- 事件类型:
OP_ACCEPT - 触发的方法:
pipeline.fireChannelRead(socketChannel) - 传递的对象:
NioSocketChannel(新连接) - 发生在:
ServerSocketChannel(服务端监听 Channel)
场景 2:客户端/服务端 SocketChannel(OP_READ)
代码流程
// NioSocketChannel.NioByteUnsafe
public final void read() {
ByteBuf byteBuf = null;
try {
do {
// 分配 ByteBuf
byteBuf = allocHandle.allocate(allocator);
// 从 Socket 读取数据到 ByteBuf
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
byteBuf.release();
byteBuf = null;
break;
}
// 触发 channelRead 事件
// 注意:msg 是 ByteBuf,包含读取的数据!
pipeline.fireChannelRead(byteBuf); // ← 传递的是 ByteBuf
byteBuf = null;
} while (allocHandle.continueReading());
// 触发 channelReadComplete 事件
pipeline.fireChannelReadComplete();
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
}
}
实际示例
// 客户端/服务端的数据处理 Handler
public class DataHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// msg 是 ByteBuf(数据)
ByteBuf buf = (ByteBuf) msg;
String message = buf.toString(CharsetUtil.UTF_8);
System.out.println("收到数据: " + message);
// 继续传播
ctx.fireChannelRead(msg);
}
}
关键点:
- 事件类型:
OP_READ - 触发的方法:
pipeline.fireChannelRead(byteBuf) - 传递的对象:
ByteBuf(数据缓冲区) - 发生在:
SocketChannel(数据传输 Channel)
完整的事件类型对照表
| 事件类型 | Channel 类型 | 触发的 Pipeline 方法 | 传递的对象 | 说明 |
|---|---|---|---|---|
| OP_ACCEPT | ServerSocketChannel | fireChannelRead(socketChannel) | NioSocketChannel | 新连接到达 |
| OP_READ | SocketChannel | fireChannelRead(byteBuf) | ByteBuf | 数据可读 |
| OP_CONNECT | SocketChannel | fireChannelActive() | 无 | 连接建立(客户端) |
| OP_WRITE | SocketChannel | 不触发 fire 方法 | 无 | 可写(直接 flush) |
典型的 Netty 服务端架构
1. 两个 EventLoopGroup
// Boss Group:处理新连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// Worker Group:处理数据读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new DataHandler());
}
});
2. 两个 Channel 类型
┌─────────────────────────────────────────────────────────┐
│ ServerSocketChannel (监听 8080 端口) │
│ - 注册到 bossGroup 的 EventLoop │
│ - 监听 OP_ACCEPT 事件 │
│ - 当有新连接时,触发 fireChannelRead(socketChannel) │
└─────────────────────────────────────────────────────────┘
↓ 接受新连接
┌─────────────────────────────────────────────────────────┐
│ SocketChannel (客户端连接) │
│ - 注册到 workerGroup 的 EventLoop │
│ - 监听 OP_READ 事件 │
│ - 当有数据时,触发 fireChannelRead(byteBuf) │
└─────────────────────────────────────────────────────────┘
3. 两个 Pipeline
ServerSocketChannel 的 Pipeline:
head -> ServerBootstrapAcceptor -> tail
↑
└─ 接收 SocketChannel,注册到 workerGroup
SocketChannel 的 Pipeline:
head -> Decoder -> Handler -> Encoder -> tail
↑
└─ 接收 ByteBuf,处理业务逻辑
完整的事件流程示例
场景:客户端连接并发送 “Hello”
1. 客户端发起连接
↓
2. 服务端 ServerSocketChannel 检测到 OP_ACCEPT
↓
3. bossGroup 的 EventLoop 处理
NioEventLoop.run()
└─ processSelectedKeys()
└─ processSelectedKey(k, serverSocketChannel)
└─ unsafe.read() // NioServerSocketChannelUnsafe
└─ SocketChannel child = serverSocket.accept()
└─ pipeline.fireChannelRead(child) ← 传递 SocketChannel
↓
4. ServerBootstrapAcceptor 接收新连接
└─ child.register(workerGroup.next()) // 注册到 worker
↓
5. SocketChannel 注册到 workerGroup
└─ pipeline.fireChannelActive() // 连接激活
↓
6. 客户端发送 "Hello"
↓
7. SocketChannel 检测到 OP_READ
↓
8. workerGroup 的 EventLoop 处理
NioEventLoop.run()
└─ processSelectedKeys()
└─ processSelectedKey(k, socketChannel)
└─ unsafe.read() // NioByteUnsafe
└─ ByteBuf buf = allocator.buffer()
└─ int n = socket.read(buf) // 读取 "Hello"
└─ pipeline.fireChannelRead(buf) ← 传递 ByteBuf
↓
9. 你的 Handler 接收数据
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
String message = buf.toString(CharsetUtil.UTF_8);
System.out.println("收到: " + message); // 输出: 收到: Hello
}
纠正后的完整理解
✅ 正确的理解
-
Register 之后
NioEventLoop.run()方法开始执行(无限循环)- 循环中不断调用
select()等待事件
-
processSelectedKeys() 检测事件
- 检测所有注册到这个 EventLoop 的 Channel
- 根据就绪的操作类型(readyOps)进行处理
-
根据 Channel 类型和事件类型触发不同的方法
-
ServerSocketChannel + OP_ACCEPT
- 触发:
pipeline.fireChannelRead(socketChannel) - 传递:
NioSocketChannel对象(新连接)
- 触发:
-
SocketChannel + OP_READ
- 触发:
pipeline.fireChannelRead(byteBuf) - 传递:
ByteBuf对象(数据)
- 触发:
-
SocketChannel + OP_CONNECT
- 触发:
pipeline.fireChannelActive() - 传递:无参数
- 触发:
-
-
事件在 Pipeline 中传播
- 从 head 到 tail
- 每个 InboundHandler 依次处理
关键要点总结
| 要点 | 说明 |
|---|---|
| EventLoop 何时启动? | 第一个 Channel 注册时启动 |
| run() 方法何时结束? | EventLoop 关闭时结束 |
| processSelectedKeys() 检测什么? | 所有注册到这个 EventLoop 的 Channel 的就绪事件 |
| OP_ACCEPT 触发什么? | fireChannelRead(socketChannel) |
| OP_READ 触发什么? | fireChannelRead(byteBuf) |
| 传递的对象是什么? | 取决于 Channel 类型和事件类型 |
记忆口诀
Register 后 EventLoop 跑,
run 方法无限循环绕。
processSelectedKeys 来检测,
就绪事件逐个找。
ServerSocket 接连接,
fireChannelRead 传 Channel。
普通 Socket 读数据,
fireChannelRead 传 ByteBuf。
事件类型要分清,
传递对象各不同。
Pipeline 传播从 head 起,
Handler 依次来处理。
931



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



