Netty服务端启动流程分析

 2023-01-06
原文作者:宁轩 原文地址:https://juejin.cn/post/7174018289180344357

前言

这是我 Netty源码阅读 活动的第三篇文章, 本篇开始带领大家去攻读ServerBootstrap.bind()方法, 在第一篇文章我带领大家学习了怎么设置Nettybacklog队列, 第二篇文章我们一起学习了启动Netty的配置详情, 感兴趣的可以去看一看, 链接贴在下面了

我的源码版本是跟着本次活动来的, 如果想跟着走一遍的建议使用同一个仓库, git命令如下

    git clone https://github.com/arthur-zhang/netty-study.git

图片看不清的, 点一下能放大, 应该不是只有我最近才知道吧.....

1 启动 bind() 方法

202212302135524981.png

以 debug 的形式启动项目, 且在 bind 方法打断点

202212302135529602.png

进入 bind 方法, 这里调用了InetSocketAddress类的构造方法, 他的作用是判断我们传入的这个端口是否正确

202212302135535483.png

我们可以看到一共有四个bind方法, 最后都会进入到最后一个, 这就是我们常用的 方法重写 , 相同方法名, 参数不同, 实现相同的功能, 更方便我们多场景的调用

接下来我们进入 doBind()方法看一看, 这也是主要的的实现

validate(); 方法是对参数进行校验的, 可以无视掉
ObjectUtil.checkNotNull() 方法也一样的, 判断参数是否为空

2 doBind() 方法

202212302135545424.png

刚好一屏放得下, 不然还不知道要怎么搞

简单说说doBind()方法都做了什么, 其实各种方法的命名是真的牛批, 一目了然

  • 先是进行初始化和注册

  • 取出channel

  • 判断中途是否出现报错

  • 判断初始化注册是否成功

    • 成功执行doBind0方法
    • 失败继续判断是否有报错, 有就抛异常, 没有就执行doBind方法

咱就说, 过了三天我才想起来doBind0方法忘记讲了.... Netty之服务启动且注册成功之后 - 掘金 (juejin.cn)

接下来我们一起学习一下initAndRegister()方法

3 initAndRegister() 方法

202212302135554985.png

首先可以看到, 先是创建了一个channel, 再通过channel = channelFactory.newChannel();为其进行赋值

channelFactory 的类型是 ReflectiveChannelFactory , 在我们执行ServerBootstrap.chennel()方法的时候就将其初始化了, 具体可以看一下我上一篇文章: Netty服务端初始化详解

202212302135572106.png

ctrl+f搜索工厂, 或者通过标题点进去都可以

3.1 ReflectiveChannelFactory.newChannel() 方法

202212302135581947.png

这是channelFactory.newChannel()的具体实现, 可以看到他就是实例化了传入的channel对象

3.2 init()

202212302135589308.png

init(channel)方法是 AbstractBootstrap 抽象类的一个抽象方法, 该方法一共有两个实现, 分别是客户端Bootstrap和服务端ServerBootstrap, 参数就是上一步实例化好的channel对象

我们分析的主要是服务端, 那直接点进ServerBootstrap的实现就可以了

202212302135595259.png

init方法中进行了各种初始化操作, 接下来我们挨个剖析

3.2.1 setChannelOptions(channel, newOptionsArray(), logger);

顾名思义设置channeloptions

2022123021360046210.png

点进了setChannelOptions方法可以看到, 他就是遍历了入参的options然后在下面的setChannelOption方法中将其加入到channel.config().setOption中, 那么我们回头看一下调用这个方法时传入的第二个参数是什么

2022123021360119211.png

2022123021360177112.png

可以看到, 他就是将当前的options属性进行了转换然后就传过来了, 如果看过我上一篇文章的小伙伴那肯定对options有些熟悉, 我们是在启动类那里对ServerBootstrap执行过option方法的时候进行配置的

2022123021360269213.png

2022123021360341514.png

那么setChannelOptions(channel, newOptionsArray(), logger);方法的作用我们也就知道了, 他就是对传入的option属性进行遍历配置, 不同环境不同的值进行不同的处理, 在上一篇文章中我也对常用的几种属性进行了讲解: Netty服务端初始化详解

3.2.2 setAttributes(channel, newAttributesArray());

2022123021360416315.png

这个方法我理解的真不透彻, 只能看出来是把当前的属性attr遍历赋值给channel, 是真没用过, 我也是 Netty 初学者, 见谅, 后面如果有更深入的理解我在补充..

2022123021360469216.png

attr属性的设置还是在启动类那里, 通过ServerBootstrap.attr()进行, 作用是给服务端通道绑定自定义属性

小声逼逼: 截图之后才看到忘写分号了...

3.2.3 addLast() 方法

2022123021360550017.png

上面都是一些获取属性的方法, 忽略他, 直接看红框部分

2022123021360634318.png

所以我们要仔细看的就上面两个红框部分了, 首先是添加config.handler(), 这个上一篇文章也说过了, 是通过ServerBootstrap.handler()方法设置的, 所以直接就是把handler放入到addList

2022123021360682019.png

第二个红框部分, 是异步调用了 ServerBootstrapAcceptor 类的构造方法, 传入之前查询到的几个参数, 进行了初始化操作

小知识: 匿名内部类中传参要用final修饰

3.2.4 init()方法总结

所以我们看在init()方法中都做了什么事:

  • option进行初始化
  • attr进行初始化
  • handler添加到addLast
  • 初始化ServerBootstrapAcceptor

所以, 本次 源码活动 - Netty部分的任务二 也就完成了

2022123021360750820.png

如果看到这里了, 我觉得可以安排一个 ?

3.3 config().group().register(channel)

回到我们的initAndRegister方法

2022123021360817621.png

如果你直接Ctrl+鼠标左键点入register方法的话, 你会发现这个方法是一个接口, 他有四个实现, 那么怎么找到这个实现呢

2022123021360946622.png

首先我们要找到我们的config.group()是指的什么, 在上一篇文章中, 我们的配置类详解有讲到过serverBootstrap.group()方法, 就是来设置这个类的, 那我们就可以看到这个方法他的类是什么了, 直接执行相应的实现不就可以了吗

记住第一个红框里面的group类具体是什么, 下张图见分晓

2022123021361030123.png

这里贴一张NioEventLoopGroup类的继承实现图, 可以看到对应register的方法实现类找到了MultithreadEventLoopGroup

可能有小伙伴第一反应是去找EventLoopGroup这个类, 建议每次直接找实现类, 然后看继承实现图, 例如本次我们就会发现EventLoopGroup类根本就是一个接口类, 何谈实现register方法呢

2022123021361090224.png

最后我们找到了MultithreadEventLoopGroup类实现的register方法, 发现他又调用了父类的next方法..

我们知道, 我们现在的groupNioEventLoopGroup的对象, 在NioEventLoopGroup里面初始化了跟传入线程数目相同的NioEventLoopGroup对象, next()方法就是用来计算出下一个选用的NioEventLoopGroup对象是哪个

2022123021361194125.png

因为我们是打断点的形式, 所以一直F5就可以到最后执行的regiter方法了, 在这里可以看到他实例化了一个DefaultChannelPromise对象

我们的快捷键可能不一样, 具体情况具体分析

2022123021361265026.png

3.3.1 register具体实现 register0

2022123021361376127.png

继续往下走, 我们会到真正的register方法的实现类

2022123021361452728.png

在真正的实现类中,我们会发现他最终都会执行到register0方法

2022123021361530529.png

3.3.2 doRegister()

2022123021361602230.png

但是当我们点到doRegister()方法的时候会发现它里面实现是空的, 这个时候不要慌, 翻译大法好

2022123021361704431.png

可以看到, 他说子类可以覆盖这个方法, 那么我们就去找他的子类

2022123021361766932.png

因为我们打断点了, 所以一直按F5, 继续下一步就可以了, 最后我们会来到AbstractNioChannel类的doRegister方法, 这个就是最后执行的实现

后面是回家写的了, idea样式有些变化, 大家见谅一下

这一步主要的作用是: 用给定的Selector注册当前Channel, 返回selectionKey, 也就是在这里进行了注册操作, 因为在走下去就是 JDK 的 nio 的方法了, 就不继续看了

2022123021361859233.png

这里注意一下传入的第三个参数为 0

3.3.2.1 pipeline.fireChannelRegistered();

pipeline里面维护的是ChannelHandler列表, 在注册之后会调用ChannelInboundHandlerchannelRegistered方法

例如我在这个位置打上断点, LoggingHandler就会被打印

2022123021361936734.png

3.2.3 isActive() 方法

2022123021362017335.png

这是一个多态方法:

  • 对于服务器: 用来判断监听是否启动
  • 对于客户端: 用来判断TCP是否连接成功

2022123021362073836.png

当我使用TCP工具进行连接我们的Netty服务器的时候, 该方法返回true

3.2.4 pipeline.fireChannelActive()

2022123021362232437.png

这个方法的实现就是客户端第一次注册的时候会触发ChannelInboundHandlerchannelActive方法, 通知ChannelHander已经注册完成, 这时就会回调他们的channelActive方法

2022123021362302238.png

还是上面的例子, 当用TCP工具连接执行完该方法之后如截图所示

总结

  • 在启动项目之后会调用serverBootstrapbind方法

  • 通过initAndRegister方法对channel进行初始化和注册

    • channelFactory工厂里实例化channel对象

    • 执行init方法, 再该方法中对option, attr进行初始化, 添加HandleraddLast, 初始化ServerBootstrapAcceptor

    • 执行register(channel)方法进行注册

    • 找到register0方法

      • 具体的注册实现doRegister()
      • 进行通知
      • 判断监听是否启动
      • 如果启动且是首次连接, 则进行回调

over

那么本篇文章就讲完了, 按理来讲回调之后也应该讲一下的, 但是我发现继续写的话, 下一个任务的我也写完了, 本来21篇文章就不好写, 这个地方单独去讲一下也比较合适, 见谅见谅

更新

Netty之第一次 TCP 连接时发生了什么 - 掘金 (juejin.cn)

本文内容到此结束了

如有收获欢迎点赞?收藏?关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问?欢迎各位大佬指出。

我是 宁轩 , 我们下次再见