Netty源码分析——服务端启动流程解析
废话不多说,直接看个例子:
这也是官方给出的server端示例。
其中我们在Boss和Worker
这篇文章中已经说过了,服务端会有两个Group,分别具体做什么可以参考那篇文章。childHandler
方法指定子pipeline中的数据处理handler,关于这个设置方式,同样可以看一下Boss和Worker
这篇文章。
代码分析
我们直接跟这个bind
方法,一直可以追到AbstractBootstrap#doBind
:
private ChannelFuture doBind(final SocketAddress localAddress) {
//初始化并且注册
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
}
//sth...
}
这里主要是做了几件事:
- 创建一个
channel
- 绑定端口和
channel
看下initAndRegister
:
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
ChannelFuture regFuture = config().group().register(channel);
这里我们可以看到,先通过工厂new了一个Channel
,然后初始化这个Channel
。最后把这个Channel
注册到EventLoopGroup
上(config().group()
返回的是我们设置的Boss Group
{关于Boss Group,请看我的另外一篇文章Boss和Worker
})。
这里的channelFactory
默认是ReflectiveChannelFactory
,通过return clazz.getConstructor().newInstance();
方式创建Channel
。这个Channel
在服务端的demo里我们可以看到,是一个NioServerSocketChannel
:
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
//打开一个channel
return provider.openServerSocketChannel();
} catch (IOException e) {
}
}
public NioServerSocketChannel() {
//调用下面这个方法
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
public NioServerSocketChannel(ServerSocketChannel channel) {
//注意,这里是初始化了一个对SelectionKey.OP_ACCEPT感兴趣的管道
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
注意,上面我也写了,服务端的一开始初始化的是一个只对OP_ACCEPT
感兴趣的管道。回想我们之前在Boss和Worker
篇中说的,一开始Boss
的Reactor
线程轮询到的基本都是ACCEPT
事件,这里我们知道原因了。
继续追溯super方法,我们主要看下如下方法:
protected AbstractChannel(Channel parent) {
//这里的parent = null 上一步写死的
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
这里初始化了id
、unsafe
和pipeline
。关于unsafe
之前已经说过了,这个类是不允许在用户编码时使用的。关于pipeline
,这是一个处理数据的通道,后面会细说,这里只看初始化:return new DefaultChannelPipeline(this);
,这里用的是默认Pipeline
,继续看:
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;
这里我们可以看到pipeline中有个头有个尾,一开始分别叫HeadContext
和TailContext
。
至此,整个Channel
创建就结束了。总结一下:通过工厂初始化一个Channel
,这里的Channel
由于是在服务端,使用的是NioServerSocketChannel
,这个管道的构造方法中,会初始化一个原生的ServerSocketChannel
,最后初始化pipeline
和unsafe
。初始化pipeline
的时候,又会初始化一开始管道中的Head
和Tail
。
init channel
回忆一下,factory生成一个channel
以后,就开始初始化这个管道了,看下这个init(Channel)
方法:
//设置options
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
//设置attrs
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
//保留child options和child attrs
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
前半部分,主要是设置options和attr。Child options和Child attr是给新的Channel
使用的配置项。之前参考Boss和Worker
篇,如果轮训到ACCEPT事件以后,服务端创建一条NioSocketChannel
并且对READ感兴趣,这时候这些配置会设置给这个新的NioSocketChannel
。
看后半部分:
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
这里,我们对这个NioServerSocketChannel
的pipeline里,设置了一个特殊的Handler——ServerBootstrapAcceptor
。至此,我们同样结合Boss和Worker
篇里说的,这个Acceptor的动作流程:
NioServerSocketChannel
轮训到新的accept事件,分配新的Channel
,管道中初始化对应的pipeline
- 新的
Channel
对应的pipeline
中的Handler
,就是我们用户自己设置的各种handler
- 用户自己设置的handler,就是通过这个
ServerBootstrapAcceptor
,被设置到新Channel
的pipeline
中的。
具体的代码,我已经在Boss和Worker
中说过了,这里就不说了,有兴趣大家可以再看下ServerBootstrapAcceptor#channelRead
方法。
Channel注册到EventLoopGroup
继续看,都初始好了,会执行ChannelFuture regFuture = config().group().register(channel);
,这里的Group之前也说过了是NioEventLoopGroup
,父类是MultithreadEventLoopGroup
。
先听一下,这里其实能看出来一些关于Netty线程模型的东西。NioEventLoop
父类是SingleThreadEventLoop
,而NioEventLoop
是一个Reacotr
线程。这里的MultithreadEventLoopGroup
,实际上我们可以猜到,其实就是管理了多个SingleThreadEventLoop
。那么对于实际的类的关系来看就是:一个NioEventLoopGroup
管理了多个NioEventLoop
。
继续看register
,就一句:next().register(channel);
。这里先看next
方法,仍然一句:chooser.next()
。这个chooser
有两种:PowerOfTwoEventExecutorChooser
和GenericEventExecutorChooser
。这两种都是通过加权平均的方式来进行选择的,总之我们是选择了一个SingleThreadEventLoop
,这里就是NioEventLoop
。
继续看NioEventLoop#register
:
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
这里的unsafe是AbstractUnsafe
,在AbstractChannel
中,看register
方法,这里我们在Boss和Worker
里也讲解过了。
我们需要注意的是,#register(EventLoop eventLoop, final ChannelPromise promise)
方法中,第一次执行到这里的时候,是异步的,说白了register0
是被异步执行的(被线程池去执行):
继续看这里:
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
这是我在读一篇文章中发现的。这里其实isActive
是返回false的,因为底层的Channel
(jdk原生的ServerSocket)并没有被绑定。
那么pipeline.fireChannelActive()
是谁触发的呢?
我们回忆一下,刚刚我们说到,register0
实际上是被异步执行的,register0
执行了什么,主要是把Channel
注册到Selector
上。
这里异步会直接返回,读者可以往前追溯一下,如果这里异步直接返回,最终会在哪里直接返回掉。应该是#doBind(final SocketAddress localAddress)
,一下就返回到最前面去了。
继续看这里会执行什么:
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
其实就是看初始化和注册好了没,好了就直接调用doBind0
,否则就等初始化和注册好了再调用。doBind0
里实际上就是调用channel.bind(localAddress, promise)
,这里通过pipeline
->tail
->head
->unsafe
这条链路,一直走到unsafe
的bind
里:
boolean wasActive = isActive();
try {
//把channel bind 到某个低智商
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
//bind之后,这里的isActive会返回true
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
//在这里触发的是NioServerSocket对应的pipeline的channelActive
pipeline.fireChannelActive();
}
});
}
又是isActive
,但是这里的isActive
返回的是true,因为上面那句doBind
:
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
这里把原生的Channel
绑定到地址上。
beginRead
这里在执行pipeline.fireChannelActive();
时,会执行readIfIsAutoRead
,这里会执行isAutoRead
,把autoRead
设置为1,然后继续传递到pipeline.read();
方法,然后走到TailContext
里,再走到HeadContext
,再通过unsafe
->AbstractChannel
->AbstractNioChannel
,走到doBeginRead()
:
final SelectionKey selectionKey = this.selectionKey;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
//这里会变成selectionKey.interestOps(OP_ACCEPT)
selectionKey.interestOps(interestOps | readInterestOp);
}
这里最终自己这条NioServerSocketChannel
是只对OP_ACCEPT
感兴趣。
总结
- 生成
Channel
,通过工厂,默认用反射。 - 生成的
Channel
默认是NioServerSocketChannel
,对accept事件感兴趣。这里的Channel
已经绑定了unsafe
和pipeline
(还有id
) - 初始化
Channel
,这里主要是设置Channel
的pipeline中的handler、attr和option。这里的handler是ServerBootstrapAcceptor
。 - 注册到
EventLoopGroup
里。最终是注册到NioEventLoop
上去 - 在注册时,实际上的
register0
操作是异步的,register0
的主要作用是把原生Channel
注册到原生selector
上。 - 执行
doBind0
,这里其实是和register0
一起在执行的。doBind0
是把channel
注册到某个地址上 - 执行
pipeline.read()
方法,最终传递到AbstractNioChannel
,并且把这个管道设置为对ACCEPT
感兴趣。