文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

原来 Netty 的核心启动逻辑是这样的!

2024-12-02 09:51

关注

你好,我是yes。

上篇我们已经了解了 Netty 的启动流程,还剩一个 bind 方法没有细讲,这篇我们就着重的说下 bind 方法,这个方法也是触发 Netty 真正启动的方法。

先打个预防针,源码也不是那么简单的,有时候看着有点绕,如果你想面试的时候胸有成竹,还是得有点耐心的,如果中间没看懂没事,最后我有总结,看完总结之后应该会清晰的。

对了,如果有条件的话,建议在电脑上看这篇文章,会更加舒适。

好了,开局先来一张图,bind 的核心操作就如下图所示,下面长篇的源码分析也是为了说清楚这个流程,所以什么类名,方法名都不重要,重要的是知晓整体流程:

注意,上图的 Channel 指的是 ServerChannel。

bind 的很多方法都是异步执行的,所以有些流程是主线程执行,有些是 eventloop 执行的,这个需要注意下,不然会感觉有点乱。

先看看这张图大体有个印象,然后我们开始盘 bind 方法~

bind 流程

从 bind 方法进入,实际上就会调用父类 AbstractBootstrap#doBind。

我们来简单的看下 doBind 这个方法,要点我都用文字标明了:

可以看到,这个方法主要做了下面这四件事:

下面我会逐一的分析。

创建 channel

先来看下 initAndRegister 方法。

从下面的源码可以看到,这个方法主要就是创建一个 Channel 对象,然后初始化 Channel,最后将它注册到 eventLoop 上。

channelFactory.newChannel() 是利用反射得到一个 Channel 对象。还记得我们构建 ServerBootstrap 时候设置的 channel 类型吗:.channel(NioServerSocketChannel.class)

通过传入的这个 class 就可以得到构造器,然后调用 newInstance 即可得到一个对象。

这样就创建了一个 NioServerSocketChannel 对象。

通过继承链,我们可以发现 NioServerSocketChannel 会调用父类 AbstractChannel 的构造器,而在这里就会创建三个重要的东西:

所以,从这里可以得知,创建一个 Channel 配套就会新建一个 pipeline,即每个 Channel 都有自己的 pipeline。

初始化 channel

创建完 Channel 的操作之后,紧接着就初始化 Channel ,即 init() 方法。

可以看到,初始化首先就是把之前在 ServerBootstrap 时配置的 option 和 attr 塞到已经创建的 ServerSocketChannel 中,这个很好理解。

然后往 ServerSocketChannel 的 pipeline 中塞入一个 ChannelInitializer。

那 ChannelInitializer 是个什么玩意?它其实是一个抽象类,且是一个入站的 handler。

不过它是一个特殊的 ChannelHandler ,从 ChannelInitializer 类的注释就知道:

它的使命就是简化注册完毕后的初始化操作,可以理解为是一个中转的 handler,一个工具类。它在完成初始化动作之后会从 pipeline 中被移除。

所以,利用 ChannelInitializer 将初始化逻辑封装起来,当 channel 注册到 eventLoop 中之后会触发事件,然后就会调用 ChannelInitializer#initChannel 来执行初始化,仅此而已。

我们可以看到,上面 initChannel 逻辑是先添加之前配置给 ServerSocketChannel 的 handler,在我们的 helloworld 示例中就是添加 LoggingHandler。

然后再异步添加一个 ServerBootstrapAcceptor 这个 handler,从名字来看,这个 handler 主要是接收处理新连接。

小贴士:此时的 initChannel 逻辑还未执行,是要等到后面事件触发了才会执行,且执行的线程是 eventLoop 线程。

好了,看到这肯定有人会有疑问,为什么 initChannel 里面要异步添加 ServerBootstrapAcceptor?

为什么要异步添加 ServerBootstrapAcceptor?不直接添加?

其实源码注释说明的很清楚(上面为了清晰结构,把注释都删了)

简单翻译下,就是用户可能会用 ChannelInitializer 来设置 ServerSocketChannel 的 handler ,注意是ServerSocketChannel 的 handler,不是那个 childHandler 哈。

来看下示例代码:

  1. // 这样是没问题的,不用异步添加 ServerBootstrapAcceptor 
  2.  ServerBootstrap sb = new ServerBootstrap(); 
  3.     sb.channel(...).group(...).childHandler(...).handler(ourHandler); 
  4.      
  5. //这样的就需要异步添加 ServerBootstrapAcceptor 
  6. ServerBootstrap sb = new ServerBootstrap(); 
  7. sb.channel(...).group(...).childHandler(...).handler( 
  8.     new ChannelInitializer() { 
  9.         @Override 
  10.         protected void initChannel(Channel ch) throws Exception { 
  11.             ChannelPipeline pipeline = ch.pipeline(); 
  12.             pipeline.addLast(ourHandler); 
  13.         } 
  14.     } 
  15. ); 

因为在利用 ChannelInitializer 设置 handler 的情况下,initChannel(…)方法只会在该方法(init 内添加ServerBootstrapAcceptor的方法)返回后被调用。

因此,需要确保以延迟的方式添加,使得用户定义的 handler 都放在 ServerBootstrapAcceptor 前面。

简单地说,就是让 ServerBootstrapAcceptor 成为 ServerSocketChannel 的 pipeline 中最后一个 inboundHandler,这样用户定义的 handler 逻辑才会被调用到。

因为当事件传递到 ServerBootstrapAcceptor 过就不会再继续通过 pipeline 传递了,会将接待的子 channel 直接分配给 workerGroup了,如果用户自定义的 handler 在 ServerBootstrapAcceptor 后面的话,里面的逻辑是不会被执行的,等于白加。

不理解的可以多读几遍上面的话哈,有一点点小绕。

都说到这了,那就来看看 ServerBootstrapAcceptor 的内部逻辑吧。

ServerBootstrapAcceptor 的内部逻辑

很简单,就是根据 selector 得到新连接对应的 channel(子channel),然后为其配置之前(初始化ServerBootstrap时)设置的 childhandler、childoption、childattr,紧接着从 workerGroup 中选择一个 eventLoop ,将 channel 注册到这个 eventLoop 上:

这样,新建的子 channel 之后的所有事件(读、写等 I/O 事件),都由从 workerGroup 中选定的那个 eventLoop 负责。

至此,我们讲完了 init(channel) 的操作。

channel 注册至 eventLoop

创建且初始化完 channel 之后,就需要把已经准备完毕的 channel 注册到一个 eventLoop 中。

即上面的ChannelFuture regFuture = config().group().register(channel);(从返回值可以得知这是一个异步执行流程)

这个动作就是从 bossGroup 中选一个 EventLoop ,然后将 channel 注册到选定的 EventLoop 上。

这个 next() 实际就是调用我们之前提到的 chooser 来选择一个 eventLoop,最终会将此 eventLoop 传递到 AbstractUnsafe#register 中,执行注册逻辑,核心就在这个 register0 方法。

可以看到,无论如何都是由 eventLoop 线程来执行 register0 操作(所以对主线程而言,这是异步的)。

我们来看下 register0 都做了什么事:

我们先看第一步 doRegister,看看具体是怎么注册 Channel 至 Selector 上的。

因为我们都知道 ServerSocketChannel 是 Netty 定义的类,和 JDK 没任何关系,那如何与 JDK 的类适配呢?到底是如何注册到 JDK 的 Selector 上的呢?

看到我圈起来的地方没,实际会把之前创建的 JDK 的 Channel 注册到 Selector 上,而 Netty 的 Channel 会作为一个 attachment 绑定到 JDK 的 Channel 上。

这样一来,每次 Selector 的时候,如对应的 JDK Channel 有事件发生,则 Netty 都可以从返回的 JDK Channel 的 attachment 中获取自己的 Channel,然后触发后续的逻辑,这招叫移花接木。

然后再来看第二步 pipeline.invokeHandlerAddedIfNeeded()。

此时才会调用 ChannelInitializer#initChannel 来添加 handler,且异步提交了一个添加 ServerBootstrapAcceptor 的任务,随后将 ChannelInitializer 从 pipeline 中移除。

紧接着就是触发 Registered 事件,这个事件会随着 pipeline 传播至所有 handler,只要是入站的 handler,且实现了下面这个方法就会被调用,所以如果你想在注册完毕之后做一些逻辑处理,那么就可以创建一个 handler 且实现 channelRegistered 方法。

好了,至此我们终于把 Channel 的创建、初始化、注册给说完了,后面就是绑定的端口的操作了。

绑定端口

绑定端口主要有两个事情需要关注下,一个是调用底层 JDK 绑定端口的实现,二是绑定完之后触发 active 事件,表明 channel 一切都已就绪。

底层 JDK Channel 的 bind 方法,我就不说了,这个触发 active 事件比较关键,最终会触发 AbstractNioChannel#doBeginRead,注册 accept 事件,这样 ServerSocketChannel 感兴趣事件也注册完毕,可以干活了!

好了,现在我们再来看下这张图,来对整体的过程捋一捋,至于上面的那些代码理不清没事,你只要看懂下面这种图,知晓大体的流程就够了:

现在看这张图是不是有不一样的感觉?其实Netty 服务端的启动流程也就这么回事儿,我再用语言组织一下:

最后

好了,我相信通过最后一张图和最后一段话,你应该大致了解了整个 Netty 的启动流程。

如果不明白的话再看看图,最后是在电脑上看哈,看着代码应该不难理解的,重点就是分清上面的 Channel 指的是 Server 端的 Channel,然后主线程和 eventLoop 线程的执行逻辑,有条件的建议自己打断点试试。

下篇我们将谈谈 Netty 的 Reactor 模型,这玩意面试被问的几率好像挺大,如果你还不清楚,没事,下篇我们慢慢盘!

 

我是 yes,从一点点到亿点点,我们下篇见~

 

来源:yes的练级攻略内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯