1. 事件驱动模型
1.1 什么是事件驱动
事件驱动是一种编程范式,系统通过监听和响应事件(Event)来执行操作,而不是通过轮询或阻塞等待。事件通常由外部刺激触发(如网络 I/O、定时器、用户任务),由事件循环(Event Loop)捕获并分发给相应的处理逻辑。事件驱动的核心优势是:
- 异步非阻塞:避免线程阻塞,提升资源利用率。
- 高并发:单线程处理多个事件,适合网络应用。
- 模块化:事件处理逻辑解耦,易于扩展。
1.2 Netty 的事件驱动模型
Netty 的事件驱动模型基于 Java NIO 的多路复用机制(Selector),结合其自定义的事件循环(EventLoop)和管道(ChannelPipeline),实现高效的事件生产、传播和处理。Netty 的事件驱动包括:
- 事件类型:
- I/O 事件:如连接建立(OP_CONNECT)、数据可读(OP_READ)、数据可写(OP_WRITE)。
- 通道事件:如通道激活(channelActive)、关闭(channelInactive)、异常(exceptionCaught)。
- 用户事件:自定义事件(fireUserEventTriggered)或定时任务。
- 事件处理:
- 事件由 EventLoop 捕获,通过 ChannelPipeline 传播到 ChannelHandler。
- ChannelHandler 执行具体的处理逻辑(如解码、编码、业务处理)。
- 异步机制:
- 使用 ChannelFuture 和 ChannelPromise 管理异步操作结果。
- 监听器(ChannelFutureListener)在事件完成时触发回调。
1.3 核心组件
Netty 的事件驱动依赖以下组件:
- EventLoop:单线程事件循环,负责 I/O 事件捕获和任务调度。
- EventLoopGroup:管理一组 EventLoop,支持多线程处理。
- Channel:表示网络连接,触发 I/O 和通道事件。
- ChannelPipeline:事件处理管道,链式传播事件。
- ChannelHandler:处理具体的事件逻辑。
- Selector:Java NIO 的多路复用器,捕获底层 I/O 事件。
2. 事件驱动的实现机制
以下从事件的生产、传播和处理三个阶段,详细解析 Netty 如何实现事件驱动。
2.1 事件的生产
事件的生产主要由 EventLoop 和 Channel 负责,涉及底层 I/O 事件、通道状态变化和用户任务。
2.1.1 I/O 事件的生产
-
机制:
- Netty 使用 Java NIO 的 Selector 监听 Channel 上的 I/O 事件(如 OP_ACCEPT、OP_CONNECT、OP_READ、OP_WRITE)。
- EventLoop 的主循环通过 selector.select() 捕获就绪事件。
-
实现类:
- NioEventLoop:Netty 的默认事件循环实现,封装了 Selector。
- NioSocketChannel 和 NioServerSocketChannel:基于 NIO 的 SocketChannel 和 ServerSocketChannel。
-
源码分析(NioEventLoop.run):
@Override protected void run() { for (;;) { try { switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { case SelectStrategy.CONTINUE: continue; case SelectStrategy.BUSY_WAIT: // fall-through to SELECT since the busy-wait is not supported with NIO case SelectStrategy.SELECT: select(wakenUp.getAndSet(false)); if (wakenUp.get()) { selector.wakeup(); } // fall through default: } cancelledKeys = 0; needsToSelectAgain = false; final int ioRatio = this.ioRatio; if (ioRatio == 100) { try { processSelectedKeys(); } finally { runAllTasks(); } } else { final long ioStartTime = System.nanoTime(); try { processSelectedKeys(); } finally { final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } } catch (Throwable t) { handleLoopException(t); } try { if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { return; } } } catch (Throwable t) { handleLoopException(t); } } }
- 逻辑:
- select:调用 selector.select() 等待 I/O 事件或被任务触发。
- processSelectedKeys:处理就绪的 SelectionKey,分发到对应的 Channel。
- runAllTasks:执行任务队列中的非 I/O 任务。
- 关键点:
- 使用 selectStrategy 优化 select 行为。
- ioRatio 控制 I/O 处理和任务执行的时间比例。
- wakenUp 避免重复唤醒 Selector。
- 逻辑:
2.1.2 通道事件的生产
-
机制:
- 通道状态变化(如连接建立、关闭、激活)触发通道事件。
- 这些事件由 Channel 的实现类(如 AbstractChannel、NioSocketChannel)生成。
-
典型事件:
- channelActive:通道激活(如连接建立)。
- channelInactive:通道关闭。
- channelRead:数据可读。
- exceptionCaught:异常发生。
-
源码分析(NioSocketChannel.doFinishConnect):
private void doFinishConnect() throws Exception { if (!javaChannel().finishConnect()) { throw new Error(); } pipeline().fireChannelActive(); }
- 逻辑:
- 当 OP_CONNECT 事件触发时,调用 finishConnect 完成连接。
- 触发 fireChannelActive,生成 channelActive 事件。
- 逻辑:
2.1.3 用户事件的生产
-
机制:
- 用户可以通过 ChannelPipeline.fireUserEventTriggered 触发自定义事件。
- 定时任务或延迟任务通过 EventLoop.schedule 生成。
-
源码分析(SingleThreadEventLoop.schedule):
@Override public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { ObjectUtil.checkNotNull(command, "command"); ObjectUtil.checkNotNull(unit, "unit"); if (delay < 0) { delay = 0; } validateScheduled(delay, unit); return schedule(new ScheduledFutureTask<Void>(this, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); }
- 逻辑:
- 将任务封装为 ScheduledFutureTask,加入优先级队列(PriorityQueue)。
- EventLoop 在主循环中检查队列,执行到期任务。
- 逻辑:
2.2 事件的传播
事件的传播由 ChannelPipeline 负责,通过链式调用将事件分发给 ChannelHandler。
2.2.1 ChannelPipeline 的作用
-
定义:
- ChannelPipeline 是一个双向链表,包含一系列 ChannelHandler。
- 每个 Channel 拥有唯一的 ChannelPipeline。
-
功能:
- 入站事件(如 channelRead、channelActive)从管道头部向尾部传播。
- 出站事件(如 write、connect)从管道尾部向头部传播。
- 支持动态添加/移除 ChannelHandler。
-
源码分析(DefaultChannelPipeline.fireChannelRead):
@Override public final ChannelPipeline fireChannelRead(Object msg) { AbstractChannelHandlerContext.invokeChannelRead(head, msg); return this; } static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline().touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(m); } else { executor.execute(() -> next.invokeChannelRead(m)); } }
- 逻辑:
- 从管道的 head 节点开始,调用 invokeChannelRead。
- 检查当前线程是否在 EventLoop 中,如果不在,提交任务到 EventLoop。
- 调用 ChannelHandler 的 channelRead 方法。
- 逻辑:
2.2.2 事件传播路径
-
入站事件:
- 由 Channel 或 EventLoop 触发(如 fireChannelRead)。
- 从 head 向 tail 传播,调用 ChannelInboundHandler 的方法。
- 示例:channelRead 事件依次调用每个 ChannelInboundHandler.channelRead。
-
出站事件:
- 由用户代码触发(如 channel.write)。
- 从 tail 向 head 传播,调用 ChannelOutboundHandler 的方法。
- 示例:write 事件调用 ChannelOutboundHandler.write。
-
源码分析(AbstractChannelHandlerContext.invokeChannelRead):
private void invokeChannelRead(Object msg) { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRead(msg); } }
- 逻辑:
- 如果当前节点是 ChannelInboundHandler,调用其 channelRead。
- 否则,调用 fireChannelRead 传播到下一个节点。
- 逻辑:
2.2.3 线程模型
- 单线程执行:
- 所有事件传播和处理都在 EventLoop 的单线程中执行。
- 避免锁竞争,确保线程安全。
- 任务调度:
- 如果事件触发时不在 EventLoop 线程,提交任务到 EventLoop 的任务队列。
- 示例:executor.execute(() -> next.invokeChannelRead(m))。
2.3 事件的处理
事件的处理由 ChannelHandler 负责,用户通过实现 ChannelInboundHandler 或 ChannelOutboundHandler 定义具体逻辑。
2.3.1 ChannelHandler 的类型
-
ChannelInboundHandler:
-
处理入站事件,如 channelRead、channelActive、exceptionCaught。
-
接口定义:
public interface ChannelInboundHandler extends ChannelHandler { void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; void channelActive(ChannelHandlerContext ctx) throws Exception; void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; // 其他方法 }
-
-
ChannelOutboundHandler:
-
处理出站事件,如 write、connect、close。
-
接口定义:
public interface ChannelOutboundHandler extends ChannelHandler { void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception; void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception; // 其他方法 }
-
-
SimpleChannelInboundHandler:
-
简化实现,自动释放消息资源。
-
示例:
public class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { boolean release = true; try { if (acceptInboundMessage(msg)) { @SuppressWarnings("unchecked") I imsg = (I) msg; channelRead0(ctx, imsg); } else { release = false; ctx.fireChannelRead(msg); } } finally { if (release) { ReferenceCountUtil.release(msg); } } } protected void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception { // 由子类实现 } }
-
2.3.2 事件处理逻辑
-
入站事件处理:
- channelRead:处理接收到的数据(如解码、业务逻辑)。
- channelActive:通道激活时执行(如初始化资源)。
- exceptionCaught:处理异常(如关闭通道)。
-
出站事件处理:
- write:将数据写入通道(如编码、缓冲)。
- connect:建立连接。
- close:关闭通道。
-
源码分析(DefaultChannelHandlerContext.write):
@Override public ChannelFuture write(final Object msg, final ChannelPromise promise) { if (msg == null) { throw new NullPointerException("msg"); } try { if (isNotValidPromise(promise, true)) { ReferenceCountUtil.release(msg); return promise; } } catch (RuntimeException e) { ReferenceCountUtil.release(msg); throw e; } write(msg, false, promise); return promise; } private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); final Object m = pipeline.touch(msg, next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { if (flush) { next.invokeWriteAndFlush(m, promise); } else { next.invokeWrite(m, promise); } } else { AbstractWriteTask task; if (flush) { task = WriteAndFlushTask.newInstance(next, m, promise); } else { task = WriteTask.newInstance(next, m, promise); } safeExecute(executor, task, promise, m); } }
- 逻辑:
- 查找下一个出站 ChannelHandler。
- 如果在 EventLoop 线程,直接调用 invokeWrite。
- 否则,创建 WriteTask 提交到 EventLoop。
- 逻辑:
2.3.3 自定义事件处理
-
用户事件:
-
通过 ctx.fireUserEventTriggered(event) 触发自定义事件。
-
示例:
ctx.fireUserEventTriggered(new CustomEvent("User event"));
-
-
定时任务:
-
通过 EventLoop.schedule 安排定时任务。
-
示例:
channel.eventLoop().schedule(() -> { System.out.println("Scheduled task executed"); }, 1, TimeUnit.SECONDS);
-
3. 核心组件的协作
3.1 EventLoop 与 Channel
-
协作:
- Channel 注册到 EventLoop 的 Selector,监听 I/O 事件。
- EventLoop 捕获事件后,调用 Channel 的方法(如 doReadBytes)。
-
示例:
- NioSocketChannel 注册 OP_READ,EventLoop 触发 pipeline.fireChannelRead。
-
源码分析(NioByteUnsafe.read):
@Override public final void read() { final ChannelConfig config = config(); final ChannelPipeline pipeline = pipeline(); final ByteBufAllocator allocator = config.getAllocator(); final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); allocHandle.reset(config); ByteBuf byteBuf = null; boolean close = false; try { do { byteBuf = allocHandle.allocate(allocator); allocHandle.lastBytesRead(doReadBytes(byteBuf)); if (allocHandle.lastBytesRead() <= 0) { byteBuf.release(); byteBuf = null; close = allocHandle.lastBytesRead() < 0; break; } allocHandle.incMessagesRead(1); pipeline.fireChannelRead(byteBuf); byteBuf = null; } while (allocHandle.continueReading()); allocHandle.readComplete(); pipeline.fireChannelReadComplete(); } catch (Throwable t) { handleReadException(pipeline, byteBuf, t, close, allocHandle); } }
- 逻辑:
- 分配 ByteBuf 读取数据。
- 调用 doReadBytes 从 SocketChannel 读取。
- 触发 fireChannelRead 传播数据。
- 逻辑:
3.2 ChannelPipeline 与 ChannelHandler
-
协作:
- ChannelPipeline 将事件分发给 ChannelHandler。
- ChannelHandler 通过 ChannelHandlerContext 控制事件传播。
-
示例:
- channelRead 事件依次调用 ChannelInboundHandler.channelRead。
-
源码分析(ChannelHandlerContext.fireChannelRead):
@Override public ChannelHandlerContext fireChannelRead(final Object msg) { invokeChannelRead(findContextInbound(), msg); return this; }
- 逻辑:
- 查找下一个入站 ChannelHandler。
- 调用其 channelRead 方法。
- 逻辑:
3.3 EventLoop 与 ChannelFuture
-
协作:
- ChannelFuture 的监听器回调在 EventLoop 中执行。
- ChannelPromise 的结果设置触发监听器。
-
示例:
- channel.connect().addListener(f -> System.out.println(“Connected”))。
-
源码分析(DefaultChannelPromise.notifyListeners):
private void notifyListeners() { Object listeners = this.listeners; if (listeners == null) { return; } EventExecutor executor = this.executor(); if (executor.inEventLoop()) { notifyListenersNow(); } else { executor.execute(this::notifyListenersNow); } }
- 逻辑:
- 如果在 EventLoop 线程,直接通知监听器。
- 否则提交任务到 EventLoop。
- 逻辑:
4. 示例代码
以下通过客户端和服务器示例,展示 Netty 事件驱动的实际应用。
4.1 服务器端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
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)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("Received: " + msg);
ctx.writeAndFlush("Hello: " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("Client connected");
ctx.fireChannelActive();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
});
}
});
ChannelFuture f = b.bind(8080).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
- 事件流程:
- NioServerSocketChannel 触发 OP_ACCEPT,接受客户端连接。
- 创建 NioSocketChannel,触发 channelActive。
- 读取数据触发 channelRead,解码后调用 channelRead0。
- writeAndFlush 触发出站事件,编码后写入。
4.2 客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("Received: " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush("ClientHello");
}
});
}
});
ChannelFuture f = b.connect("localhost", 8080).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
- 事件流程:
- connect 触发 OP_CONNECT,连接建立后触发 channelActive。
- writeAndFlush 发送数据,触发出站事件。
- 接收数据触发 channelRead,解码后调用 channelRead0。
5. 性能优化
- 单线程事件循环:
- EventLoop 使用单线程,避免锁竞争。
- NioEventLoop 优化 Selector.select 性能。
- 任务调度:
- 任务队列(MpscQueue)高效处理非 I/O 任务。
- ioRatio 平衡 I/O 和任务处理时间。
- 零拷贝:
- ByteBuf 使用直接内存,减少用户态到内核态拷贝。
- 支持复合缓冲区(CompositeByteBuf)和切片。
- 事件传播优化:
- ChannelPipeline 使用双向链表,O(1) 复杂度查找节点。
- 事件直接调用,减少反射开销。
- 内存管理:
- PooledByteBufAllocator 实现内存池化。
- 引用计数(ReferenceCounted)防止内存泄漏。
6. 总结
Netty 的事件驱动模型通过 EventLoop、ChannelPipeline 和 ChannelHandler 的协作实现了高效的网络 I/O 和事件处理。其核心机制包括:
- 事件生产:EventLoop 捕获 I/O 的 I/O 事件,Channel 触发通道事件。
- 事件传播:ChannelPipeline 链式分发事件,支持入站和出站处理。
- 事件处理:ChannelHandler 执行自定义逻辑,结合异步回调。
- 性能优化:单线程模型、零拷贝、任务调度、内存池。