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

说说NioServerSocketChannel的初始化源码

时间:2023-03-23 11:22:16 科技观察

本文转载自微信公众号《源码学徒》,作者皇甫傲傲鸣。转载本文请联系出处学徒公众号。有办法无本事,本事还是可以求的!有技巧没办法,止步于技巧!源码分析上节课我们对NioEventLoop的初始化做了初步的讲解。是Netty的一个非常重要的类,后面会有更多的对其进行分析,大家先对我前面介绍的组件有个初步的了解吧!仔细看,看完会有一种豁然开朗的感觉!本节课我们将学习服务端ServerSocketChannel的初始化源码。首先,我们还是老规矩,我告诉你在哪找的,他是怎么一步步调用ServerSocketChannel的,然后再分析!1、找到入口首先,我们在开发Netty服务器的时候,都会有这样几行代码:ServerBootstrapserverBootstrap=newServerBootstrap();serverBootstrap.group(boss,work).channel(NioServerSocketChannel.class).childOption(ChannelOption.TCP_NODELAY,true).childAttr(AttributeKey.newInstance("childAttr"),"childAttrValue").handler(...).childHandler(...);serverBootstrap.bind(8888).sync();1.channel()下面来详细分析一下:ServerBootstrap在初始化过程中做了什么,我们看两个具体的地方,channel和childHandler,其他的大家可以自己试试看,都是一样的,我们进入.channel里面查看源码:publicBchannel(ClasschannelClass){returnchannelFactory(newReflectiveChannelFactory(ObjectUtil.checkNotNull(channelClass,"channelClass")));}为了分析过程尽量简洁,我们只分析主线代码和支线代码。NioServerSocketChannel被打包为ReflectiveChannelFactory对象。我们从名字上就可以基本知道它是一个与反射相关的工厂,然后将ReflectiveChannelFactory对象传入channelFactory方法中。我们跟进看看源码:publicBchannelFactory(ChannelFactorychannelFactory){.....忽略不必要的代码...//保存SocketChannel的包装对象this.channelFactory=channelFactory;returnsself();}我们可以看到他只是把我们的NioServerSocketChannel的包装对象保存起来!我们回头看看ReflectiveChannelFactory做了什么:publicReflectiveChannelFactory(Classclazz){try{//.channel传入NioServerSocketChannelthis.constructor=clazz.getConstructor();}catch(NoSuchMethodExceptione){。...............................................................}}我们可以看到,ReflectiveChannelFactory的逻辑也很简单,将我们传入的NioServerSocketChannel传入即可,获取其空的构造方法,保存即可!2.childHandler()让我们回顾一下childHandler方法。基本原理是一样的:publicServerBootstrapchildHandler(ChannelHandlerchildHandler){this.childHandler=ObjectUtil.checkNotNull(childHandler,"childHandler");returnthis;}也是同样的逻辑,只是把我们设置到outboundstackhandler中保存,然后做不做任何其他特殊操作。大家可以试试其他的方法分析一下,都是为了保存一些我们要设置的属性,方便后续调用!3.绑定方法说说保存一些属性,那么在哪里调用呢?最重要的方法是bind()方法,它是启动服务器的主要入口点!publicChannelFuturebind(intinetPort){returnbind(newInetSocketAddress(inetPort));}首先,他把port端口打包成一个InetSocketAddress对象,这个和我们NIO开发基本一样,我们继续跟进:publicChannelFuturebind(SocketAddresslocalAddress){validate();returndoBind(ObjectUtil.checkNotNull(localAddress,"localAddress"));}//没什么好说的然后跟着privateChannelFuturedoBind(finalSocketAddresslocalAddress){//在server端创建一个channel//初始化并注册Channel,返回一个ChannelFutureinstanceregFutureasynchronousfinalChannelFutureregFuture=initAndRegister();.......其余代码后续分析.......}我们往下看了两层,终于看到了一大段代码.我们只分析了第一行代码,然后分析了后面所有的代码。本课我们只关注NioServerSocketChannel相关的代码,进入initAndRegister方法4..initAndRegisterfinalChannelFutureinitAndRegister(){Channelchannel=null;try{//创建服务端通道反射创建//io.netty.channel。反射通道工厂。newChannelchannel=channelFactory.newChannel();//初始化channelinit(channel);}case{............忽略................}.......忽略......}这里调用channelFactory.newChannel()创建一个Channel对象。什么是通道工厂?当我们再次设置ServerSocketChannel时,内部的channelFactory会被封装成一个ReflectiveChannelFactory对象。忘记了,看前面!下面跟进io.netty.channel.ReflectiveChannelFactory#newChannel的源码:@OverridepublicTnewChannel(){try{//reflectioncreatesNioServerSocketChannelreturnconstructor.newInstance();}catch(Throwablet){..............................................................}}这段代码相信大家都很熟悉了,使用我们在构建ReflectiveChannelFactory时保存的构造函数对象来创建NioServerSocketChannel对象!因为前面获取的是无参结构,所以我们需要进入NioServerSocketChannel的无参结构,找到它的逻辑!2.源码分析以上基本描述了我们要分析NioServerSocketChannel的源码入口。下面开始正式分析。我们进入NioServerSocketChannel的无参结构体参考构造方法:/***新建一个实例*/publicNioServerSocketChannel(){//DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()//newSocket创建一个channelthis(newSocket(DEFAULT_SELECTOR_PROVIDER));}首先,让我们关注newSocket方法:privatestaticServerSocketChannelnewSocket(SelectorProviderprovider){try{returnprovider.openServerSocketChannel();}catch(IOExceptione){......................}}newSocket方法使用提供者创建了一个JDK底层的一个ServerSocketChannel。注意这个对象是JDK的原始通道对象。至此,我们基本可以推断出Netty的Channel是基于JDK的Channel进行封装的!回到无参构造方法:publicNioServerSocketChannel(ServerSocketChannelchannel){//保存对应的配置项,同时保存关注连接事件OP_ACCEPTsuper(null,channel,SelectionKey.OP_ACCEPT);//创建配置类你保存当前对象和底层的socketconfigjdk=newNioServerSocketChannelConfig(this,javaChannel().socket());}我们关注super方法。这里,传入了上一步创建的JDKNIO最底层的SocketChannel和一个客户端访问事件,我们跟进来看一下:;}//没什么好说的继续protectedAbstractNioChannel(Channelparent,SelectableChannelch,intreadInterestOp){//创建关键数据super(parent);//保存jdk底层channelthis.ch=ch;//保存关注的事件this.readInterestOp=readInterestOp;try{//设置为非阻塞ch.configureBlocking(false);}catch(IOExceptione){...................}}我们还是暂时跳过super方法,先分析如下,然后依次分析super方法:首先,保存我们之前获取的JDKNIOChannel对象!传入SelectionKey.OP_ACCEPT事件保存!调用JDKNIO方法,将原生Channel设置为非阻塞!这些对象将保存在这里。以后使用这些属性的时候,不要忘记这些属性是从哪里来的!下面分析super方法protectedAbstractChannel(Channelparent){//保存channelthis.parent=parent;//通道的唯一标识id=newId();//Jdk底层操作读写类//Unsafe操作底层读写//NioServerSocketChannelcreatesNioMessageUnsafe这是处理连接的//NioSocketChannelcreatesNioByteUnsafe这是读取字节的id,你可以认为是一个唯一标识,分为长标识和短标识,它们可以唯一标识一个channel,通过这行代码我们可以了解到,每个Channel对象都会有一个唯一的id与之对应!2。创建一个新的不安全的。想要进入这行代码,就必须知道NioServerSocketChannel的继承关系。有必要了解一下他的类的层次结构,NioServerSocketChannel的继承关系如下:图中可以看到,NioServerSokcetChannel继承自AbstractNioMessageChannel,那我们自然而然的进入AbstractNioMessageChannel的实现:@OverrideprotectedAbstractNioUnsafenewUnsafe(){returnnewNioMessage;Unsafe()可以看到,这里返回的是一个NioMessageUnsafe。希望大家记住一件事,就是NioServerSocketChannel对象中的unsafe属性是NioMessageUnsafe类型的!知道了unsafe属性的类型后,我们回到主线,继续往下分析。是时候查看管道的初始化了。我们进入newChannelPipeline方法查看源码。这是通过查看上面的继承关系图,走进这个对象就可以很容易的知道:this.channel=ObjectUtil.checkNotNull(channel,"channel");succeededFuture=newSucceededChannelFuture(channel,null);voidPromise=newVoidChannelPromise(channel,true);tail=newTailContext(this);head=newHeadContext(this);head.next=尾巴;尾巴。prev=head;}??这里的逻辑比较清晰。我们关注最后四行代码。注意这里创建了一个双向链表。默认有尾节点和头节点。结构如下:通过上面的分析我们可以知道,然后初始化NioServerSocketChannel的pipeline属性会默认创建一个双向链表,默认有两个节点,头节点和尾节点,形成双向链表链表!至此,NioServerSocketChannel的创建就完成了,我们直接回到最开始反射创建Channel的initAndRegister方法:channel=channelFactory.newChannel();init(channel);这里通过反射创建了一个通道对象。经过以上过程,就成为了一个初步的Channel。我们需要再次调用它进行初始化,以便后续使用,我们跟进init方法,至于为什么选择下图这个,我就不用多说了://.optionmethodpassedinsetChannelOptions(channel,options0().entrySet().toArray(EMPTY_OPTION_ARRAY),logger);//.attr方法传入setAttributes(channel,attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));这里我们只是设置了我们在通道中构建的.option和.attr传入的参数!//获取管道ChannelPipelinep=channel.pipeline();//获取workerGroupfinalEventLoopGroupcurrentChildGroup=childGroup;//获取之前设置的.childHandlerfinalChannelHandlercurrentChildHandler=childHandler;//获取之前设置的.childOption方法finalEntry,Object>[]currentChildOptions=childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);//获取之前设置的.attr属性finalEntry,Object>[]currentChildAttrs=childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);首先获取我们在初始化ServerSocketChannel时创建的pipeline,获取在创建ServerBootstrap时设置的childxxxx()相关属性p.addLast(newChannelInitializer(){@OverridepublicvoidinitChannel(finalChannelch){finalChannelPipelinepipeline=ch.pipeline();//Addtheuser-definedhandlertothepipelinehandlerisInbuildingServerBootS中HandlerChannelHandlerhandler=config.handler();if(handler!=null){pipeline.addLast(handler);}ch.eventLoop().execute(()->{pipeline.addLast(newServerBootstrapAcceptor(ch,currentChildGroup,currentChildHandler,currentChildOptions,currentChildAttrs));});}});p是我们在创建Channel对象时创建的pipeline。默认情况下有两个节点,正如我们上面解释的那样,那么addLast方法是做什么的呢?我们看一下:privatevoidaddLast0(AbstractChannelHandlerContextnewCtx){AbstractChannelHandlerContextprev=tail.prev;newCtx.prev=prev;newCtx.next=tail;prev.next=newCtx;tail.prev=newCtx;}这里我截取一段比较重要的代码,关于我将在接下来的章节中详细解释这一点。从上面的代码,我们基本可以理解他是想给双向链表添加一个handler。这时我们的pipeline变成了如下图的格式:三、总结1、通过ServerBootstrap设置一些属性,比如:NioServerSocketChannel、handler等bind方法,创建NioServerSocketChannel2、保存JDK原生的SocketChannel,并设置为非-blocking创建并保存通道对应的唯一ID创建一个不安全的对象,类型为NioMessageUnsafe创建一个双向链表,里面有Head和Tail节点初始化创建的通道,设置自定义配置,添加一个ChannelInitializer双向链表!3、至此,NioServerSocketChannel的初始化完成!