本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
原来 Netty 的核心启动逻辑是这样的!
吴振华 · 285浏览 · 发布于2022-01-13 +关注

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

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

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

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

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

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

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

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

bind 流程

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

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

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

  • 创建 channel

  • 初始化 channel

  • 绑定 channel 至 group 内的某个 eventLoop 上

  • 绑定端口

下面我会逐一的分析。

创建 channel

先来看下 initAndRegister 方法。

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

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

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

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

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

  • id,channel 的标识

  • unsafe,用来操作底层数据读写

  • pipeline,handler的编排

所以,从这里可以得知,创建一个 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 哈。

来看下示例代码:

// 这样是没问题的,不用异步添加 ServerBootstrapAcceptor 
 ServerBootstrap sb = new ServerBootstrap(); 
    sb.channel(...).group(...).childHandler(...).handler(ourHandler); 
     
//这样的就需要异步添加 ServerBootstrapAcceptor 
ServerBootstrap sb = new ServerBootstrap(); 
sb.channel(...).group(...).childHandler(...).handler( 
    new ChannelInitializer<Channel>() { 
        @Override 
        protected void initChannel(Channel ch) throws Exception { 
            ChannelPipeline pipeline = ch.pipeline(); 
            pipeline.addLast(ourHandler); 
        } 
    } 
);

    因为在利用 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 都做了什么事:

    • 调用底层接口,将 channel 注册到 selector 上

    • 触发之前配置的 ChannelInitializer#initChannel

    • 异步添加绑定端口的任务到 eventLoop 中

    • 触发 Registered 事件,使之在 pipeline 上传递

    我们先看第一步 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 服务端的启动流程也就这么回事儿,我再用语言组织一下:

    • 创建 ServerSocketChannel(配套指定一个ID、底层操作 unsafe对象、pipeline)

    • 将配置的option、attr设置在创建的 ServerSocketChannel 上

    • 添加一个 ChannelInitializer 至 ServerSocketChannel 的 pipeline 中

    • 从 bossGroup 中选择一个 eventLoop,将 ServerSocketChannel 与之绑定,且之后生命周期内的操作都由这个 eventLoop 执行

    • 触发第三步添加的 ChannelInitializer 的 initChannel 方法,将用户配置的 handler 添加到 ServerSocketChannel 的 pipeline 中,且由于需要保证框架内置的 ServerBootstrapAcceptor 这个 handler 处于 inboundhandler 的最后一个,因此是异步添加。

    • 触发 registered 事件,使之在 pipeline 上传播

    • 调用 JDK 底层方法,绑定端口

    • 触发 active 事件,注册 ServerSocketChannel 感兴趣的事件(OP_ACCEPT),用于接收新连接

    • over

    最后

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

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

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


    相关推荐

    PHP实现部分字符隐藏

    沙雕mars · 1325浏览 · 2019-04-28 09:47:56
    Java中ArrayList和LinkedList区别

    kenrry1992 · 908浏览 · 2019-05-08 21:14:54
    Tomcat 下载及安装配置

    manongba · 970浏览 · 2019-05-13 21:03:56
    JAVA变量介绍

    manongba · 962浏览 · 2019-05-13 21:05:52
    什么是SpringBoot

    iamitnan · 1086浏览 · 2019-05-14 22:20:36
    加载中

    0评论

    评论
    坐标是江苏.南京,行业是互联网,技术是PHP和java,还有熟悉前后端等。
    分类专栏
    小鸟云服务器
    扫码进入手机网页