「我正在参与掘金会员专属活动-源码共读第一期,点击参与」
前言
这是我 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篇文章就不好写, 这个地方单独去讲一下也比较合适, 见谅见谅
更新
Netty之第一次 TCP 连接时发生了什么 - 掘金 (juejin.cn)
本文内容到此结束了
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
我是 宁轩 , 我们下次再见