文章详情

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

请输入下面的图形验证码

提交验证

短信预约提醒成功

聊聊NioServerSocketChannel的初始化源码

2024-12-03 02:26

关注

本文转载自微信公众号「源码学徒」,作者皇甫嗷嗷叫。转载本文请联系源码学徒公众号。

有道无术,术尚可求也!有术无道,止于术!

源码分析

上一节课我们就NioEventLoop的初始化进行了一个初步的讲解,他是Netty很重要的一个类,后面还有针对它的分析,大家先对我前面介绍的组件有一个初步的认识!仔细的看,看到后面会有一种豁然开朗的感觉!

我们这一节课学习服务端的ServerSocketChannel的初始化源码,首先,我们还是老规矩,我告诉你你从哪里找,他是如何一步一步调用到ServerSocketChannel的,然后在进行分析!

一、入口寻找

首先,我们大家再开发Netty服务端的时候,都会有这样几行代码:

  1. ServerBootstrap serverBootstrap = new ServerBootstrap(); 
  2. serverBootstrap.group(boss,work
  3.     .channel(NioServerSocketChannel.class) 
  4.     .childOption(ChannelOption.TCP_NODELAY,true
  5.     .childAttr(AttributeKey.newInstance("childAttr"),"childAttrValue"
  6.     .handler(...) 
  7.     .childHandler(...); 
  8. serverBootstrap.bind(8888).sync(); 

1. channel()

我们先具体分析下:ServerBootstrap再初始化过程中做了什么,我们具体看两个地方,channel和childHandler, 其余的大家可以自己试着看,都是一样的,我们进入到.channel内部查看源码:

  1. public B channel(Class channelClass) { 
  2.     return channelFactory(new ReflectiveChannelFactory
  3.         ObjectUtil.checkNotNull(channelClass, "channelClass"
  4.     )); 

为了分析过程中尽量做到简洁,我们只分析主线代码,支线代码,我会在用到的时候做具体的讲解:

我们看到上述的代码,将我们传入的通道类型NioServerSocketChannel包装为了一个ReflectiveChannelFactory对象,从名字我们基本可以知道,他是和反射相关的工厂,然后把ReflectiveChannelFactory对象传入到channelFactory方法里面,我们跟进去看下源码:

  1. public B channelFactory(ChannelFactory channelFactory) { 
  2.     .....忽略不必要代码...... 
  3.     //保存SocketChannel的包装对象 
  4.     this.channelFactory = channelFactory; 
  5.     return self(); 

我们可以看到,他只是将我们的NioServerSocketChannel的包装对象给保存了起来!

我们再回过头来看一下ReflectiveChannelFactory做了什么:

  1. public ReflectiveChannelFactory(Class clazz) { 
  2.     try { 
  3.         //.channel 传入的  NioServerSocketChannel 
  4.         this.constructor = clazz.getConstructor(); 
  5.     } catch (NoSuchMethodException e) { 
  6.        ........................................ 
  7.     } 

我们可以看到,ReflectiveChannelFactory的逻辑也很简单,就只是将我们传入的NioServerSocketChannel,获取他的空构造方法,然后保存起来!

2. childHandler()

我们再回头看childHandler方法,基本的原理是一样的:

  1. public ServerBootstrap childHandler(ChannelHandler childHandler) { 
  2.     this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler"); 
  3.     return this; 

也是一样的逻辑,只是将 我们设置到出站入栈处理器保存起来,并没有做其他特别多的操作,其余的方法大家可以试着分析一下,全部都是将我们要设置的一些属性保存起来,供后续调用!

3. bind方法

我们讲一些属性保存了起来,那么在哪里调用呢? 最主要的方法就是这个bind()方法了,他是启动服务端的主要入口!

  1. public ChannelFuture bind(int inetPort) { 
  2.     return bind(new InetSocketAddress(inetPort)); 

首先,他将port端口包装为一个InetSocketAddress对象,和我们NIO开发中基本一致,我们继续跟下去:

  1. public ChannelFuture bind(SocketAddress localAddress) { 
  2.     validate(); 
  3.     return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress")); 
  4.  
  5. //没什么好说的  再往下跟 
  6. private ChannelFuture doBind(final SocketAddress localAddress) { 
  7.         //创建服务端的channel 
  8.         //初始化并注册 Channel,同时返回一个 ChannelFuture 实例 regFuture  异步 
  9.         final ChannelFuture regFuture = initAndRegister(); 
  10.     ..........其余代码后续分析.............. 

我们向下跟了两层,终于看到了大段的代码,我们只分析第一行代码,后面的代码再后面全部分析了,这一节课我们只关注和NioServerSocketChannel相关的代码,我们进入到initAndRegister方法里面

4.. initAndRegister

  1. final ChannelFuture initAndRegister() { 
  2.         Channel channel = null
  3.         try { 
  4.             //创建服务端的channel  反射创建 
  5.             //io.netty.channel.ReflectiveChannelFactory.newChannel 
  6.             channel = channelFactory.newChannel(); 
  7.             //初始化channel 
  8.             init(channel); 
  9.         }case
  10.             ..............忽略.............. 
  11.         } 
  12.     ..............忽略.............. 

这里我们调用channelFactory.newChannel()创建了一个Channel对象,channelFactory是什么?我们再设置ServerSocketChannel的时候,内部将channelFactory包装为了ReflectiveChannelFactory对象,忘了的话看下前面!我们跟进io.netty.channel.ReflectiveChannelFactory#newChannel源码里面:

  1. @Override 
  2. public T newChannel() { 
  3.     try { 
  4.         //反射创建  NioServerSocketChannel 
  5.         return constructor.newInstance(); 
  6.     } catch (Throwable t) { 
  7.         ........................................ 
  8.     } 

这段代码相信大家就及其熟悉了,利用我们再构建ReflectiveChannelFactory的时候保存的构造方法对象,创建出来一个NioServerSocketChannel对象! 因为之前获取的是无参构造,所以,我们需要进入到NioServerSocketChannel的无参构造里面寻找他的逻辑!

二、源码分析

前面基本描述了我们要分析NioServerSocketChannel的源码入口,下面开始正式的分析它,我们进入到NioServerSocketChannel的无参构造方法:

  1.  
  2. public NioServerSocketChannel() { 
  3.     //DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider() 
  4.     //newSocket 创建一个channel 
  5.     this(newSocket(DEFAULT_SELECTOR_PROVIDER)); 

首先,我们先关注一下newSocket方法:

  1. private static ServerSocketChannel newSocket(SelectorProvider provider) { 
  2.     try { 
  3.         return provider.openServerSocketChannel(); 
  4.     } catch (IOException e) { 
  5.         ......................... 
  6.     } 

newSocket方法使用provider创建了一个JDK底层的ServerSocketChannel,注意该对象是JDK原始的通道对象,至此,我们基本可以推断出,Netty的Channel是基于JDK的Channel进行封装的!我们继续回到无参构造方法:

  1. public NioServerSocketChannel(ServerSocketChannel channel) { 
  2.     //保存对应的配置项  同时保存关注连接事件 OP_ACCEPT 
  3.     super(null, channel, SelectionKey.OP_ACCEPT); 
  4.     //创建一个配置类 你保存的是当前对象以及jdk底层的socket 
  5.     config = new NioServerSocketChannelConfig(this, javaChannel().socket()); 

我们关注super方法,这里将上一步创建的JDK NIO底层的SocketChannel,和一个客户端接入事件传入进去,我们跟进看一下:

  1. protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) { 
  2.     super(parent, ch, readInterestOp); 
  3.  
  4.  
  5.  
  6. //没什么好说的  继续往下跟 
  7. protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { 
  8.     //创建关键数据 
  9.     super(parent); 
  10.     //保存jdk底层channel 
  11.     this.ch = ch; 
  12.     //保存关注的事件 
  13.     this.readInterestOp = readInterestOp; 
  14.     try { 
  15.         //设置为非阻塞 
  16.         ch.configureBlocking(false); 
  17.     } catch (IOException e) { 
  18.        。。。。。。。。。。。。。。。。。。。。 
  19.     } 

我们还是暂时先略过super方法,先分析下面的,下面的分析完,再反过来分析super方法:

这里会保存这几个对象,注意后面使用这些属性的时候,千万别想不起来这些属性哪里来的!

我们开始分析super方法

  1. protected AbstractChannel(Channel parent) { 
  2.     //保存channel 
  3.     this.parent = parent; 
  4.     //channel的唯一标识 
  5.     id = newId(); 
  6.     //jdk底层操作读写的类 
  7.     //unsafe 操作底层读写 
  8.     //NioServerSocketChannel创建的是  NioMessageUnsafe  这个是处理连接的 
  9.     //NioSocketChannel创建的是 NioByteUnsafe 这个是处理字节读取的 
  10.     unsafe = newUnsafe(); 
  11.     //管道 pipeline 负责业务处理器编排 
  12.     pipeline = newChannelPipeline(); 

首先,我们会创建一个id,你可以把它认为是一个唯一标识,分为长标识和短标识,他们可以唯一标识一段管道,通过这行代码我们可以了解到,每一个Channel对象,都会由一个唯一的id与之对应!

创建一个newUnsafe, 想要进入到这行代码,就要知道NioServerSocketChannel的继承关系,不然一点出来一大片就比如这样:

你也不知道该看那个源码,想要了解这个,我就必须要了解他的类的层次结构,NioServerSocketChannel的继承关系入下:

如图可以看到,NioServerSokcetChannel继承于AbstractNioMessageChannel,那么,我们自然而然就要进入到AbstractNioMessageChannel的实现:

  1. @Override 
  2. protected AbstractNioUnsafe newUnsafe() { 
  3.     return new NioMessageUnsafe(); 

可以看到,这里返回的是一个NioMessageUnsafe,我希望大家着重记一个东西,就是NioServerSocketChannel对象里面的unsafe属性,是NioMessageUnsafe类型的!

我们知道了unsafe属性的类型之后,我们回到主线继续向下分析,该看pipeline的初始化了,我们进入到newChannelPipeline方法查看源码,这种通过查看上述的继承关系图,很轻易的就能够知道走到这个对象里面:

  1. protected DefaultChannelPipeline newChannelPipeline() { 
  2.     return new DefaultChannelPipeline(this); 

这里创建了一个DefaultChannelPipeline对象,我们继续往下跟:

  1. protected DefaultChannelPipeline(Channel channel) { 
  2.     this.channel = ObjectUtil.checkNotNull(channel, "channel"); 
  3.     succeededFuture = new SucceededChannelFuture(channel, null); 
  4.     voidPromise =  new VoidChannelPromise(channel, true); 
  5.  
  6.     tail = new TailContext(this); 
  7.     head = new HeadContext(this); 
  8.  
  9.     head.next = tail; 
  10.     tail.prev = head; 

这里的逻辑还是比较清晰的,我们重点关注后四行代码,注意这里创建了一个双向链表,默认存在tail和head节点,结构如下图:

我们通过上述分析可以知道,再初始化NioServerSocketChannel的时候 pipeline属性会默认创建一个双向链表,并默认存在两个节点,头节点和尾节点,并组成双向链表!

至此,NioServerSocketChannel的创建就完成了,

我们直接回到最开始反射创建Channel的地方initAndRegister方法:

  1. channel = channelFactory.newChannel(); 
  2. init(channel); 

这里通过反射创建一个channel对象,经过上述的过程已经变成了一个初具雏形的Channel,我们需要再对他进行一次初始化的调用,以便后续使用,我们跟进到init方法,至于为什么选下图指示的,就不用我多说了:

  1. //.option方法传入的 
  2. setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger); 
  3. //.attr方法传入的 
  4. setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY)); 

这里只是将我们构建的 .option和.attr传入的参数,设置进通道里面!

  1. //拿到管道 
  2. ChannelPipeline p = channel.pipeline(); 
  3. //获取worker Group 
  4. final EventLoopGroup currentChildGroup = childGroup; 
  5. //获取先前设置的 .childHandler 
  6. final ChannelHandler currentChildHandler = childHandler; 
  7. //获取先前设置的 .childOption方法 
  8. final Entry, Object>[] currentChildOptions = 
  9.     childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY); 
  10. //获取先前设置的 .attr属性 
  11. final Entry, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY); 

先获取我们在初始化ServerSocketChannel的时候创建的管道

获取在创建ServerBootstrap的时候设置的childxxxx()相关的属性

  1. p.addLast(new ChannelInitializer() { 
  2.     @Override 
  3.     public void initChannel(final Channel ch) { 
  4.         final ChannelPipeline pipeline = ch.pipeline(); 
  5.         //将用户自定义的handler添加进管道  handler 是在构建ServerBootStr的时候传入的  handler 
  6.         ChannelHandler handler = config.handler(); 
  7.         if (handler != null) { 
  8.             pipeline.addLast(handler); 
  9.         } 
  10.         ch.eventLoop().execute(() -> { 
  11.             pipeline.addLast(new ServerBootstrapAcceptor( 
  12.                 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); 
  13.         }); 
  14.     } 
  15. }); 

p是我们在创建Channel对象的时候创建的管道,默认存在两个节点,我们在上面讲解过,那么addLast方法是干什么呢? 我们看一下:

  1. private void addLast0(AbstractChannelHandlerContext newCtx) { 
  2.     AbstractChannelHandlerContext prev = tail.prev; 
  3.     newCtx.prev = prev; 
  4.     newCtx.next = tail; 
  5.     prev.next = newCtx; 
  6.     tail.prev = newCtx; 

这里我截取一段比较重要的代码,有关这一块详细的我会在后面的章节做具体讲解,从上面这段代码可以基本看明白,他是想双向链表追加一个handler,此时我们的管道就变成了如下图这种格式:

三、总结

通过ServerBootstrap设置一些属性,譬如:NioServerSocketChannel、handler等等

bind方法,创建NioServerSocketChannel

保存JDK原生的SocketChannel,并设置为非阻塞

至此NioServerSocketChannel初始化完成!

 

来源:源码学徒内容投诉

免责声明:

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

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

软考中级精品资料免费领

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

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

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

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

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

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

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