Netty 的事件驱动框架基于 主从 Reactor 多线程 实现,其工作处理的流程如下所示。根据功能的不同,Netty 的组件主要可分为两个部分,分别是 事件分发组件 和 业务处理组件
事件分发组件主要包含了 EventLoopGroup
和 Acceptor
两类,其中 EventLoopGroup
为事件循环组,负责事件的分发处理,Acceptor
则是服务端处理 accept 事件的处理器,负责将主事件循环组新建的连接注册到从事件循环组,起到连接的作用
EventLoopGroup
主要管理 EventLoop 的生命周期,内部维护了一组 EventLoop,每个 EventLoop 负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个 EventLoop
NioEventLoopGroup
继承结构很深,各个关键属性分布在不同的父类中,比较重要的如下:
MultithreadEventLoopGroup:
DEFAULT_EVENT_LOOP_THREADS
: 表示默认的事件循环线程数,默认值为机器可用线程 * 2MultithreadEventExecutorGroup:
children
: EventExecutor 数组,维护属于当前 group 的事件循环线程,也就是 NioEventLoop 实例对象
MultithreadEventExecutorGroup
是 EventLoopGroup
最重要的实现,其构造方法主要完成了以下几件事:
new ThreadPerTaskExecutor(new DefaultThreadFactory())
生成 Executor 实例,并指定其线程工厂- 调用
newChild()
方法为当前 group 新建 NioEventLoop 实例,并指定其 Executor 入参为ThreadPerTaskExecutor
对象,该对象后续将用于创建和启动 EventLoop 线程- 如果有一个 NioEventLoop 实例新建失败,调用已创建的每个 NioEventLoop 实例的
shutdownGracefully()
方法启动事件循环线程
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
// #1
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
// #2
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
// #3
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
NioEventLoop
维护了一个线程和任务队列,类似于单线程线程池,支持异步提交执行任务,主要执行 I/O 任务
和非 I/O 任务
。两种任务的执行时间比由变量 ioRatio
控制,默认为 50,表示非 IO 任务执行的时间与 IO 任务的执行时间相等
I/O 任务
即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由NioEventLoop#processSelectedKeys()
方法触发非 IO 任务
添加到任务队列中的任务,如 register0、bind0 等任务,由NioEventLoop#runAllTasks()
方法触发
NioEventLoop
的继承结构同样很深,其比较关键的属性如下:
selector
选择器,用于监听注册其上的 channel 连接上的各个事件SingleThreadEventExecutor:
taskQueue
: 任务存放的队列
executor
: 用于新建线程的线程工厂和执行器
NioEventLoop#run()
方法是事件循环处理的核心逻辑,SingleThreadEventExecutor#execute()
方法则为事件循环开始的入口,此处暂不做深入分析
Netty 中的 Acceptor
在实现上属于业务处理组件
,因为其实现类ServerBootstrapAcceptor
继承于 ChannelHandler
,主要负责在 Server 端完成对 Channel 的处理
之所以将这个组件归入事件分发组件,是因为从功能划分的角度看其属于 MainReactor 与 SubReactor 之间的连接结构,负责将 MainReactor 接收客户端请求建立的新连接注册到 SubReactor 上
ServerBootstrapAcceptor#channelRead()
是这个 Acceptor
最重要的方法,其实现了将新建连接注册到 SubReactor 的逻辑
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
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);
}
}
业务处理组件主要包含了Channel
、ChannelHandler
以及 ChannelHandlerContext
。一个 Channel
包含了一个 ChannelPipeline
,而 ChannelPipeline
中又维护了一个由 ChannelHandlerContext
组成的双向链表,每个 ChannelHandlerContext
中又关联着一个 ChannelHandler
根据ChannelHandler
实现类继承接口的不同,可以将其分为 ChannelInboundHandler入站处理器
和 ChannelOutboundHandler出站处理器
,二者分别对应读写操作。 入站事件(read)
和出站事件(write)
在ChannelPipeline
中链式处理,入站事件会从链表 head 往后传递到最后一个入站处理器 tail,出站事件会从链表 tail 往前传递到最前一个出站处理器 head,两种类型的 ChannelHandler 互不干扰
Channel
是一个管道,是用于连接字节缓冲区和另一端的实体,可以分成服务端 NioServerSocketChannel
和 客户端 NioSocketChannel
两个大类。Netty 中 Channel
经过 ChannelPipeline
中的多个 ChannelHandler
处理器处理,最终完成 IO 数据的处理
- 服务端 NioServerSocketChannel
该类继承于AbstractNioMessageChannel
,持有一个NioMessageUnsafe
对象来完成 Chanel 上 IO 操作- 客户端 NioSocketChannel
该类继承于AbstractNioByteChannel
,持有一个NioByteUnsafe
对象完成 Channel 上事件的处理所有继承于
AbstractChannel
的 Channel 实例中都会持有一个DefaultChannelPipeline
对象,该对象用于完成 IO 数据的流处理,另外还会持有一个EventLoop
对象标识 Channel 所属的事件循环实例, 可用于处理任务提交
ChannelHandler
是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到 ChannelPipeline
业务处理链中的下一个 ChannelHandler
ChannelHandler
主要分为了进站处理器ChannelInboundHandler
和出站处理器ChannelOutboundHandler
,分别对应 read 和 write 操作处理。处理器可由用户编写处理逻辑,Netty 内部会将其包装为AbstractChannelHandlerContext
对象,再将其注册到业务处理链中,即可完成对 IO 数据的逻辑处理
ChannelHandlerContext
保存 Channel
相关的上下文信息,其主要有 3 种实现DefaultChannelHandlerContext
、HeadContext
以及 TailContext
,以下为其关键属性:
AbstractChannelHandlerContext:
next
: 当前管道处理器上下文节点的下一个节点
prev
: 当前管道处理器上下文节点的上一个节点
inbound
: 当前管道处理器上下文节点的出入站标识属性,为 true 表示该节点可处理入站事件
outbound
: 当前管道处理器上下文节点的出入站标识属性,为 true 表示该节点可处理出站事件
pipeline
:ChannelPipeline
的实例,其实现为DefaultChannelPipeline
类DefaultChannelHandlerContext:
handler
: 封装的 ChannelHandler 处理器
ChannelPipeline
的子类 DefaultChannelPipeline
对象实例化时会生成一个HeadContext
及 TailContext
对象,并将其前后指针互相指向对方,形成了一个双向链表。 DefaultChannelPipeline#addLast()
方法会向该双向链表添加节点,主要处理步骤如下:
newContext()
将ChannelHandler
封装到DefaultChannelHandlerContext
对象addLast0()
将新建的ChannelHandlerContext
对象加入到双向链表中callHandlerAdded0()
会回调ChannelHandler#handlerAdded()
方法,以便编写的相关逻辑在处理器添加到处理链中时被执行
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx);
// If the registered is false it means that the channel was not registered on an eventloop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}