阅读 1177

Netty源码分析——服务端启动流程解析

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...
}
复制代码

这里主要是做了几件事:

  1. 创建一个channel
  2. 绑定端口和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篇中说的,一开始BossReactor线程轮询到的基本都是ACCEPT事件,这里我们知道原因了。

继续追溯super方法,我们主要看下如下方法:

protected AbstractChannel(Channel parent) {
//这里的parent = null 上一步写死的
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
复制代码

这里初始化了idunsafepipeline。关于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中有个头有个尾,一开始分别叫HeadContextTailContext

至此,整个Channel创建就结束了。总结一下:通过工厂初始化一个Channel,这里的Channel由于是在服务端,使用的是NioServerSocketChannel,这个管道的构造方法中,会初始化一个原生的ServerSocketChannel,最后初始化pipelineunsafe。初始化pipeline的时候,又会初始化一开始管道中的HeadTail

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的动作流程:

  1. NioServerSocketChannel轮训到新的accept事件,分配新的Channel,管道中初始化对应的pipeline
  2. 新的Channel对应的pipeline中的Handler,就是我们用户自己设置的各种handler
  3. 用户自己设置的handler,就是通过这个ServerBootstrapAcceptor,被设置到新Channelpipeline中的。

具体的代码,我已经在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有两种:PowerOfTwoEventExecutorChooserGenericEventExecutorChooser。这两种都是通过加权平均的方式来进行选择的,总之我们是选择了一个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这条链路,一直走到unsafebind里:

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感兴趣。

总结

  1. 生成Channel,通过工厂,默认用反射。
  2. 生成的Channel默认是NioServerSocketChannel,对accept事件感兴趣。这里的Channel已经绑定了unsafepipeline(还有id
  3. 初始化Channel,这里主要是设置Channel的pipeline中的handler、attr和option。这里的handler是ServerBootstrapAcceptor
  4. 注册到EventLoopGroup里。最终是注册到NioEventLoop上去
  5. 在注册时,实际上的register0操作是异步的,register0的主要作用是把原生Channel注册到原生selector上。
  6. 执行doBind0,这里其实是和register0一起在执行的。doBind0是把channel注册到某个地址上
  7. 执行pipeline.read()方法,最终传递到AbstractNioChannel,并且把这个管道设置为对ACCEPT感兴趣。
文章分类
后端