前言
这是我 Netty源码阅读 活动的第三篇文章, 本篇开始带领大家去攻读ServerBootstrap.bind()
方法, 在第一篇文章我带领大家学习了怎么设置Netty
的backlog
队列, 第二篇文章我们一起学习了启动Netty
的配置详情, 感兴趣的可以去看一看, 链接贴在下面了
我的源码版本是跟着本次活动来的, 如果想跟着走一遍的建议使用同一个仓库, git命令如下
git clone https://github.com/arthur-zhang/netty-study.git 复制代码
图片看不清的, 点一下能放大, 应该不是只有我最近才知道吧.....
1 启动 bind() 方法
以 debug 的形式启动项目, 且在 bind 方法打断点
进入bind方法, 这里调用了InetSocketAddress
类的构造方法, 他的作用是判断我们传入的这个端口是否正确
我们可以看到一共有四个bind
方法, 最后都会进入到最后一个, 这就是我们常用的方法重写, 相同方法名, 参数不同, 实现相同的功能, 更方便我们多场景的调用
接下来我们进入 doBind()
方法看一看, 这也是主要的的实现
validate(); 方法是对参数进行校验的, 可以无视掉
ObjectUtil.checkNotNull() 方法也一样的, 判断参数是否为空
2 doBind() 方法
刚好一屏放得下, 不然还不知道要怎么搞
简单说说doBind()
方法都做了什么, 其实各种方法的命名是真的牛批, 一目了然
- 先是进行
初始化和注册
- 取出
channel
- 判断中途是否出现
报错
- 判断
初始化注册
是否成功
- 成功执行
doBind0
方法 - 失败继续判断是否有报错, 有就抛异常, 没有就执行
doBind
方法
咱就说, 过了三天我才想起来
doBind0
方法忘记讲了....Netty之服务启动且注册成功之后 - 掘金 (juejin.cn)
接下来我们一起学习一下initAndRegister()
方法
3 initAndRegister() 方法
首先可以看到, 先是创建了一个channel
, 再通过channel = channelFactory.newChannel();
为其进行赋值
channelFactory的类型是ReflectiveChannelFactory, 在我们执行ServerBootstrap.chennel()
方法的时候就将其初始化了, 具体可以看一下我上一篇文章: Netty服务端初始化详解
ctrl+f
搜索工厂
, 或者通过标题点进去都可以
3.1 ReflectiveChannelFactory.newChannel() 方法
这是channelFactory.newChannel()
的具体实现, 可以看到他就是实例化了传入的channel
对象
3.2 init()
init(channel)
方法是AbstractBootstrap抽象类的一个抽象方法, 该方法一共有两个实现, 分别是客户端Bootstrap
和服务端ServerBootstrap
, 参数就是上一步实例化好的channel
对象
我们分析的主要是服务端, 那直接点进ServerBootstrap
的实现就可以了
在init
方法中进行了各种初始化操作, 接下来我们挨个剖析
3.2.1 setChannelOptions(channel, newOptionsArray(), logger);
顾名思义设置channel
的options
点进了setChannelOptions
方法可以看到, 他就是遍历了入参的options
然后在下面的setChannelOption
方法中将其加入到channel.config().setOption
中, 那么我们回头看一下调用这个方法时传入的第二个参数是什么
可以看到, 他就是将当前的options
属性进行了转换然后就传过来了, 如果看过我上一篇文章的小伙伴那肯定对options
有些熟悉, 我们是在启动类那里对ServerBootstrap
执行过option
方法的时候进行配置的
那么setChannelOptions(channel, newOptionsArray(), logger);
方法的作用我们也就知道了, 他就是对传入的option
属性进行遍历配置, 不同环境不同的值进行不同的处理, 在上一篇文章中我也对常用的几种属性进行了讲解: Netty服务端初始化详解
3.2.2 setAttributes(channel, newAttributesArray());
这个方法我理解的真不透彻, 只能看出来是把当前的属性attr
遍历赋值给channel
, 是真没用过, 我也是Netty初学者, 见谅, 后面如果有更深入的理解我在补充..
attr
属性的设置还是在启动类那里, 通过ServerBootstrap.attr()
进行, 作用是给服务端通道绑定自定义属性
小声逼逼: 截图之后才看到忘写分号了...
3.2.3 addLast() 方法
上面都是一些获取属性的方法, 忽略他, 直接看红框部分
所以我们要仔细看的就上面两个红框部分了, 首先是添加config.handler()
, 这个上一篇文章也说过了, 是通过ServerBootstrap.handler()
方法设置的, 所以直接就是把handler
放入到addList
中
第二个红框部分, 是异步调用了ServerBootstrapAcceptor类的构造方法, 传入之前查询到的几个参数, 进行了初始化操作
小知识: 匿名内部类中传参要用
final
修饰
3.2.4 init()方法总结
所以我们看在init()
方法中都做了什么事:
- 对
option
进行初始化 - 对
attr
进行初始化 - 将
handler
添加到addLast
- 初始化
ServerBootstrapAcceptor
类
所以, 本次源码活动 - Netty部分的任务二也就完成了
如果看到这里了, 我觉得可以安排一个 👍
3.3 config().group().register(channel)
回到我们的initAndRegister
方法
如果你直接Ctrl+鼠标左键
点入register
方法的话, 你会发现这个方法是一个接口, 他有四个实现, 那么怎么找到这个实现呢
首先我们要找到我们的config.group()
是指的什么, 在上一篇文章中, 我们的配置类详解有讲到过serverBootstrap.group()
方法, 就是来设置这个类的, 那我们就可以看到这个方法他的类是什么了, 直接执行相应的实现不就可以了吗
记住第一个红框里面的
group
类具体是什么, 下张图见分晓
这里贴一张NioEventLoopGroup
类的继承实现图, 可以看到对应register
的方法实现类找到了MultithreadEventLoopGroup
可能有小伙伴第一反应是去找
EventLoopGroup
这个类, 建议每次直接找实现类, 然后看继承实现图, 例如本次我们就会发现EventLoopGroup
类根本就是一个接口类, 何谈实现register
方法呢
最后我们找到了MultithreadEventLoopGroup
类实现的register
方法, 发现他又调用了父类的next
方法..
我们知道, 我们现在的group
是NioEventLoopGroup
的对象, 在NioEventLoopGroup
里面初始化了跟传入线程数目相同的NioEventLoopGroup
对象, next()
方法就是用来计算出下一个选用的NioEventLoopGroup
对象是哪个
因为我们是打断点的形式, 所以一直F5就可以到最后执行的regiter
方法了, 在这里可以看到他实例化了一个DefaultChannelPromise
对象
我们的快捷键可能不一样, 具体情况具体分析
网络异常,图片无法展示|
3.3.1 register具体实现 register0
继续往下走, 我们会到真正的register
方法的实现类
在真正的实现类中,我们会发现他最终都会执行到register0
方法
3.3.2 doRegister()
但是当我们点到doRegister()
方法的时候会发现它里面实现是空的, 这个时候不要慌, 翻译大法好
可以看到, 他说子类可以覆盖这个方法, 那么我们就去找他的子类
因为我们打断点了, 所以一直按F5, 继续下一步就可以了, 最后我们会来到AbstractNioChannel
类的doRegister
方法, 这个就是最后执行的实现
后面是回家写的了, idea样式有些变化, 大家见谅一下
这一步主要的作用是: 用给定的Selector
注册当前Channel
, 返回selectionKey
, 也就是在这里进行了注册操作, 因为在走下去就是 JDK 的 nio 的方法了, 就不继续看了
这里注意一下传入的第三个参数为 0
3.3.2.1 pipeline.fireChannelRegistered();
pipeline
里面维护的是ChannelHandler
列表, 在注册之后会调用ChannelInboundHandler
的channelRegistered
方法
例如我在这个位置打上断点,
LoggingHandler
就会被打印
3.2.3 isActive() 方法
这是一个多态方法:
- 对于服务器: 用来判断监听是否启动
- 对于客户端: 用来判断TCP是否连接成功
当我使用TCP
工具进行连接我们的Netty
服务器的时候, 该方法返回true
3.2.4 pipeline.fireChannelActive()
这个方法的实现就是客户端第一次注册的时候会触发ChannelInboundHandler
的channelActive
方法, 通知ChannelHander
已经注册完成, 这时就会回调他们的channelActive
方法
还是上面的例子, 当用TCP
工具连接执行完该方法之后如截图所示
总结
- 在启动项目之后会调用
serverBootstrap
的bind
方法 - 通过
initAndRegister
方法对channel
进行初始化和注册
- 在
channelFactory
工厂里实例化channel
对象 - 执行
init
方法, 再该方法中对option, attr
进行初始化, 添加Handler
到addLast
, 初始化ServerBootstrapAcceptor
类 - 执行
register(channel)
方法进行注册 - 找到
register0
方法
- 具体的注册实现
doRegister()
- 进行通知
- 判断监听是否启动
- 如果启动且是首次连接, 则进行回调
over
那么本篇文章就讲完了, 按理来讲回调之后也应该讲一下的, 但是我发现继续写的话, 下一个任务的我也写完了, 本来21篇文章就不好写, 这个地方单独去讲一下也比较合适, 见谅见谅