Netty-服务端启动流程 源码分析
Netty-服务端启动流程 源码分析
预备知识
在看这篇 Netty启动流程 源码解析 之前, 最好请先了解 NioEventLoopGroup 和 NioEventLoop 这两个组件。可以参考我之前的文章 [Netty-组件 (NioEventLoopGroup、NioEventLoop)源码分析] ,其中主要是从构造方法入手 来分析 这两个组件中 关键的 属性变量等。 而其中具体的 核心方法,以及其它属性 我会通过服务端的启动流程慢慢展开。
ChannelFutrue
在分析启动流程前, 我们需要先了解一下 ChannelFutrue
ChannelFuture
是 Netty 为了 感知异步任务的执行结果,所封装的一个任务接口。
该类 与 java.util.concurrent 包下的 FutureTask很像,他俩同样都实现了Future接口,不同点是FutrueTask多实现了Runnable接口用于提交给线程执行的入口。
该接口源码很简单, 简单介绍下主要的几个功能:
// 用于保存 当前任务所关联的 Channel
Channel channel();
// --------------- 任务监听器相关 ---------------------------------
// 添加任务监听器
@Override
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
// 移除任务监听器
@Override
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
//-------------- 任务具体操作相关 ----------------------
@Override
ChannelFuture sync() throws InterruptedException;
@Override
ChannelFuture syncUninterruptibly();
@Override
ChannelFuture await() throws InterruptedException;
@Override
ChannelFuture awaitUninterruptibly();
// 取消任务
boolean cancel(boolean mayInterruptIfRunning);
// --------------- 任务执行结果相关 -----------------------
// 任务是否成功
boolean isSuccess();
// 任务是否取消
boolean isCancellable();
// 任务失败的 错误原因
Throwable cause();
Netty示例代码
众所周知, Netty服务端标准的代码中 会先创建两个 NioEventLoopGroup
(Boss组,Worker组) 。
然后通过 ServerBootStrap
来注册相关的组件 ,最终绑定 ip , port 后启动服务器。
大致的标准启动代码如下:
// 1. 创建 boss组 和 worker组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 2. 创建bootStrap启动器
ServerBootstrap b = new ServerBootstrap();
// 3. bootStrap中设置 boss组 和 worker组
b.group(bossGroup, workerGroup)
// 4. bootStrap设置ServerSocketChannel
.channel(NioServerSocketChannel.class)
// 5. 设置配置项
.option(ChannelOption.SO_BACKLOG, 100)
// 6. 给ServerSocketChannel添加 handler
.handler(new LoggingHandler(LogLevel.INFO))
// 7. 给SocketChannel添加handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverHandler);
}
});
// 8. 启动服务器
ChannelFuture f = b.bind(PORT).sync();
从上面的代码可知,最终启动服务端的入口 则是 b.bind(PORT)
方法。
因此我们就可以从 该方法入口,来逐步分析 Netty 服务端启动时 到底做了哪些事情。
思考
在看源码前, 我们可以先思考一下:
1.其中有哪些事情是必须要做的?
2.如果让我们来实现该Netty的启动,我们需要怎么去处理?
回答问题1:
Netty实际上是 在 Nio 上又套了一层框架, 因此基本的服务启动流程 是按照Nio的方式来的
下面是Nio 启动服务前必做的操作代码:
// 1. 创建 ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
// 2. 创建 Selector 多路复用器
Selector selector = Selector.open();
// 3. 设置ServerSocketChannel 为非阻塞
ssc.configureBlocking(false);
// 4. 将ServerSocketChannel注册到Selector上
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 5. ServerSocketChannel绑定端口
ssc.bind(new InetSocketAddress(8080));
问题2请结合 之前一篇 Netty-Reactor线程模型(NIO)
来思考。
接下来 我们需要带着这两个问题,来进入Netty的源码分析中。
1、doBind 核心入口
从示例代码中的bind方法, 往里面追踪,则会来到 #AbstractBootstrap doBind
该方法。 从 bind() 到 dobind() 的跟进过程没什么好说的,代码也非常浅显易懂。 那么我们重点就以 doBind() 方法 为核心入口。
//真正完成 bind 工作的方法, 非常关键
private ChannelFuture doBind(final SocketAddress localAddress) {
//---------------------- 初始化注册Channel------------------------
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// ----------------------- 绑定Channel-----------------------
// 当register0 已经执行完后, regFuture状态就是Done
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
//这里向register0 相关的promise对象 添加一个监听器对象 register0任务成功或失败都会回调该监听器中的方法
//监听器回调线程 是 eventLoop线程 不是当前主线程
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();
// 当Channel注册完成, 会执行该方法
doBind0(regFuture, channel, localAddress, promise);
}
}
});
//主线程返回一个 与bind 操作相关的 promise对象
return promise;
}
}
该方法实际上包含两部分:
1. **初始化注册Channel**
2. **给Channel绑定端口**
而其中 给Channel 绑定端口,需要建立在 Channel 初始化注册完毕之后 , 因此我们先来分析Channel的初始化与注册。
2 、初始化注册Channel
上面 doBind 方法中,跟踪 initAndRegister方法。 顾名思义,该方法主要的功能是 初始化Channel和注册Channel。
在 JDK 的NIO 代码中,可以按照 初始化 和注册来划分
1. 初始化
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
Selector selector = Selector.open();
2. 注册
ssc.register(selector, SelectionKey.OP_ACCEPT);
在明确了上面 初始化和注册的操作目的后,我们来看下 initAndRegister方法 做了哪些事
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 反射创建 Channel
// 服务端创建 NioServerSocketChannel
// 客户端创建 NioSocketChannel
channel = channelFactory.newChannel();
// ------------初始化逻辑-------------------
// 模板模式
// 初始化Channel (根据服务端与客户端不同 调用子类BootStrap实现方法)
init(channel);
} catch (Throwable t) {
//... 省略
}
// ---------------注册逻辑------------------
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
上述代码中 主要分为三块逻辑:
- 创建 Channel
- 初始化Channel
- 注册Channel
创建Channel 很简单,就是根据 反射逻辑创建Channel对象。
初始化Channel 以及 注册Channel在下面会逐个分析。
2.1、初始化Channel
void init(Channel channel) {
// 给Channel 设置 Options 和 Attributes
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
// 获取该Channel的 pipeline
ChannelPipeline p = channel.pipeline();
// 获取初始化赋值的workerGroup (childGroup是 workerGroup)
final EventLoopGroup currentChildGroup = childGroup;
// 获取初始化赋值的 childHandler
final ChannelHandler currentChildHandler = childHandler;
// 客户端Socket选项信息
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
// Netty的channel都是实现了 AttributeMap接口的, 可以在启动类内配置一些自定义数据,这样的话 创建出来的Channel实例 就包含这些数据了。
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
// 向 NioServerSocketChannel的 管道pipeline中添加处理器
// ChannelInitializer 它本身不是个Handler, 只是通过 适配器 实现了 Handler接口
// 它存在的意义 就是为了延迟初始化 Pipeline 什么时候初始化呢?
// 当Pipeline 上的Channel激活后,真正的添加handler 逻辑才会执行
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
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));
}
});
}
});
}
上述代码不是很难理解, 基本就是将 之前 给 BootStrap
赋的值,添加到 NioServerSocketChannel
上。
赋值的变量如下:(注意:下面属性都是专属于 ServerSocketChannel的 ,因为同样的属性对于SocketChannel也有一份)
- Options
- Attr
- Handler
其中 Handler的赋值最为特殊, 它将其包装成了 ChannelInitializer
放进了 Pipeline
中。 这里可以把ChannelInitializer 理解成一个 压缩包, 最终会在Channel 激活后,将ChannelInitialer中的Handler解压出来放入pipeline中。
p.addLast() 向管道添加Handler
该方法属于 Pipeline组件 中的知识,后面会专门出一篇对pipeline的解析,本次就根据方法简单追踪即可。
跟着上面的 p.addLast() 方法追踪 会来到下面的代码
// 参数1: group null
// 参数2: name null
// 参数3: handler ChannelInitializer压缩包
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 根据 Handler上的 @Sharable 注解,检查是否是共享处理器
checkMultiplicity(handler);
// 再将 ChannelInitializer 包装成 AbstractChannelHandlerContext
newCtx = newContext(group, filterName(name, handler), handler);
// 真正添加到pipeline中的方法 (通过操作指针入队)
addLast0(newCtx);
// 检查Channel是否注册过 (第一次进来没注册过会进入该方法)
if (!registered) {
// 设置包装后的 newCtx的状态为 ADD_PENDING 等待添加状态
newCtx.setAddPending();
// 将newCtx包装成 task添加到等待队列中去
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
上述代码针对 第一次初始化 NioServerSocketChannel 并添加 ChannelInitializer 到pipeline中 的逻辑主要的步骤:
- 将 ChannelInitialzer 包装成 newCtx
- 将 newCtx 设置成 ADD_PENDING 状态
- 将 newCtx 包装成 task 添加到 等待队列中
初始化总结:
Channel初始化主要做了如下几个事:
- 添加初始的一些属性 (Options, Attrs 等)
- 向该Channel的 pipeline中添加 ChannelInitializer压缩包
2.2、注册Channel
此时回到 initAndRegister
方法中的 注册Channel逻辑上
// ---------------注册逻辑------------------
// config() 返回 该BootStrap的 config属性。 该config相当于门面,能够通过该config访问到BootStrap内部的其它属性值.
// config.group() 返回的是之前设置的 BossGroup(NioEventLoopGroup)
// 最终就是调用 NioEventLoopGroup 的 register方法来注册 Channel
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
上述核心代码就一行 ChannelFuture regFuture = config().group().register(channel);
config()
: 获取BootStrap的config属性 (门面属性,通过其可访问到BootStrap其他的属性)
config().group()
: 最终会获取到 bossGroup (NioEventLoopGroup)
那么下面我们进 追踪进 NioEventLoopGroup 的 register 方法
- 首先进入到
MultithreadEventLoopGroup #register
中
public ChannelFuture register(Channel channel) {
// 1. next() 从该 NioEventLoopGroup中 轮询算法选择一个 NioEventLoop
// 2. 调用 NioEventLoop的register方法
return next().register(channel);
}
- 继续跟进到
SingleThreadEventLoop #register
中
public ChannelFuture register(Channel channel) {
// 将Channel 和 当前NioEventLoop 包装成 ChannelPromise 并调用下面的register方法
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
/**
* promise.channel() = NioServerSocketChannel / NioSocketChannel
*
* NioServerSocketChannel.unsafe() = NioMessageUnsafe / NioByteUnsafe
*
* unsafe.register(NioEventLoop, DefaultChannelPromise)
*/
//参数1: NioEventLoop 单线程 线程池
//参数2: promise 结果封装..外部可以注册监听 进行异步操作获取结果
promise.channel().unsafe().register(this, promise);
return promise;
}
最终会调用该 Channel的 Unsafe 来执行 相关 register 注册逻辑。 该unsafe 为父类 AbstractUnsafe
- 继续跟进到
AbstractUnsafe #register
中
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
ObjectUtil.checkNotNull(eventLoop, "eventLoop");
//初次进来是false 防止Channel重复注册
if (isRegistered()) {
//1.设置promise结果为失败 2.回调监听器进行失败的逻辑
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
//不重要 不看
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
//AbstractChannel.this 获取到Channel作用域, 这个Channel就是unsafe的外层对象。
//AbstractChannel.this => NioServerSocketChannel对象
//绑定个关系 后续Channel上的事件 或者 任务 都会依赖当前EventLoop线程去处理
AbstractChannel.this.eventLoop = eventLoop;
//判断当前线程 是不是 当前eventLoop 自己这个线程
if (eventLoop.inEventLoop()) {
// 若当前线程是 eventLoop线程,则直接调用 register0 方法
register0(promise);
} else {
// 若不是,则向 eventloop线程中 提交 register0 任务
try {
// 异步任务一:
// 将注册的任务,提交到eventLoop 工作队列内了 .. 带着promise过去了
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
// ...省略
}
}
}
上述代码 中由于 当前线程不是 eventLoop线程,因此向eventLoop线程池中提交了 异步任务1(register0).
其中两条判断逻辑 最终都指向了 register0
方法, 继续追踪进去,看看Channel注册 到底做了哪些事情.
- 最终会来到
AbstractUnsafe #register0
中 (Channel注册核心逻辑)
// 这个方法一定是 当前Channel关联的EventLoop线程执行
//参数 promise : 可以异步从中获取结果
private void register0(ChannelPromise promise) {
try {
// 校验判断
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
//******* 执行 JDK NIO原生Channel的注册逻辑代码*****:
// javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
doRegister();
//表示已经注册了
neverRegistered = false;
//表示当前Channel已经注册到多路复用器中了
registered = true;
// 这里会向当前EventLoop线程提交异步任务2 : 对ChannelInitailzer进行拆包
pipeline.invokeHandlerAddedIfNeeded();
//这一步会去回调 注册相关的 promise 上注册的Listener监听器
safeSetSuccess(promise);
//向当前Channel的Pipeline 发起注册完成事件 关注的Handler 可以做一些事情
pipeline.fireChannelRegistered();
//目前咱们是 NioServerSocketChannel
// 咱们在这一步完成绑定了吗? 绑定操作一定是 当前EventLoop线程去做的
// 在这一步的时候,绑定一定是 没完成的。 绑定操作是异步任务3 该操作是异步任务1 中间还差一个任务呢
// 这一步isActive()条件是不成立的。
if (isActive()) {
//客户端 NioSocketChannel 这一步是成立的 所以会进来
if (firstRegistration) {
// 传播channelActivie()方法 实际上就是让SocketChannel 开始监听事件了
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
上述代码中:
doRegister()
方法是真正完成 NIO Channel 注册到 selector 上的方法。
pipeline.invokeHandlerAddedIfNeeded()
方法最终会对 之前添加进 pipeline中的 ChannelInitializer
进行拆包, 并提交异步任务2.
2.3、拆压缩包
下面我们关注,ChannelInitializer
拆包的过程:
private void callHandlerAddedForAllHandlers() {
final PendingHandlerCallback pendingHandlerCallbackHead;
synchronized (this) {
assert !registered;
registered = true;
// 取出 等待队列的 头节点 task
pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
// 将头节点task 置空
this.pendingHandlerCallbackHead = null;
}
PendingHandlerCallback task = pendingHandlerCallbackHead;
//循环执行 等待队列中的 task
while (task != null) {
task.execute();
task = task.next;
}
}
跟进 task.execute() 会来到 PendingHandlerAddedTask #execute
方法
void execute() {
//拿到 eventLoop 线程
EventExecutor executor = ctx.executor();
// 判断当前线程 是否是 eventLoop线程
if (executor.inEventLoop()) {
// 是eventLoop线程, 则直接执行 (当前必走该分支)
callHandlerAdded0(ctx);
} else {
// 若不是, 则将该任务提交给eventLoop线程池
try {
executor.execute(this);
} catch (RejectedExecutionException e) {
// ...
}
}
}
}
接着来到 DefaultChannelPipeline #callHandlerAdded0
方法
// 参数1 ctx 该ctx就是包装了 ChannelInitializer的newCtx
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
//执行CTX的 callHandlerAdded()方法
ctx.callHandlerAdded();
} catch (Throwable t) {
// ...
}
}
继续来到 AbstractChannelHandlerContext #callHandlerAdded
方法
final void callHandlerAdded() throws Exception {
//CAS方式设置 ctx的状态为 complete (原来是 Add Pending 状态)
if (setAddComplete()) {
//handler() 拿到handler 此handler是 ChannelInitializer压缩包
// 执行ChannelInitializer 的 handlerAdded() 方法
handler().handlerAdded(this);
}
}
由于 Ctx 包装了 ChannelInitializer
, 因此 handler()
方法就会拿到所包装的 ChannelInitilizer
跟进到 ChannelInitializer #handlerAdded
方法中
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 判断所属Channel是否已经注册过了
if (ctx.channel().isRegistered()) {
// 调用模板方法, initChannel() 执行用户自定义的重写逻辑
// 将ChannelInitializer中的Handler取出并放入pipeline中。
if (initChannel(ctx)) {
// 成功拆包后, 将ChannelInitializer自己从 pipeline中移除
removeState(ctx);
}
}
}
接着就会来到 Channel初始化时候 的那段代码:
会执行 initChannel
方法
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
// 获取 pipeline
final ChannelPipeline pipeline = ch.pipeline();
// 获取 向BootStrap设置的 Server端的 handler,如:LoggingHandler
ChannelHandler handler = config.handler();
if (handler != null) {
// 添加到 pipeline中
pipeline.addLast(handler);
}
// 向当前Channel所属的eventLoop线程池,提交 异步任务2
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
上述代码 主要就是
1. 向pipeline中 添加 BootStrap初始化设置的Handler,
2. **向eventLoop线程池中 提交异步任务2 (重点)**
2.4、总结
Channel的 初始化注册做了哪些事情:
- 反射创建Channel (NioSeverSocketChannel / NioSocketChannel)
- 初始化Channel
- 设置 Options,Attrs 等基本属性值
- 向pipeline中添加 ChannelInitializer 压缩包
- 注册Channel
- 封装成 Promise 任务对象,并调用 Channel的 unsafe对象的 register方法,提交异步任务1(register0)
- register0方法
- 完成JDK NIO的 Channel注册
- 解压缩包,执行ChannelInitializer 的 initChannel() 方法,该方法中会提交异步任务2 (p.addLast(Acceptor))
3、绑定Channel
回到 一开始 的 doBind
方法 , 至此 Channel的 初始化和注册已经分析完毕,下面我们来分析 Channel的端口绑定逻辑.
Channel 的端口绑定逻辑,必须建立在 Channel已经完成了初始化和注册后才能执行。 而核心逻辑就是下面一行代码:
doBind0(regFuture, channel, localAddress, promise);
跟进doBind0 方法
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// 异步任务3 提交
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
// 绑定端口, 并通知监听器
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
上述代码中,核心的方法 channel.bind(localAddress, promise)
,他会调用 Channel的 pipeline 的 bind方法,让pipeline中的 Handler,从尾部向头部依次执行 bind 方法
下面会来到 HeadContext #bind
方法中
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
// 调用所属Channel的 unsafe 对象的 bind方法
unsafe.bind(localAddress, promise);
}
最终核心逻辑就在 AbstractUnsafe #bind
方法中
实际上逻辑也非常简单,就是使用NIO Channel 的 绑定端口方法
javaChannel().socket().bind(localAddress, config.getBacklog());
4、激活Channel
绑定Channel的操作实际上很简单,实际上就是调用 NIO 的 Channel 绑定方法
但是在Netty中,Channel绑定完成后,还有一个重要的操作就是激活Channel.
invokeLater(new Runnable() {
@Override
public void run() {
// 激活操作, HeadContext 响应做具体的激活工作
pipeline.fireChannelActive();
}
});
pipeline.fireChannelActive();
向管道中发送 ChannelActive 事件,最终只有HeadContext
响应
**接下来看 HeadContext #channelActive
**
public void channelActive(ChannelHandlerContext ctx) {
// 向后面的HandlerContext节点传递 channelActive事件 (后面的节点都是空实现)
ctx.fireChannelActive();
// 激活操作 Channel开启读事件响应
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
// 向channel开启读
channel.read();
}
}
channel.read();
:会调用 该channel 的pipeline的read方法,从尾节点向头节点传递
最终还是会来到HeadContext #read
方法
public void read(ChannelHandlerContext ctx) {
// 调用 Unsafe 的 beginRead
unsafe.beginRead();
}
接着追踪到 该Channel的 unsafe 中 beginRead 方法
一步步进入会来到 AbstractNioUnsafe #doBeginRead
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
// 将感兴趣的事件 加上读事件 (连接事件 + 读事件)
selectionKey.interestOps(interestOps | readInterestOp);
}
}
5、总结
这里总结下 Netty 服务端 启动做了哪些事情:
- 初始化 和 注册 NioServerSocketChannel
- 通过反射创建NioServerSocketChannel
- 初始化Channel
- 设置 Options
- 设置 Attrs
- 向Channel的pipeline中添加 ChannelInitializer 压缩包 (其中包含 用户自定义服务端handler 和 处理连接事件的AcceptorHandler) , 此时会加入到 pipeline的 等待队列中
- 注册Channel
- 将注册Channel 封装成 Promise 任务(其中有 Channel对象, EventLoop对象)
- 提交 异步任务1 (注册Channel)
- 完成 JDK NIO 的Channel 注册到 selector多路复用器上。
- 解压缩 pipeline中的等待队列中的 ChannelInitializer ,此时会提交异步任务2(向pipeline中添加AcceptorHandler 连接事件处理器 )
- 绑定Channel
- 绑定Channel 是必须建立在 注册Channel 完成的基础上才会执行, 这是通过Promise的 监听器回调机制实现的, 响应回调则会 提交 异步任务 3(绑定Channel)
- 绑定Channel 的 核心方法也就是 JDK NIO 的 bind 方法,绑定 ip 和 端口
- 激活Channel
- 激活Channel 同样是 在绑定Channel完成后 执行的, 会提交 异步任务4(激活Channel)
- 激活Channel 的核心逻辑就是 给该Channel 在多添加一个 读感兴趣事件到selector上,此时Channel感兴趣的事件为 (连接事件 + 读事件)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)