一条流策略能应用到多个接口吗(带你深入学习NioServerSocketChannel的初始化源码)
一条流策略能应用到多个接口吗(带你深入学习NioServerSocketChannel的初始化源码)publicReflectiveChannelFactory(Class<?extendsT>clazz){ try{ //.channel传入的NioServerSocketChannel this.constructor=clazz.getConstructor(); }catch(NoSuchMethodExceptione){ ........................................ } } 我们可以看到,ReflectiveChannelFactory的逻辑也很简单,就只是将我们传入的NioServerSocketChannel 获取他的空构造方法,然后保存起来!我们再回过头来看一下ReflectiveChannelFactory做了什么:publicBchannel(Class<?extendsC>channelClass){ r
源码分析上一节课我们就NioEventLoop的初始化进行了一个初步的讲解,他是Netty很重要的一个类,后面还有针对它的分析,大家先对我前面介绍的组件有一个初步的认识!仔细地看,看到后面会有一种豁然开朗的感觉!
我们这一节课学习服务端的ServerSocketChannel的初始化源码,首先,我们还是老规矩,我告诉你你从哪里找,他是如何一步一步调用到ServerSocketChannel的,然后在进行分析!
一、入口寻找首先,我们大家在开发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(Class<?extendsC>channelClass){
returnchannelFactory(newReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass "channelClass")
));
}
为了分析过程中尽量做到简洁,我们只分析主线代码,支线代码,我会在用到的时候做具体的讲解:
我们看到上述的代码,将我们传入的通道类型NioServerSocketChannel包装为了一个ReflectiveChannelFactory对象,从名字我们基本可以知道,他是和反射相关的工厂,然后把ReflectiveChannelFactory对象传入到channelFactory方法里面,我们跟进去看下源码:
publicBchannelFactory(ChannelFactory<?extendsC>channelFactory){
.....忽略不必要代码......
//保存SocketChannel的包装对象
this.channelFactory=channelFactory;
returnself();
}
我们可以看到,他只是将我们的NioServerSocketChannel的包装对象给保存了起来!
我们再回过头来看一下ReflectiveChannelFactory做了什么:
publicReflectiveChannelFactory(Class<?extendsT>clazz){
try{
//.channel传入的NioServerSocketChannel
this.constructor=clazz.getConstructor();
}catch(NoSuchMethodExceptione){
........................................
}
}
我们可以看到,ReflectiveChannelFactory的逻辑也很简单,就只是将我们传入的NioServerSocketChannel 获取他的空构造方法,然后保存起来!
2. childHandler()我们再回头看childHandler方法,基本的原理是一样的:
publicServerBootstrapchildHandler(ChannelHandlerchildHandler){
this.childHandler=ObjectUtil.checkNotNull(childHandler "childHandler");
returnthis;
}
也是一样的逻辑,只是将 我们设置到出站入栈处理器保存起来,并没有做其他特别多的操作,其余的方法大家可以试着分析一下,全部都是将我们要设置的一些属性保存起来,供后续调用!
3. bind方法我们将一些属性保存了起来,那么在哪里调用呢? 最主要的方法就是这个bind()方法了,他是启动服务端的主要入口!
publicChannelFuturebind(intinetPort){
returnbind(newInetSocketAddress(inetPort));
}
首先,他将port端口包装为一个InetSocketAddress对象,和我们NIO开发中基本一致,我们继续跟下去:
publicChannelFuturebind(SocketAddresslocalAddress){
validate();
returndoBind(ObjectUtil.checkNotNull(localAddress "localAddress"));
}
//没什么好说的再往下跟
privateChannelFuturedoBind(finalSocketAddresslocalAddress){
//创建服务端的channel
//初始化并注册Channel,同时返回一个ChannelFuture实例regFuture异步
finalChannelFutureregFuture=initAndRegister();
..........其余代码后续分析..............
}
我们向下跟了两层,终于看到了大段的代码,我们只分析第一行代码,后面的代码再后面全部分析了,这一节课我们只关注和NioServerSocketChannel相关的代码,我们进入到initAndRegister方法里面
I. initAndRegisterfinalChannelFutureinitAndRegister(){
Channelchannel=null;
try{
//创建服务端的channel反射创建
//io.netty.channel.ReflectiveChannelFactory.newChannel
channel=channelFactory.newChannel();
//初始化channel
init(channel);
}case{
..............忽略..............
}
..............忽略..............
}
这里我们调用channelFactory.newChannel()创建了一个Channel对象,channelFactory是什么?我们再设置ServerSocketChannel的时候,内部将channelFactory包装为了ReflectiveChannelFactory对象,忘了的话看下前面!我们跟进io.netty.channel.ReflectiveChannelFactory#newChannel源码里面:
@Override
publicTnewChannel(){
try{
//反射创建NioServerSocketChannel
returnconstructor.newInstance();
}catch(Throwablet){
........................................
}
}
这段代码相信大家就及其熟悉了,利用我们再构建ReflectiveChannelFactory的时候保存的构造方法对象,创建出来一个NioServerSocketChannel对象! 因为之前获取的是无参构造,所以,我们需要进入到NioServerSocketChannel的无参构造里面寻找他的逻辑!
二、源码分析前面基本描述了我们要分析NioServerSocketChannel的源码入口,下面开始正式的分析它,我们进入到NioServerSocketChannel的无参构造方法:
/**
*创建一个新实例
*/
publicNioServerSocketChannel(){
//DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()
//newSocket创建一个channel
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
首先,我们先关注一下newSocket方法:
privatestaticServerSocketChannelnewSocket(SelectorProviderprovider){
try{
returnprovider.openServerSocketChannel();
}catch(IOExceptione){
.........................
}
}
newSocket方法使用provider创建了一个JDK底层的ServerSocketChannel,注意该对象是JDK原始的通道对象,至此,我们基本可以推断出,Netty的Channel是基于JDK的Channel进行封装的!我们继续回到无参构造方法:
publicNioServerSocketChannel(ServerSocketChannelchannel){
//保存对应的配置项同时保存关注连接事件OP_ACCEPT
super(null channel SelectionKey.OP_ACCEPT);
//创建一个配置类你保存的是当前对象以及jdk底层的socket
config=newNioServerSocketChannelConfig(this javaChannel().socket());
}
我们关注super方法,这里将上一步创建的JDK NIO底层的SocketChannel,和一个客户端接入事件传入进去,我们跟进看一下:
protectedAbstractNioMessageChannel(Channelparent SelectableChannelch intreadInterestOp){
super(parent ch readInterestOp);
}
//没什么好说的继续往下跟
protectedAbstractNioChannel(Channelparent SelectableChannelch intreadInterestOp){
//创建关键数据
super(parent);
//保存jdk底层channel
this.ch=ch;
//保存关注的事件
this.readInterestOp=readInterestOp;
try{
//设置为非阻塞
ch.configureBlocking(false);
}catch(IOExceptione){
。。。。。。。。。。。。。。。。。。。。
}
}
我们还是暂时先略过super方法,先分析下面的,下面的分析完,再反过来分析super方法:
- 首先将我们前面获取的JDK NIO Channel对象保存起来!
- 将前面传入的SelectionKey.OP_ACCEPT事件保存起来!
- 调用JDK NIO的方法,将原生的Channel设置为非阻塞!
这里会保存这几个对象,注意后面使用这些属性的时候,千万别想不起来这些属性哪里来的!
我们开始分析super方法
protectedAbstractChannel(Channelparent){
//保存channel
this.parent=parent;
//channel的唯一标识
id=newId();
//jdk底层操作读写的类
//unsafe操作底层读写
//NioServerSocketChannel创建的是NioMessageUnsafe这个是处理连接的
//NioSocketChannel创建的是NioByteUnsafe这个是处理字节读取的
unsafe=newUnsafe();
//管道pipeline负责业务处理器编排
pipeline=newChannelPipeline();
}
- 首先,我们会创建一个id,你可以把它认为是一个唯一标识,分为长标识和短标识,他们可以唯一标识一段管道,通过这行代码我们可以了解到,每一个Channel对象,都会由一个唯一的id与之对应!
- 创建一个newUnsafe 想要进入到这行代码,就要知道NioServerSocketChannel的继承关系,不然一点出来一大片就比如这样: image-20210428095058571
- ,你也不知道该看哪个源码,想要了解这个,我就必须要了解它的类的层次结构,NioServerSocketChannel的继承关系入下: image-20210428095001582
如图可以看到,NioServerSokcetChannel继承于AbstractNioMessageChannel,那么,我们自然而然就要进入到AbstractNioMessageChannel的实现:
@Override
protectedAbstractNioUnsafenewUnsafe(){
returnnewNioMessageUnsafe();
}
可以看到,这里返回的是一个NioMessageUnsafe,我希望大家着重记一个东西,就是NioServerSocketChannel对象里面的unsafe属性,是NioMessageUnsafe类型的!
我们知道了unsafe属性的类型之后,我们回到主线继续向下分析,该看pipeline的初始化了,我们进入到newChannelPipeline方法查看源码 这种通过查看上述的继承关系图,很轻易的就能够知道走到这个对象里面:
image-20210428095509567
protectedDefaultChannelPipelinenewChannelPipeline(){
returnnewDefaultChannelPipeline(this);
}
这里创建了一个DefaultChannelPipeline对象,我们继续往下跟:
protectedDefaultChannelPipeline(Channelchannel){
this.channel=ObjectUtil.checkNotNull(channel "channel");
succeededFuture=newSucceededChannelFuture(channel null);
voidPromise=newVoidChannelPromise(channel true);
tail=newTailContext(this);
head=newHeadContext(this);
head.next=tail;
tail.prev=head;
}
这里的逻辑还是比较清晰的,我们重点关注后四行代码,注意这里创建了一个双向链表,默认存在tail和head节点,结构如下图:
image-20210428103747413
我们通过上述分析可以知道,再初始化NioServerSocketChannel的时候 pipeline属性会默认创建一个双向链表,并默认存在两个节点,头节点和尾节点,并组成双向链表!
至此,NioServerSocketChannel的创建就完成了,
我们直接回到最开始反射创建Channel的地方initAndRegister方法:
channel=channelFactory.newChannel();
init(channel);
这里通过反射创建一个channel对象,经过上述的过程已经变成了一个初具雏形的Channel,我们需要再对他进行一次初始化的调用,以便后续使用,我们跟进到init方法 至于为什么选下图指示的,就不用我多说了:
image-20210428101000185
//.option方法传入的
setChannelOptions(channel options0().entrySet().toArray(EMPTY_OPTION_ARRAY) logger);
//.attr方法传入的
setAttributes(channel attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
这里只是将我们构建的 .option和.attr传入的参数,设置进通道里面!
//拿到管道
ChannelPipelinep=channel.pipeline();
//获取workerGroup
finalEventLoopGroupcurrentChildGroup=childGroup;
//获取先前设置的.childHandler
finalChannelHandlercurrentChildHandler=childHandler;
//获取先前设置的.childOption方法
finalEntry<ChannelOption<?> Object>[]currentChildOptions=
childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
//获取先前设置的.attr属性
finalEntry<AttributeKey<?> Object>[]currentChildAttrs=childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
- 先获取我们在初始化ServerSocketChannel的时候创建的管道
- 获取在创建ServerBootstrap的时候设置的childxxxx()相关的属性
p.addLast(newChannelInitializer<Channel>(){
@Override
publicvoidinitChannel(finalChannelch){
finalChannelPipelinepipeline=ch.pipeline();
//将用户自定义的handler添加进管道handler是在构建ServerBootStr的时候传入的handler
ChannelHandlerhandler=config.handler();
if(handler!=null){
pipeline.addLast(handler);
}
ch.eventLoop().execute(()->{
pipeline.addLast(newServerBootstrapAcceptor(
ch currentChildGroup currentChildHandler currentChildOptions currentChildAttrs));
});
}
});
p是我们在创建Channel对象的时候创建的管道,默认存在两个节点,我们在上面讲解过,那么addLast方法是干什么呢? 我们看一下:
privatevoidaddLast0(AbstractChannelHandlerContextnewCtx){
AbstractChannelHandlerContextprev=tail.prev;
newCtx.prev=prev;
newCtx.next=tail;
prev.next=newCtx;
tail.prev=newCtx;
}
这里我截取一段比较重要的代码,有关这一块详细的我会在后面的章节做具体讲解,从上面这段代码可以基本看明白,他是想双向链表追加一个handler,此时我们的管道就变成了如下图这种格式:
image-20210428104053217
三、总结- 通过ServerBootstrap设置一些属性,譬如:NioServerSocketChannel、handler等等
- bind方法,创建NioServerSocketChannel 保存JDK原生的SocketChannel,并设置为非阻塞创建并保存通道对应的唯一ID创建一个unsafe对象,他是NioMessageUnsafe类型的创建一个双向链表,存在Head和Tail节点
- 初始化创建完成的channel,设置自定义的配置,添加一个ChannelInitializer到双向链表!
至此NioServerSocketChannel初始化完成!