源码剖析
上一节课咱们就NioEventLoop的初始化进行了一个初步的解说,他是Netty很重要的一个类,前面还有针对它的剖析,大家先对我后面介绍的组件有一个初步的意识!认真的看,看到前面会有一种恍然大悟的感觉!
咱们这一节课学习服务端的ServerSocketChannel的初始化源码,首先,咱们还是老规矩,我通知你你从哪里找,他是如何一步一步调用到ServerSocketChannel的,而后在进行剖析!
一、入口寻找
首先,咱们大家再开发Netty服务端的时候,都会有这样几行代码:
ServerBootstrap serverBootstrap = new ServerBootstrap();
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外部查看源码:
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
为了剖析过程中尽量做到简洁,咱们只剖析主线代码,干线代码,我会在用到的时候做具体的解说:
咱们看到上述的代码,将咱们传入的通道类型NioServerSocketChannel
包装为了一个ReflectiveChannelFactory
对象,从名字咱们根本能够晓得,他是和反射相干的工厂,而后把ReflectiveChannelFactory对象传入到channelFactory办法外面,咱们跟进去看下源码:
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
.....疏忽不必要代码......
//保留SocketChannel的包装对象
this.channelFactory = channelFactory;
return self();
}
咱们能够看到,他只是将咱们的NioServerSocketChannel
的包装对象给保留了起来!
咱们再回过头来看一下ReflectiveChannelFactory
做了什么:
public ReflectiveChannelFactory(Class<? extends T> clazz) {
try {
//.channel 传入的 NioServerSocketChannel
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
........................................
}
}
咱们能够看到,ReflectiveChannelFactory的逻辑也很简略,就只是将咱们传入的NioServerSocketChannel,获取他的空构造方法,而后保存起来!
2. childHandler()
咱们再回头看childHandler办法,根本的原理是一样的:
public ServerBootstrap childHandler(ChannelHandler childHandler) {
this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
return this;
}
也是一样的逻辑,只是将 咱们设置到出站入栈处理器保存起来,并没有做其余特地多的操作,其余的办法大家能够试着剖析一下,全部都是将咱们要设置的一些属性保存起来,供后续调用!
3. bind办法
咱们讲一些属性保留了起来,那么在哪里调用呢? 最次要的办法就是这个bind()办法了,他是启动服务端的次要入口!
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
首先,他将port端口包装为一个InetSocketAddress对象,和咱们NIO开发中基本一致,咱们持续跟上来:
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
//没什么好说的 再往下跟
private ChannelFuture doBind(final SocketAddress localAddress) {
//创立服务端的channel
//初始化并注册 Channel,同时返回一个 ChannelFuture 实例 regFuture 异步
final ChannelFuture regFuture = initAndRegister();
..........其余代码后续剖析..............
}
咱们向下跟了两层,终于看到了大段的代码,咱们只剖析第一行代码,前面的代码再前面全副剖析了,这一节课咱们只关注和NioServerSocketChannel相干的代码,咱们进入到initAndRegister办法外面
I. initAndRegister
final ChannelFuture initAndRegister() {
Channel channel = 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
public T newChannel() {
try {
//反射创立 NioServerSocketChannel
return constructor.newInstance();
} catch (Throwable t) {
........................................
}
}
这段代码置信大家就及其相熟了,利用咱们再构建ReflectiveChannelFactory的时候保留的构造方法对象,创立进去一个NioServerSocketChannel对象! 因为之前获取的是无参结构,所以,咱们须要进入到NioServerSocketChannel的无参结构外面寻找他的逻辑!
二、源码剖析
后面根本形容了咱们要剖析NioServerSocketChannel
的源码入口,上面开始正式的剖析它,咱们进入到NioServerSocketChannel的无参构造方法:
/**
* 创立一个新实例
*/
public NioServerSocketChannel() {
//DEFAULT_SELECTOR_PROVIDER:SelectorProvider.provider()
//newSocket 创立一个channel
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
首先,咱们先关注一下newSocket办法:
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
.........................
}
}
newSocket办法应用provider创立了一个JDK底层的ServerSocketChannel,留神该对象是JDK原始的通道对象,至此,咱们根本能够推断出,Netty的Channel是基于JDK的Channel进行封装的!咱们持续回到无参构造方法:
public NioServerSocketChannel(ServerSocketChannel channel) {
//保留对应的配置项 同时保留关注连贯事件 OP_ACCEPT
super(null, channel, SelectionKey.OP_ACCEPT);
//创立一个配置类 你保留的是以后对象以及jdk底层的socket
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
咱们关注super办法,这里将上一步创立的JDK NIO底层的SocketChannel,和一个客户端接入事件传入进去,咱们跟进看一下:
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
//没什么好说的 持续往下跟
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
//创立要害数据
super(parent);
//保留jdk底层channel
this.ch = ch;
//保留关注的事件
this.readInterestOp = readInterestOp;
try {
//设置为非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
。。。。。。。。。。。。。。。。。。。。
}
}
咱们还是临时先略过super办法,先剖析上面的,上面的剖析完,再反过来剖析super办法:
- 首先将咱们后面获取的JDK NIO Channel对象保存起来!
- 将后面传入的SelectionKey.OP_ACCEPT事件保存起来!
- 调用JDK NIO的办法,将原生的Channel设置为非阻塞!
这里会保留这几个对象,留神前面应用这些属性的时候,千万别想不起来这些属性哪里来的!
咱们开始剖析super办法
protected AbstractChannel(Channel parent) {
//保留channel
this.parent = parent;
//channel的惟一标识
id = newId();
//jdk底层操作读写的类
//unsafe 操作底层读写
//NioServerSocketChannel创立的是 NioMessageUnsafe 这个是解决连贯的
//NioSocketChannel创立的是 NioByteUnsafe 这个是解决字节读取的
unsafe = newUnsafe();
//管道 pipeline 负责业务处理器编排
pipeline = newChannelPipeline();
}
- 首先,咱们会创立一个id,你能够把它认为是一个惟一标识,分为长标识和短标识,他们能够惟一标识一段管道,通过这行代码咱们能够理解到,每一个Channel对象,都会由一个惟一的id与之对应!
-
创立一个newUnsafe, 想要进入到这行代码,就要晓得NioServerSocketChannel的继承关系,不然一点进去一大片就比方这样:
-
,你也不晓得该看那个源码,想要理解这个,我就必须要理解他的类的层次结构,NioServerSocketChannel的继承关系入下:
如图能够看到,NioServerSokcetChannel继承于AbstractNioMessageChannel,那么,咱们自然而然就要进入到AbstractNioMessageChannel的实现:
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
}
能够看到,这里返回的是一个NioMessageUnsafe,我心愿大家着重记一个货色,就是NioServerSocketChannel对象外面的unsafe属性,是NioMessageUnsafe类型的!
咱们晓得了unsafe属性的类型之后,咱们回到主线持续向下剖析,该看pipeline的初始化了,咱们进入到newChannelPipeline
办法查看源码,这种通过查看上述的继承关系图,很轻易的就可能晓得走到这个对象外面:
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
这里创立了一个DefaultChannelPipeline对象,咱们持续往下跟:
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
这里的逻辑还是比拟清晰的,咱们重点关注后四行代码,留神这里创立了一个双向链表,默认存在tail和head节点,构造如下图:
咱们通过上述剖析能够晓得,再初始化NioServerSocketChannel的时候 pipeline属性会默认创立一个双向链表,并默认存在两个节点,头节点和尾节点,并组成双向链表!
至此,NioServerSocketChannel的创立就实现了,
咱们间接回到最开始反射创立Channel的中央initAndRegister
办法:
channel = channelFactory.newChannel();
init(channel);
这里通过反射创立一个channel对象,通过上述的过程曾经变成了一个初具雏形的Channel,咱们须要再对他进行一次初始化的调用,以便后续应用,咱们跟进到init办法,至于为什么选下图批示的,就不必我多说了:
//.option办法传入的
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
//.attr办法传入的
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
这里只是将咱们构建的 .option和.attr传入的参数,设置进通道外面!
//拿到管道
ChannelPipeline p = channel.pipeline();
//获取worker Group
final EventLoopGroup currentChildGroup = childGroup;
//获取先前设置的 .childHandler
final ChannelHandler currentChildHandler = childHandler;
//获取先前设置的 .childOption办法
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
//获取先前设置的 .attr属性
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
- 先获取咱们在初始化ServerSocketChannel的时候创立的管道
- 获取在创立ServerBootstrap的时候设置的childxxxx()相干的属性
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
//将用户自定义的handler增加进管道 handler 是在构建ServerBootStr的时候传入的 handler
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(() -> {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
});
}
});
p是咱们在创立Channel对象的时候创立的管道,默认存在两个节点,咱们在下面解说过,那么addLast办法是干什么呢? 咱们看一下:
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
这里我截取一段比拟重要的代码,无关这一块具体的我会在前面的章节做具体解说,从下面这段代码能够根本看明确,他是想双向链表追加一个handler,此时咱们的管道就变成了如下图这种格局:
三、总结
- 通过ServerBootstrap设置一些属性,譬如:NioServerSocketChannel、handler等等
-
bind办法,创立NioServerSocketChannel
- 保留JDK原生的SocketChannel,并设置为非阻塞
- 创立并保留通道对应的惟一ID
- 创立一个unsafe对象,他是NioMessageUnsafe类型的
- 创立一个双向链表,存在Head和Tail节点
- 初始化创立实现的channel,设置自定义的配置,增加一个ChannelInitializer到双向链表!
至此NioServerSocketChannel初始化实现!
本文由mdnice多平台公布
发表回复