为了加深对netty的理解,主要对netty的主要源码进行了debug,分析,有些细节没去深究,主要目的是通过源码流程加深理解。
netty的reactor模型如图1.1所示:
图1.1 netty reactor模型
对图1.1进行一个简要说明:
1) BossGroup 负责接收客户端的连接, WorkerGroup 负责网络的读写,两者的类型均为 NioEventLoopGroup
2) NioEventLoopGroup 相当于一个事件循环组, 包含多个NioEventLoop事件循环
3) NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个 NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯
4) NioEventLoopGroup 可以有多个线程, 即可以含有多个NioEventLoop
5) Boss NioEventLoop 循环执行的步骤分为3步
i.轮询accept 事件
ii. 处理accept 事件 , 与client建立连接 , 生成NioScocketChannel , 并将其注册到某个worker NIOEventLoop上的selector
iii.处理任务队列的任务 ,即 runAllTasks
6) 每个 Worker NIOEventLoop 循环执行的步骤
i. 轮询读写事件
ii. 在对应NioScocketChannel 处理读写事件
iii. 处理任务队列的任务 , 即 runAllTasks
7) 每个Worker NIOEventLoop 处理业务时,会使用pipeline(管道), pipeline中含有多个context,每个context中含有channelHandler,通过责任链的方式处理出站、入站(后续分析pipeline源码的时候再细讲这部分)
图2.1 demo示例
bossGroup和workerGroup都是NioEventLoopGroup(),debug追到new NioEventLoopGroup()中,先追到线程数的创建,如图2.2所示:
图2.2
在demo中,new NioEventLoopGroup()中可以指定线程数量,如果不指定会通过
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
设置线程数量,默认为cpu核心数的双倍。继续往下追代码,如图2.3
图 2.3
从图2.3可知,new了一个 EventExecutor数组,这个数组的大小就是创建的线程数量的大小,我的cpu为16核,因此这里children为32。接下来会通过for循环通过newChild(executor, args)给children每个元素赋值,图中可知newChild(executor, args)得到的就是NioEventLoop。
注:newChild(executor, args)没有跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析
接下来简要说下finally中的事情,如图2.4所示
图 2.4
如果给children创建NioEventLoop失败,会关闭终止NioEventLoop。如果children创建成功,会为每个NioEventLoop添加监听器,并把children加入到childrenSet集合中,如图2.5
图2.5
注:如何关闭的线程,创建的监听没有跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析
接下来回到demo中的ServerBootstrap b = new ServerBootstrap()跟进分析,如图2.6
图 2.6
将bossGroup(parentGroup)赋值给group属性,将workerGroup(childGroup)赋值给childGroup属性。
注:后续链式添加channel,option,handler等这里不跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析。
ServerBootstrap创建好后,接下来跟进分析b.bind,这是服务器启动的核心代码。
// 启动服务器
ChannelFuture f = b.bind(PORT).sync();
跟进到bind中,首先是进行了基本的启动校验,然后调用doBind()方法。
图 2.7
进入到doBind方法,去掉一些细枝末节,这里最核心的两个方法是initAndRegister()和doBind0()。首先分析initAndRegister()
图 2.8
图 2.9
initAndRegister()主要做了三件事情:通过工厂方法new了一个channel;初始化channel;将channel进行注册。
什么是channel:
官方描述为:A nexus to a network socket or a component which is capable of I/O * operations such as read, write, connect, and bind.即能够进行读,写,连接,绑定等IO操作的套接字或组件
这个channelFactory是啥,debug可以发现,这个channel是一个ReflectiveChannelFactory,并且给这个ReflectiveChannelFactory传入了一个NioServerSocketChannel.class。这个就是demo中调用.channel传入的。
图2.10
跟进到channel = channelFactory.newChannel(),通过反射将我们传入的NioServerSocketChannel.class创建出来。
图2.11
我们创建bossGroup的时候,都会给bossGroup添加一个NioServerSocketChannel,有必要深入跟进这个NioServerSocketChannel是个啥。在NioServerSocketChannel打断点,层层跟进,如下所示:
/**
* Create a new instance
*/
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
图2.12
最终会进到AbstractNioChannel方法中,将SelectableChannel设置为非阻塞。注意这个readInteresOp方法,该方法是调用super(null, channel, SelectionKey.OP_ACCEPT);传入的,可知,从SelectionKey.OP_ACCEPT可知NioServerSocketChannel负责监听的事件就是accept连接事件,这就是为什么bossGroup只负责处理连接的原因。
分析下AbstractNioChannel调用的父类方法,进入父类方法,设置了三个重要的属性,分别为ChannelId,Unsafe,DefaultChannelPipeline。
图2.13
这里不对这三个属性进行深入的分析,仅简要说明其含义。
官方解释:
ChannelId:Represents the globally unique identifier of a {@link Channel}。channel全局唯一id
Unsafe:Unsafe operations that should never be called from user-code,These methods are only provided to implement the actual transport,and must be invoked from an I/O thread:不能由用户调用的不安全操作。这些操作只能实现实际的传输并且必须由IO线程调用。
DefaultChannelPipeline implements ChannelPipeline:A list of {@link ChannelHandler}s which handles or intercepts inbound events and outbound operations of a * {@link Channel}:进行出站,入站事件操作用的
这里稍微说下DefaultChannelPipeline,为后续讲解pipeline做准备。
图 2.14
每一个pipeline都会默认初始化一个头结点和尾节点,且头尾节点是不会变化的。并且头结点实现了ChannelOutboundHandler和ChannelInboundHandler,尾节点只实现了ChannelInboundHandler。
/ A special catch-all handler that handles both bytes and messages.
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler
总结下:创建channel主要就是通过反射的方式new一个NioServerSocketChannel
对象,在new的过程找那个会创建一系列组件,且NioServerSocketChannel只负责监听accpet的事件。
接下来分析init(channel),这个方法比较长,先看前半段,如图:
图 2.15
首先是赋值了options属性和attrs属性,由于我们是讨论服务器是如何启动的,这里不对这两个属性进行进一步说明。
图 2.16
这里主要就是给DefaultChannelPipeline中加入new ServerBootstrapAcceptor接入器,这个接入器接受新的请求,新的请求通过事件循环器来处理。这里不深入跟进。
跟进代码:
1.
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
2.
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
3.
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
图 2.17
最终通过一个NioMessageUnsafe的register方法将EventLoop事件循环器绑定到NioServerSocketChannel上,然后调用register0()方法,该方法中会进行pipeline中handler链的调用,这里不展开说了,后面单独将pipeline的时候再说。
图 2.18
initAndRegister()执行完毕后,后续就要执行到最关键的doBind0()方法:
图 2.19
跟进代码,发现通过eventLoop起来一个线程执行绑定,如果成功就执行channel.bind,如果失败就给promise中设置错误信息(读者可自行了解promise编程,这里不展开说)。
图 2.20
跟进到channel.bind中:、
这里我们只关注bind, 看看到底是哪里绑定端口的。一步一步debug。如果
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
图 2.21
bind的调用比较深,不去展开分析。调到最后发现最终调到了java nio的方法,通过nio调用bind对端口进行了最终的绑定。端口绑定完后,最终会执行到safeSetSuccess(promise)告知promise已经成功创建。可以开始循环监听了
图 2.22
最终会一直在NioEventLoop中进行循环的监听:
图 2.23
至此,Netty服务端启动的代码分析到此结束了,源码中只对整体流程进行跟进,有些细节没有深入了解,有兴趣的读者可以执行debug跟进。后续讲到netty的pipeline, reactor源码进行分析。
Netty服务端启动总结:
1.创建2个EventLoopGroup,一个是bossGroup, 一个是workerGroup。
2.在bind方法中调用了initAndRegister和dobind两个重要方法
3.initAndRegister通过反射来创建,NioServerSocketChannel,并创建了一些重要属性,如pipeline,unsafe等。
4.在register0()方法成功调用后,在dobind方法中调用dobind0方法,最终会调用java nio中的bind方法去绑定端口,来完成服务端的启动。
5.最后NioEventLoop会一直循环监听事件。