当前位置: 首页 > 科技观察

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

时间:2023-03-19 23:08:29 科技观察

你好,我是。在上一篇文章中,我们了解了Netty的启动过程,还有一个bind方法没有细说。在本文中,我们将重点关注bind方法,该方法也是触发Netty真正启动的方法。先打个预防针吧,源码没那么简单,有时候看起来有点乱,想要面试自信一点,还是要有点耐心,如果中间没看懂,没关系,最后我有一个总结,看完总结应该就清楚了。对了,如果可以的话,建议在电脑上看这篇文章,会更舒服。好吧,让我们从一张图片开始。bind的核心操作如下图所示。下面长长的源码分析也是为了理清流程,所以类名和方法名并不重要。重要的是要知道整体的流程:注意上图中的Channel指的是ServerChannel。bind的很多方法都是异步执行的,所以有的流程由主线程执行,有的由事件循环执行。这个需要注意一下,不然会感觉有点乱。先看这张图有个大概的印象,然后我们开始盘bind方法~bind过程从bind方法进入,其实就是调用了父类AbstractBootstrap#doBind。让我们简单看一下doBind方法。我用文字标注了要点:可以看到,这个方法主要做了以下四件事:创建通道初始化通道将通道绑定到组中的一个eventLoop下面我会一一分析端口。要创建频道,首先要看initAndRegister方法。从下面的源码可以看出,这个方法主要是创建一个Channel对象,然后初始化Channel,最后注册到eventLoop中。channelFactory.newChannel()使用反射来获取Channel对象。还记得我们在构建ServerBootstrap时设置的通道类型吗:.channel(NioServerSocketChannel.class)传入这个类就可以得到构造函数,然后调用newInstance得到一个对象。这将创建一个NioServerSocketChannel对象。通过继承链我们可以发现,NioServerSocketChannel会调用父类AbstractChannel的构造函数,这里会创建三个重要的东西:id,通道的标识,不安全,用于操作底层数据读写管道,以及处理程序的安排。从这里我们可以知道,创建一个Channel包会创建一个新的pipeline,即每个Channel都有自己的pipeline。初始化Channel创建Channel的操作后,立即初始化Channel,即init()方法。可以看出,初始化首先是将ServerBootstrap中配置的option和attr填充到创建的ServerSocketChannel中,这个很容易理解。然后在ServerSocketChannel的管道中插入一个ChannelInitializer。那么什么是ChannelInitializer?它实际上是一个抽象类,并且是一个入站处理程序。但是它是一个特殊的ChannelHandler,从ChannelInitializer类的注释可以看出:它的使命是简化注册后的初始化操作,可以理解为一个transithandler,一个工具类。它在初始化后从管道中移除。因此,使用ChannelInitializer来封装初始化逻辑。当通道注册到eventLoop中,会触发一个事件,然后调用ChannelInitializer#initChannel进行初始化,就这样。我们可以看到上面的initChannel逻辑是先添加ServerSocketChannel配置的handler,在我们的helloworld例子中是添加LoggingHandler。然后异步添加ServerBootstrapAcceptor处理程序。从名字上看,这个handler主要是接收和处理新的连接。Tips:此时initChannel逻辑还没有执行,等到后面触发事件时才会执行,执行线程为eventLoop线程。那么,看到这里,肯定有人会有疑问了。为什么在initChannel中异步添加了ServerBootstrapAcceptor?为什么要异步添加ServerBootstrapAcceptor?不直接?其实源码注释已经很清楚了(为了结构清晰,上面的注释都删掉了)simple在翻译中,用户可以使用ChannelInitializer来设置ServerSocketChannel的handler。注意是ServerSocketChannel的handler,不是childHandler。我们看示例代码://这个没问题,不用加ServerBootstrapAcceptorServerBootstrapsb=newServerBootstrap();sb.channel(...).group(...).childHandler(...).handler(ourHandler)异步;//这需要添加ServerBootstrapAcceptorServerBootstrapsb=newServerBootstrap();sb.channel(...).group(...).childHandler(...).handler(newChannelInitializer(){@OverrideprotectedvoidinitChannel(Channelch)throwsException{ChannelPipelinepipeline=ch.pipeline();pipeline.addLast(ourHandler);}});因为在使用ChannelInitializer设置handler的情况下,initChannel(...)方法只会在返回后调用的方法(init)中添加ServerBootstrapAcceptor的方法。因此,您需要确保以延迟的方式添加它们,以便将用户定义的处理程序放在ServerBootstrapAcceptor之前。简单的说就是让ServerBootstrapAcceptor成为ServerSocketChannel管道中的最后一个inboundHandler,从而调用用户自定义的handler逻辑。因为当事件传递给ServerBootstrapAcceptor时,不会继续通过管道传递,接收到的子通道会直接分配给workerGroup。如果用户定义的处理程序在ServerBootstrapAcceptor后面,则不会执行里面的逻辑。等于白加。不明白的可以把上面的话多看几遍,有点迷糊。说了这么多,我们再来看看ServerBootstrapAcceptor的内部逻辑。ServerBootstrapAcceptor的内部逻辑非常简单。就是根据selector获取新连接对应的channel(子通道),然后为其配置之前(初始化ServerBootstrap时)设置的childhandler、childoption、childattr,然后从workerGroup中选择一个eventLoop,并设置channelRegister到这个eventLoop:这样,新建的sub-channel之后的所有事件(读写等I/O事件)都负责从workerGroup中选出的eventLoop。至此,我们完成了init(channel)的操作。将通道注册到事件循环创建并初始化通道后,您需要将准备好的通道注册到事件循环。即上面的ChannelFutureregFuture=config().group().register(channel);(从返回值可以知道这是一个异步执行的过程)这个动作是从bossGroup中选择一个EventLoop,然后将channel注册到EventLoop上的selected。这个next()其实就是调用我们前面说的chooser来选择一个eventLoop,最后把这个eventLoop传递给AbstractUnsafe#register去执行注册逻辑。核心是register0方法。可以看到,register0操作无论如何都是由eventLoop线程执行的(所以与主线程是异步的)。我们来看看register0做了什么:调用底层接口,将channel注册到selector并触发之前配置的ChannelInitializer#initChannel异步添加绑定端口的任务到eventLoop触发Registered事件,这样就可以了通过管道。我们先来看第一步doRegister,看看如何将Channel注册到Selector中。因为我们都知道ServerSocketChannel是Netty定义的类,与JDK无关,那么如何适配JDK类呢?它是如何注册到JDK的Selector上的呢?看我圈出来的地方,其实就是将之前创建的JDKChannel注册到Selector中,NettyChannel会作为附件绑定到JDKChannel上。这样,每选择一个Selector,如果对应的JDKChannel上发生事件,Netty就可以从返回的JDKChannel的附件中获取自己的Channel,进而触发后续逻辑。然后看第二步pipeline.invokeHandlerAddedIfNeeded()。然后才会调用ChannelInitializer#initChannel添加handler,异步提交一个添加ServerBootstrapAcceptor的task,然后ChannelInitializer从pipeline中移除。下一步是触发Registered事件,该事件将传播到管道中的所有处理程序。只要是inboundhandler并且实现了下面的方法,就会被调用,所以如果你想在注册之后做一些逻辑处理,那么你可以创建一个handler,实现channelRegistered方法。好了,至此我们终于完成了Channel的创建、初始化、注册,接下来就是绑定端口的操作了。绑定端口绑定端口主要需要注意两点。一种是调用底层JDK绑定端口的实现,另一种是绑定后触发active事件,表示通道一切就绪。JDKChannel底层的bind方法,我就不说了,这个触发active事件比较关键,最终会触发AbstractNioChannel#doBeginRead,注册accept事件,这样ServerSocketChannel感兴趣的事件也注册了,我们能行得通!好吧,现在我们再来看这张图,一起来看看整体的流程。至于上面的代码,看不清楚也没关系。你只需要看懂下面这张图,就知道大致流程:现在看这张图。你觉得不一样吗?其实Netty服务器的启动过程就是这样的。我用文字来组织一下:创建一个ServerSocketChannel(支持ID,底层操作的不安全对象,管道)并在创建时设置配置的option和attr在ServerSocketChannel的ServerSocketChannel管道中添加一个ChannelInitializer,选择一个eventLoop从bossGroup中绑定ServerSocketChannel,然后生命周期中的操作会被这个eventLoop执行,触发第三步添加的ChannelInitializer的initChannel方法,并将用户配置的handler添加到pipeline中ServerSocketChannel,并且是异步添加的,因为要保证框架内置的ServerBootstrapAcceptor的handler是inboundhandler的最后一个。触发注册的事件传播并调用管道上的JDK底层方法,绑定端口触发active事件,注册ServerSocketChannel感兴趣的事件(OP_ACCEPT)接收新的连接过来最后相信通过上一张图和在最后一段,你应该对整个Netty的启动过程有了一个大概的了解。看不懂再看图,最后在电脑上看。看代码应该不难理解。重点是要区分,上面的Channel指的是Server端的Channel,然后是主线程和eventLoop线程的执行逻辑。根据情况,建议自己打断试试。下一篇我们会讲到Netty的Reactor模型。看来这东西面试被问到的几率还是挺高的。如果您仍然不知道,没关系。让我们在下一篇文章中轻松一点!我是的,从一点点到十亿,我们下去见~