【源码剖析】Netty 服务端 启动流程 详解

shadowLogo

首先,本人来给出一个 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 构造器:

逻辑1
逻辑2
我们可以看到:

真正的 业务逻辑,只是:

  • 创建一个 大小参数所传EventExecutor数组
  • 创建一个 set集合,将之前创建的 EventExecutor数组 放入
  • 将 之前创建的 set集合,设置为 “只读”模式,并 赋值readonlyChildren属性

接下来,我们来看看 对应 workerGroup无参构造 的 源码实现:

无参构造:

无参构造
那么,看了上文的 单参构造 的 源码,
想必同学们也能清楚,唯一的 不同点,就在下图中:
区别
那么,我们来看看 这个 DEFAULT_EVENT_LOOP_THREADS属性 的值 是多少:
取值
我们可以看到:

无参构造,最后 创建的线程数CPU核数的2倍


接下来,我们来看看 ServerBootstrap无参构造,执行了什么逻辑:

ServerBootstrap 类 · 无参构造:

无参
我们可以看到:

无参构造,只是 创建了一个 纯净ServerBootstrap类对象


那么,我们继续看看 使用示例 中的 下一行代码,内部执行了什么逻辑:

ServerBootstrap 类 · group方法:

group

我们可以看到:

group方法业务逻辑,只是:

  • bossGroup参数,调用 父类 的 group方法
  • childGroup属性 赋值为 workerGroup

那么,我们来看看,父类group方法内部逻辑 是如何执行的:
group父
我们可以看到:

内部只是 为 group属性,赋值为 bossGroup


那么,本人现在来总结下 ServerBootstrapgroup方法,内部执行了什么逻辑:

总结:

  • childGroup属性 赋值为 workerGroup
  • 父类group属性,赋值为 bossGroup

接下来,我们来看看 ServerBootstrapchannel方法源码

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方法:

绑定1
绑定2
我们可以看到:

内部执行逻辑,只是:

  • 初始化注册 通道
  • 执行 绑定逻辑

那么,我们来看看 实现了 初始化注册 通道 逻辑 的 initAndRegister方法

初始化 并 注册 通道 —— initAndRegister方法:

初始化注册
我们可以看到:

内部执行逻辑,只是:

  • 创建初始化 通道
  • 注册 通道

我们先来看看,创建通道 的逻辑:

创建通道 —— newChannel方法:

创建通道

我们可以看到:

创建通道 的逻辑,只是:
调用 之前的 ServerBootstrap类的channel方法 中,赋值的 构造器无参构造,创建一个 Channel对象


我们接着,来看看 初始化通道 的逻辑:

初始化通道 —— init方法:

init

我们可以看到:

内部执行逻辑,只是:

  • 获取 之前创建的channelpipeline
  • pipeline 中,放入一个 ChannelInitializer,其作用是:

当 客户端连接时,向 pipeline 中,加入一个 ServerBootstrapAcceptor处理器
并且 处理器 的 构造参数之前用户传入的,保证了 扩展性
(构造参数 分别是 当前NioServerSocketChannel通道workerGroup用户传入的处理器与客户端连接通道的 TCP连接参数与客户端连接通道的 属性)

pipeline 中的 结构 如下:
结构


接着,我们来看看 注册通道 的 逻辑 是如何实现的:

注册通道 —— register方法:

注册通道
我们可以看到:

内部执行逻辑,只是:

  • 调用 父类 的 线程池 中的 某一个线程,执行 父类register方法

我们继续 debug,来看看 调用的方法 的 源码:
父类
我们可以看到:

内部执行逻辑,只是:

  • 参数所传channel 包装成 DefaultChannelPromise类型,然后继续调用 register方法

我们跟下去,来看看是如何执行的:
包装
这还是一个 包装方法,我们继续跟进:

register

我们可以看到:

内部执行逻辑,只是:

  • 进行 一系列检验
  • 线程池 中 获取一个线程,执行 register0方法

那么,我们来看看,NioEventLoop线程池如何 执行任务 的:

线程池任务的执行 —— execute方法:

任务执行
我们可以看到:

内部执行逻辑,只是:

  • 任务 丢到 taskQueue
    (下文中进行验证)
  • 开启线程,监听事件

任务丢入 taskQueue —— addTask方法:

任务入队
我们可以看到:

这里的代码逻辑,和 JDK 提供的 线程池 十分类似

我们继续跟进:
阻塞队列
可以看到:

任务 最终被提交进 taskQueue

同时也验证了 本人上文讲解没有问题!


那么,我们再来看看 开启线程,监听事件 都做了什么:

开启线程,监听事件 —— startThread()方法:

开启线程

我们可以看到:

内部执行逻辑,只是:

  • 判断修改 当前线程池的 状态
  • 真正的 “开启线程”逻辑

我们继续跟进,看看 “开启线程”逻辑 是如何实现的:
开启线程

我们可以看到:

这里 调用了 run方法

那么,我们再来看看 调用了 run方法,内部是如何执行的:
监听
处理请求

我们可以看到:

这里 死循环执行

  • 监听IO事件
  • 处理 selectedKeys 中的 所有key
  • 执行 taskQueue 中的 所有任务

而这些,正好对应本人之前博文所讲解的 NettyReactor模型
模型

那么,我们现在来看看 select方法 的内部执行逻辑:

超时阻塞式监听 —— select方法:

超时阻塞监听
我们可以看到:

此方法中:
调用了 NIO 的 API,死循环超时阻塞 式 地 监听 IO事件


接下来,本人来展示下 处理selectedKeys中的 keyprocessSelectedKey方法

selectedKeys 的 key处理 —— processSelectedKey方法:

此处调用逻辑嵌套了几层,本人就不展示 无关紧要 的几层了

我们直接来看 核心逻辑
key处理
我们可以看到:

此方法中:
根据 select方法 监听到的 不同事件,调用了 不同的处理逻辑


接下来,我们再来看看 执行taskQueue 中任务runAllTasks方法

taskQueue 中任务 的执行 —— runAllTasks方法:

此处调用逻辑嵌套了几层,本人就不展示 无关紧要 的几层了

我们直接来看 核心逻辑
任务执行

可以看到:

此方法中:
执行了 之前步骤,放入 taskQueue 中的 所有任务


那么,我们来看看 register0方法执行逻辑

register0方法:

0
我们可以看到:

内部执行逻辑,只是:

  • 调用 doRegister方法,执行 注册逻辑
  • 责任链模式,调用 当前通道pipelinechannelActive方法

我们继续跟进 doRegister方法,来看看是如何实现的:

doRegister方法:

nio
我们可以看到:

内部执行逻辑,只是:

  • 调用 NIOselector 注册API
    参数0,就相当于 NIOAPI 中的 OP_ACCEPT

到这里,注册通道 的逻辑,也就实现了!

(说实话,看到这里,本人差不多快吐了。。。
吐了
)


接下来,就是 绑定端口逻辑实现 的 doBind0 方法

绑定端口 —— doBind0方法:

绑定端口
我们可以看到:

内部执行逻辑,只是:

  • 当 之前的操作都成功后,执行 channelbind方法

那么,我们跟进去,开看看 绑定端口 的逻辑,是如何实现的:

bind方法:

bind

我们可以看到:

这个方法,是一个 包装方法

我们继续跟进:
包装
又是 包装方法,我们继续跟进:
bind

我们可以看到:

内部执行逻辑,只是:

  • 使用 线程池 中的 任一线程,执行 绑定端口 逻辑

我们继续跟进 invokeBind方法

invokeBind方法:

invoke
我们继续跟进:
继续

我们可以看到:

内部执行逻辑,只是:

  • 调用 unsafebind方法
    (但是注意:这里的 unsafe 并不是 Java的API中的Unsafe)

继续跟进:
继续跟进
我们继续跟进:
绑定端口

我们可以看到:

内部执行逻辑,只是:

  • 根据 Java版本,调用 Java原生API,实现 “绑定端口” 逻辑

(u1s1,跟到这里,我是真佛了,难道写 Netty 的大佬不晕乎吗?)


那么,到这里,ServerBootstrap 类bind方法,就讲解完毕了
本人现在来总结下:

总结:

  • 检验 当前状态,用户是否设置了 group属性channelFactory属性
    (这里的 group属性 是指 bossGroup)
  • 调用 Java原生API初始化注册 通道
  • 超时阻塞 式 监听 IO事件
  • 处理 selectedKeys 中的 所有key
  • 执行 taskQueue 中的 所有任务
  • 调用 Java原生API绑定端口

(反正我是写得头晕目眩了,不知道同学们有没有感受到同样的“痛楚”
痛楚
)


那么,到这里,Netty服务端 就准备完毕了!

之后就等 客户端 进行连接、发起请求

讲到这里,是真不容易,希望同样看到这里的同学,为我们共同的努力,留下你们的 关注 和 👍
理解理解

posted @ 2021-05-07 21:24  在下右转,有何贵干  阅读(162)  评论(0编辑  收藏  举报