【源码剖析】Netty 服务端 启动流程 详解
首先,本人来给出一个 Netty
的 服务端 的 使用示例,以方便后续的 源码讲解:
使用示例:
package edu.youzg.demo.source;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @Author: Youzg
* @CreateTime: 2021-05-07 16:55
* @Description: 带你深究Java的本质!
*/
public class NettyServerDemo {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ChannelInboundHandlerAdapter()); // 模拟 添加自定义处理器
}
});
System.out.println("Netty Server start...");
ChannelFuture channelFuture = bootstrap.bind(9000).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
我们首先来看看 NioEventLoopGroup
类 的 构造方法,做了什么:
NioEventLoopGroup 类 · 构造方法:
本人先来展示 对应 bossGroup
的 单参构造:
单参构造:
我们可以看到:
内部执行逻辑,只是 调用了 双参构造
且 第二个参数 是 null
那么,我们追进去,看看 双参构造 是如何实现的:
我们可以看到:
内部执行逻辑,只是 调用了 三参构造
但是,第三个参数 是 NIO源码中的“熟客”
我们继续跟进去:
我们可以看到:
内部执行逻辑,只是 调用了 四参构造
继续跟进:
我们可以看到:
内部执行逻辑,只是 调用了 父类 的 五参构造
继续跟进:
由于此处的 单参构造,参数的 nThreads属性 不为0
因此,此处我们直接看 父类 的 三参构造:
我们可以看到:
又是 调用其它构造方法
继续跟进,就到了 真正的逻辑实现:
逻辑实现 —— MultithreadEventExecutorGroup 构造器:
我们可以看到:
真正的 业务逻辑,只是:
- 创建一个 大小为 参数所传 的 EventExecutor数组
- 创建一个 set集合,将之前创建的 EventExecutor数组 放入
- 将 之前创建的 set集合,设置为 “只读”模式,并 赋值给
readonlyChildren
属性
接下来,我们来看看 对应 workerGroup
的 无参构造 的 源码实现:
无参构造:
那么,看了上文的 单参构造 的 源码,
想必同学们也能清楚,唯一的 不同点,就在下图中:
那么,我们来看看 这个 DEFAULT_EVENT_LOOP_THREADS
属性 的值 是多少:
我们可以看到:
无参构造,最后 创建的线程数 是 CPU核数的2倍
接下来,我们来看看 ServerBootstrap
的 无参构造,执行了什么逻辑:
ServerBootstrap 类 · 无参构造:
我们可以看到:
无参构造,只是 创建了一个 纯净的 ServerBootstrap类 的 对象
那么,我们继续看看 使用示例 中的 下一行代码,内部执行了什么逻辑:
ServerBootstrap 类 · group方法:
我们可以看到:
group方法 的 业务逻辑,只是:
- 以 bossGroup 为 参数,调用 父类 的 group方法
- 为 childGroup属性 赋值为 workerGroup
那么,我们来看看,父类 的 group方法 的 内部逻辑 是如何执行的:
我们可以看到:
内部只是 为 group属性,赋值为 bossGroup
那么,本人现在来总结下 ServerBootstrap
类 的 group方法,内部执行了什么逻辑:
总结:
- 为 childGroup属性 赋值为 workerGroup
- 为 父类 的 group属性,赋值为 bossGroup
接下来,我们来看看 ServerBootstrap
类 的 channel方法 的 源码:
ServerBootstrap 类 · channel方法:
我们可以看到:
这是一个 包装方法
我们继续跟进去:
ReflectiveChannelFactory 类 · 单参构造:
我们可以看到:
内部逻辑,只是 将 参数Class对象 的 构造器,赋值给了
constructor
属性
那么,我们再来看看 channelFactory方法 的 内部逻辑:
channelFactory方法:
这又是一个 包装方法
我们继续跟下去:
我们可以看到:
内部执行逻辑,只是 为
channelFactroy
属性,赋值为 参数
那么,本人现在来 总结 下 ServerBootstrap 类 的 channel方法 的 内部执行逻辑:
总结:
- 将 参数Class对象 的 构造器,赋值给了 创建的 ReflectiveChannelFactory类 的 对象 的
constructor
属性- 将 上一步创建的 ReflectiveChannelFactory类 的 对象,赋值给 父类 的
channelFactroy
属性
接下来,本人来讲解下 ServerBootstrap 类 的 childHandler方法 的 执行逻辑:
ServerBootstrap 类 · childHandler方法:
可以看到:
这个方法 的 内部执行逻辑 很简单:
为childHandler
属性 赋值为 我们创建的 ChannelInitializer对象
我们接着来看 之后的代码 —— ServerBootstrap 类 的 bind方法:
ServerBootstrap 类 · bind方法:
又是一个 包装方法,我们来 继续跟进:
那么,我们来看看 检验状态 的 validate方法,内部是如何执行的:
检验状态 —— validate方法:
我们可以看到:
validate方法 的 内部执行逻辑 为 检验
group属性
和channelFactory属性
是否赋值
我们再来看看 真正的“绑定端口”逻辑 是如何实现的:
封装的“绑定端口”逻辑 —— doBind方法:
我们可以看到:
内部执行逻辑,只是:
- 初始化 并 注册 通道
- 执行 绑定逻辑
那么,我们来看看 实现了 初始化 和 注册 通道 逻辑 的 initAndRegister方法:
初始化 并 注册 通道 —— initAndRegister方法:
我们可以看到:
内部执行逻辑,只是:
- 创建 并 初始化 通道
- 注册 通道
我们先来看看,创建通道 的逻辑:
创建通道 —— newChannel方法:
我们可以看到:
创建通道 的逻辑,只是:
调用 之前的 ServerBootstrap类的channel方法 中,赋值的 构造器 的 无参构造,创建一个 Channel对象
我们接着,来看看 初始化通道 的逻辑:
初始化通道 —— init方法:
我们可以看到:
内部执行逻辑,只是:
- 获取 之前创建的channel 的 pipeline
- 向 pipeline 中,放入一个 ChannelInitializer,其作用是:
当 客户端连接时,向 pipeline 中,加入一个 ServerBootstrapAcceptor处理器,
并且 处理器 的 构造参数 是 之前用户传入的,保证了 扩展性
(构造参数 分别是当前NioServerSocketChannel通道
、workerGroup
、用户传入的处理器
、与客户端连接通道的 TCP连接参数
、与客户端连接通道的 属性
)
pipeline 中的 结构 如下:
接着,我们来看看 注册通道 的 逻辑 是如何实现的:
注册通道 —— register方法:
我们可以看到:
内部执行逻辑,只是:
- 调用 父类 的 线程池 中的 某一个线程,执行 父类 的 register方法
我们继续 debug,来看看 调用的方法 的 源码:
我们可以看到:
内部执行逻辑,只是:
- 将 参数所传channel 包装成 DefaultChannelPromise类型,然后继续调用 register方法
我们跟下去,来看看是如何执行的:
这还是一个 包装方法,我们继续跟进:
我们可以看到:
内部执行逻辑,只是:
- 进行 一系列检验
- 从 线程池 中 获取一个线程,执行 register0方法
那么,我们来看看,NioEventLoop线程池 是 如何 执行任务 的:
线程池任务的执行 —— execute方法:
我们可以看到:
内部执行逻辑,只是:
- 将 任务 丢到 taskQueue 中
(下文中进行验证)- 开启线程,监听事件
任务丢入 taskQueue —— addTask方法:
我们可以看到:
这里的代码逻辑,和 JDK 提供的 线程池 十分类似
我们继续跟进:
可以看到:
任务 最终被提交进
taskQueue
中
同时也验证了 本人上文讲解没有问题!
那么,我们再来看看 开启线程,监听事件 都做了什么:
开启线程,监听事件 —— startThread()方法:
我们可以看到:
内部执行逻辑,只是:
- 判断 并 修改 当前线程池的 状态
- 真正的 “开启线程”逻辑
我们继续跟进,看看 “开启线程”逻辑 是如何实现的:
我们可以看到:
这里 调用了 run方法
那么,我们再来看看 调用了 run方法,内部是如何执行的:
我们可以看到:
这里 死循环执行:
- 监听IO事件
- 处理
selectedKeys
中的 所有key- 执行
taskQueue
中的 所有任务
而这些,正好对应本人之前博文所讲解的 Netty
的 Reactor模型:
那么,我们现在来看看 select方法 的内部执行逻辑:
超时阻塞式监听 —— select方法:
我们可以看到:
此方法中:
调用了NIO
的 API,死循环、超时阻塞 式 地 监听 IO事件
接下来,本人来展示下 处理selectedKeys中的 key 的 processSelectedKey方法:
selectedKeys 的 key处理 —— processSelectedKey方法:
此处调用逻辑嵌套了几层,本人就不展示 无关紧要 的几层了
我们直接来看 核心逻辑:
我们可以看到:
此方法中:
根据 select方法 监听到的 不同事件,调用了 不同的处理逻辑
接下来,我们再来看看 执行taskQueue 中任务 的 runAllTasks方法:
taskQueue 中任务 的执行 —— runAllTasks方法:
此处调用逻辑嵌套了几层,本人就不展示 无关紧要 的几层了
我们直接来看 核心逻辑:
可以看到:
此方法中:
执行了 之前步骤,放入 taskQueue 中的 所有任务
那么,我们来看看 register0方法 的 执行逻辑:
register0方法:
我们可以看到:
内部执行逻辑,只是:
- 调用 doRegister方法,执行 注册逻辑
- 责任链模式,调用 当前通道 的 pipeline 的 channelActive方法
我们继续跟进 doRegister方法,来看看是如何实现的:
doRegister方法:
我们可以看到:
内部执行逻辑,只是:
- 调用
NIO
的selector
注册 的 API
而 参数0,就相当于NIO
的 API 中的OP_ACCEPT
到这里,注册通道 的逻辑,也就实现了!
(说实话,看到这里,本人差不多快吐了。。。
)
接下来,就是 绑定端口逻辑实现 的 doBind0 方法:
绑定端口 —— doBind0方法:
我们可以看到:
内部执行逻辑,只是:
- 当 之前的操作都成功后,执行 channel 的 bind方法
那么,我们跟进去,开看看 绑定端口 的逻辑,是如何实现的:
bind方法:
我们可以看到:
这个方法,是一个 包装方法
我们继续跟进:
又是 包装方法,我们继续跟进:
我们可以看到:
内部执行逻辑,只是:
- 使用 线程池 中的 任一线程,执行 绑定端口 逻辑
我们继续跟进 invokeBind方法:
invokeBind方法:
我们继续跟进:
我们可以看到:
内部执行逻辑,只是:
- 调用 unsafe 的 bind方法
(但是注意:这里的 unsafe 并不是 Java的API中的Unsafe)
继续跟进:
我们继续跟进:
我们可以看到:
内部执行逻辑,只是:
- 根据 Java版本,调用 Java原生API,实现 “绑定端口” 逻辑
(u1s1,跟到这里,我是真佛了,难道写 Netty
的大佬不晕乎吗?)
那么,到这里,ServerBootstrap 类 的 bind方法,就讲解完毕了
本人现在来总结下:
总结:
- 检验 当前状态,用户是否设置了
group属性
和channelFactory属性
(这里的group属性
是指bossGroup
)- 调用 Java原生API,初始化 并 注册 通道
- 超时阻塞 式 监听 IO事件
- 处理 selectedKeys 中的 所有key
- 执行 taskQueue 中的 所有任务
- 调用 Java原生API,绑定端口
(反正我是写得头晕目眩了,不知道同学们有没有感受到同样的“痛楚”
)
那么,到这里,Netty
的 服务端 就准备完毕了!
之后就等 客户端 进行连接、发起请求 了
讲到这里,是真不容易,希望同样看到这里的同学,为我们共同的努力,留下你们的 关注 和 👍