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

NioServerSocketChannel注册源码解析

时间:2023-03-13 02:04:55 科技观察

没有办法,有办法还是可以的!无可奈何,只好挡道!上一章我们分析了Netty中NioServerSocketChaennl的创建和初始化。本章继续分析NioServerSocketChannel,NioServerSocketChannel是Netty官方封装的通道对象,旨在替代或包装JDK原生的SocketChannel对象,那么他是如何将NioServerSocketChannel与JDK的NIO相关代码联系起来的呢?一、源码入口找我们上节课源码方法主要分析的是initAndRegister方法。其实从名字就可以看出,这里就是通道的初始化和注册。我们继续返回这个方法。这个方法的查找可以参考上一章:AbstractBootstrap#initAndRegister我们跳过上节课已经分析过的代码直接来到注册相关的逻辑:ChannelFutureregFuture=config().group().register(channel);下面一一分析:config()现在我们创建ServerBootstrap,为什么选择这个我就不多说了:privatefinalServerBootstrapConfigconfig=newServerBootstrapConfig(this);我们可以看到他返回的是这个对象,这个对象是在再次创建ServerBootstrap的时候自动创建的。我们看,他的构造方法中有一个this,证明他持有对ServerBootstrap的引用,也就是说他可以通过这个对象获取ServerBootstrap中的所有属性和方法!上完这门课之后做什么?config().group()估计很多人已经猜出来了,我们直接点进群验证一下:@SuppressWarnings("deprecation")publicfinalEventLoopGroupgroup(){returnbootstrap.group();}这段代码就是获取我们再次构建ServerBootstrap时设置的bossGroup对象,有兴趣的可以跟进。这里比较简单,就不多说了。让我们继续继续回到主线,config().group().register(channel);通过上面代码的分析,我们知道group方法返回的是NioEventLoopGroup,我们进入register方法:发现这里并没有NioEventLoopGroup,但是通过前面几章的学习,我们知道NioEventLoopGroup是一个MultithreadEventLoopGroup的子类,所以我们的子类没有找父类,我们进入了MultithreadEventLoopGroup的源码:@OverridepublicChannelFutureregister(Channelchannel){//一般来说这里获取到的NioEventLoop是继承了WithSingleThreadEventLoopreturnext().register(channel);}这里,我们看到前面分析过的一段代码,next(),它调用了chooser.next();,chooser是我们在构建NioEventLoopGroup时创建的一个执行器选择器,next方法的作用是轮流返回一个线程执行器:Nio事件循环!记不清的同学可以回头看看NioEventLoopGroup初始化源码分析的章节代码!现在我们在前面几章的基础上,我们知道next()方法返回的是一个NioEventLoop类,我们进入register()方法查看:但是,我们找到了NioEventLoop相关的实现,但是基于什么我们之前学习过,可以知道NioEventLoop的父类是SingleThreadEventLoop,所以我们进入SingleThreadEventLoop#register(io.netty.channel.Channel):@OverridepublicChannelFutureregister(Channelchannel){//调用自己的注册方法returnregister(newDefaultChannelPromise(channel,this));}//没什么好说的继续追@OverridepublicChannelFutureregister(finalChannelPromisepromise){ObjectUtil.checkNotNull(promise,"promise");promise.channel().unsafe().register(this,promise);returnpromise;}我们一定能猜到这里的主要代码是:promise.channel().unsafe().register(this,promise);上一章我们分析了unsafe就是NioMessageUnsafe,但是register并没有它的实现:我们还是要到父类里面进入io.netty.channel.AbstractChannel.AbstractUnsafe#register(this,promise):大家注意一下到这里的参数:this:传入的是它本身,它是一个什么样的NioEventLoop,也就是说他传入了一个executorpromise:NioServerSocketChannel的wrapper对象,我们进入register方法中,分析main代码:@Overridepublicfinalvoidregister(EventLoopeventLoop,finalChannelPromisepromise){......................暂时忽略不必要的代码...................AbstractChannel.this.eventLoop=eventLoop;//注意此时thread=null所以returnfalseif(eventLoop.inEventLoop()){//实际注册注册选择器触发handlerAdded事件和channelRegistered事件register0(promise);}else{............暂时忽略不必要的代码..........}}AbstractChannel.this.eventLoop=事件循环;首先我们将上一步获取到的executor保存在NioServerSocketChannel中!这行代码就是一个有力的证明现在,每个Channel都绑定了一个NioEventLoop对象!如果(事件循环.inEventLoop()){//实际注册选择器触发handlerAdded事件和channelRegistered事件register0(promise);}注意:这里需要说明一下,在真正的调试过程中,不会走这个分支,而是走elsetaken分支是异步注册的。为了让大家更容易理解,我按照if分支来分析源码。其实变化不大。都是调用register0方法进行注册,只是一个是同步的,一个是异步的。关于异步,是Netty及其一个重要的知识点,后面我会单独放在一个章节来讲解!下面进入register0的源码:privatevoidregister0(ChannelPromisepromise){try{.................忽略代码........]//实际注册调用jdk底层的数据注册选择器//调用JDK底层的register()进行注册//io.netty.channel.nio。AbstractNioChannel.doRegisterdoRegister();neverRegistered=false;registered=true;//通知管道传播handlerAdded事件//触发handlerAdded事件触发任务添加事件pipeline.invokeHandlerAddedIfNeeded();safeSetSuccess(promise);//通知管道传播channelRegistered事件//firechannelRegisteredeventpipeline.fireChannelRegistered();//如果通道从未被注册过,只触发channelActive//如果通道注销再重新注册,则多个通道处于激活状态。//isActive()returnsfalse//Channel此时还没有注册绑定地址,所以处于inactive状态if(isActive()){.....................忽略不必要的代码......}}catch(Throwablet){//直接关闭通道,避免FD泄露。closeForcibly();closeFuture.setClosed();safeSetFailure(promise,t);}}二、源码分析doRegister();doRegister();真正的注册方法,这个方法是Netty自带的NioServerSocket与JDK连接的最佳方式很重要的一堂课!selectionKey=javaChannel().register(eventLoop().unwrappedSelector(),0,this);javaChannel()方法返回的是JDK原生的SocketChannel,在NioServerSocketChannel初始化时保存,还记得我们讲一下NIO开发Socket的流程吗?我们重点关注一下javaChannel().register的参数:eventLoop().unwrappedSelector():重新创建NioEventLoop时,会保存两个selector,一个是原来的JDKSelector,一个是Netty封装的selector,这里是原来的选择器!0:不关注任何事件this:this表示当前类,是NioServerSocketChannel类型,绑定了一个NioServerSocketChannel的对象到JDK原生选择器时,只需要通过SelectionKey.attachment(),而一个NioServerSocketChannel包含了一个JDK原生的Channel对象,你可以基于jdk原生的Channel进行各种读写操作!至此,我们在JDK中完成了通道与NIO中选择器的绑定,回到上一步:pipeline.invokeHandlerAddedIfNeededpipeline.invokeHandlerAddedIfNeeded();开始在回调管道通道中添加自定义事件:finalvoidinvokeHandlerAddedIfNeeded(){assertchannel.eventLoop().inEventLoop();if(firstRegistration){firstRegistration=false;//现在我们已经注册到EventLoop,是时候调用ChannelHandler的回调了,//在注册完成之前添加了什么。callHandlerAddedForAllHandlers();}}//callHandlerAddedForAllHandlersprivatevoidcallHandlerAddedForAllHandlers(){//task=PendingHandlerAddedTaskPendingHandlerCallbacktask=pendingHandlerCallbackHead;while(task!=null){task.execute();task=task.next;}}需要注意TaskHandlerAddedPendingCallback意为PendingType,他什么时候加载的?其实就是我们在初始化NioServerSocketChannel的时候调用addLast方法时赋值的,有兴趣的朋友可以自己看源码,这里直接输入:if(executor.inEventLoop()){callHandlerAdded0(ctx);}//输入callHandlerAdded0源码逻辑privatevoidcallHandlerAdded0(finalAbstractChannelHandlerContextctx){try{ctx.callHandlerAdded();}......................}//进入ctx.callHandlerAdded();finalvoidcallHandlerAdded()throwsException{if(setAddComplete()){handler().handlerAdded(this);}}你还记得handler()吗?我是在NioServerSocketChannel里面初始化的,我当时说了,程序当时在pipeline里面加了一个ChannelInitializer,这个ChannelInitializer就是在这里返回的!我们进入了ChannelInitializer#handlerAdded方法:@OverridepublicvoidhandlerAdded(ChannelHandlerContextctx)throwsException{if(ctx.channel().isRegistered()){if(initChannel(ctx)){removeState(ctx);}}}首先我们关注一个initChannel(ctx),privatebooleaninitChannel(ChannelHandlerContextctx)throwsException{//防止重入if(initMap.add(ctx)){try{//调用ChannelInitializer实现的initChannel()方法initChannel((C)ctx.channel());}catch(Throwablecause){...................}finally{ChannelPipelinepipeline=ctx.pipeline();if(pipeline.context(this)!=null){//ChannelInitializer将自身从Pipeline中移除。pipeline.remove(this);}}returntrue;}returnfalse;}这个方法会回调ChannelInitializer的抽象方法initChannel,在我们初始化的时候就完成了,我们需要找到这个抽象方法实现的地方,我们回去到上一课的代码:io.netty.bootstrap.ServerBootstrap#initvoidinit(Channelchannel){...................................;p.addLast(newChannelInitializer(){@OverridepublicvoidinitChannel(finalChannelch){finalChannelPipelinepipeline=ch.pipeline();//将用户自定义的handler添加到pipeline中handler就是当传入的handlerChannelHandlerhandler=config构建ServerBootStr.handler();if(handler!=null){pipeline.addLast(handler);}ch.eventLoop().execute(()->{pipeline.addLast(newServerBootstrapAcceptor(ch,currentChildGroup,currentChildHandler,currentChildOptions,currentChildAttrs));});}}上节课我们跳过了这段逻辑,只据说会在channel中加入一个ChannelInitializer实现,现在会回调它的initChannel方法:ChannelHandlerhandler=config.handler();if(handler!=null){pipeline.addLast(handler);}这段代码将添加客户端在构建ServerBootstrap时,将传入的处理程序添加到通道中。为了方便理解,我们假设用户没有设置handler,所以这个handler判断不通过,跳过它,我们继续:ch.eventLoop().execute(()->{pipeline.addLast(newServerBootstrapAcceptor(ch,currentChildGroup,currentChildHandler,currentChildOptions,currentChildAttrs));});这里异步注册了一个默认的Handler给pipeline流,为什么是异步的呢,暂时不说,我们认为是同步Add。此时我们的通道如下:ServerBootstrapAcceptor(finalChannelchannel,EventLoopGroupchildGroup,ChannelHandlerchildHandler,Entry,Object>[]childOptions,Entry,Object>[]childAttrs){this.childGroup=childGroup;this.childHandler=childHandler;this.childOptions=childOptions;this.childAttrs=childAttrs;enableAutoReadTask=newRunnable(){@Overridepublicvoidrun(){channel.config().setAutoRead(true);}};}我们可以看到,他会保存一系列的参数,包括WorkGroup、childHandler、childOptions、childAttrs这些参数都是我们创建的serverBootstrap时传入的参数也证明了这些参数是应用于客户端Socket连接的!关于ServerBootstrapAcceptor,后面会进行详细的分析。我们继续说,这里需要重点关注addLast方法,@OverridepublicfinalChannelPipelineaddLast(EventExecutorGroupgroup,Stringname,ChannelHandlerhandler){......忽略.............//NotificationAdd方法回调callHandlerAdded0(newCtx);returnthis;}添加的时候,他会回调内部生成的handlerAdded方法。记住,我们在介绍Netty基本架构的业务通道章节,调用addLast后,方法会被回调!这样,所有的方法都注册好了,我们继续返回ChannelInitializer#handlerAdded方法。调用**initChannel(ctx)**时:privatebooleaninitChannel(ChannelHandlerContextctx)throwsException{//防止重入if(initMap.add(ctx)){try{//调用ChannelInitializer实现的initChannel()方法initChannel((C)ctx.channel());}catch(Throwablecause){...................}finally{ChannelPipelinepipeline=ctx.pipeline();if(pipeline.context(this)!=null){//ChannelInitializer移除pipeline.remove(this);}}returntrue;}returnfalse;}我们最后会进入,我们会看到此时会做一个操作,删除当前类,当前类是谁,是ChannelInitializer,所以删除完成后,pipeline对象的结构体如图:至此,invokeHandlerAddedIfNeeded分析完毕。正如我所说,您可以自己调试代码。这段代码的意思是从HeadContext节点传播channelRegistered方法:至此基本分析完了NioServerSocketChannel的注册,可能有同学觉得少了一段:if(isActive()){if(firstRegistration){//当前通道处于活动状态时,触发channelActive事件这意味着我们需要再次开始读取//以便我们可以处理传入的数据。////参见https://github.com/netty/netty/issues/4805//开始读取事件beginRead();}}这段代码在第一次启动时不会被调用,因为此时的通道time绑定端口还没有正式启动,所以这里的isActive会返回false,相关逻辑等新连接解释的时候再分析!att绑定了选拔活动!注册完成后会回调handlerAdded方法。Netty会回调在重新初始化NioServerSocketChannel时注册的ChannelInitialization,添加新的连接访问器ServerBootstrapAcceptor,并删除自身!注册完成后会回调ChannelRegistered方法