ServerBootstrap初始化及启动流程
文章目录
源码版本4.1.66
ServerBootstrap
ServerBootstrap的关系如下,它和客户端Bootstrap都是AbstractBootstrap的子类,一些公共的抽象被定义在AbstractBootstrap中:
接下来看一段熟悉的代码:创建一个ServerBootstrap服务端并绑定到本机的8888端口:
public static void main(String[] args) {
// 创建boss和worker两个线程组
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
// option / handler / attr方法都定义在AbstractBootstrap中
// 定义在AbstractBootstrap中:handler: 针对服务的ServerSocketChannel的处理器(服务端接收的主要是客户端的连接所以是处理连接请求的处理器)
.handler(new LoggingHandler(LogLevel.INFO))
// 针对子线程组设置的参数都定义在ServerBootstrap(AbstractBootstrap的子类)中
// 为SocketChannel添加处理器链
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
}
});
try {
ChannelFuture future = serverBootstrap.bind(8888).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
各个方法的作用:
- group:设置父子线程组
- handler:设置针对服务端ServerSocketChannel的处理器
- childHandler:主要针对客户端SocketChannel的处理器
通过观察每个方法内部的逻辑,可以发现一些子类的参数(child开头的方法)主要定义在ServerBootstrap中。而一些可以提取出来的公共的抽象,如父group,handler等主要定义在AbstractBootstrap中。
channel方法
channel方法用于设置服务端使用的通道类型,这里我们用到的是NioServerSocketChannel,接下来一起看channel内部做了什么:
public B channel(Class<? extends C> channelClass) {
// 入参是要使用的通道类型,然后初始化一个反射创建Channel实例的工厂
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
这里将我们传入的 NioServerSocketChannel.class 传递给 ReflectiveChannelFactory 创建了一个工厂实例,看名字是一个使用反射创建对象的工厂:
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
// 获取目标类的构造方法
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
该工厂类的构造方法将获取了通道的构造方法,并提供了newChannel方法,用于在后续的使用中(newChannel)创建NioServerSocketChannel实例。
@Override
public T newChannel() {
try {
return constructor.newInstance();// 直接新建一个实例
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
到这里,我们大致可以了解到:后续实例化NioServerSocketChannel时,将会使用反射工厂ReflectiveChannelFactory调用其构造方法创建。
bind方法
在使用原生的nio编码时,我们需要先获取Selector和ServerSocketChannel,并为ServerSocketChannel设置非阻塞、绑定关注事件到选择器、并绑定到具体的端口。
在前面对NioEventLoopGroup的学习中,了解到其初始化时将会为每个NioEventLoop获取一个Selector,在里不难猜测:bind()方法可能会从父线程组中获取一个NioEventLoop,并将实例化的NioServerSocketChannel绑定到该NioEventLoop对应的Selector上。
进入bind()方法,首先涉及到对参数的校验,然后继续调用doBind()方法:
public ChannelFuture bind(SocketAddress localAddress) {
validate();// 校验有没有绑定 父线程组group 和 通道channel
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
- validate:检查有没有设置线程组参数和通道channel
- doBind:继续执行绑定操作
doBind方法
接下来一起看doBind()方法:
private ChannelFuture doBind(final SocketAddress localAddress) {
/**
* 其实到这里为止,在nio的操作中,jdk层面的服务端Channel-ServerSocketChannel还没有创建,
* 所以猜测这个方法内部可能会通过前面构建的反射通道工厂(ReflectiveChannelFactory)来初始化通道
* 并将通道注册到父线程组(boss-NioEventLoopGroup的某个线程的选择器上)
*/
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;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
// 为future添加监听器 在执行完之后将会调用
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;
}
}
doBind中有几个关键的步骤:
- 执行initAndRegister方法获取异步对象ChannelFuture-regFuture
- 获取regFuture的通道channel(这里不难想到获取的肯定是实例化好的NioServerSocketChannel)
- 为regFuture添加监听,并在监听中执行doBind0()方法
接下来一个个看,首先是initAndRegister。
initAndRegister方法
/**
* 初始化并注册通道
*/
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
/**
* 从前面可以知道,之前构建的通道工厂是 ReflectiveChannelFactory
* 所以这里肯定是通过这个线程工厂来调用channel()方法传入的NioServerSocketChannel.class
* 的构造方法来实例化一个NioServerSocketChannel
*/
channel = channelFactory.newChannel();// 截止到这里已经完成了通道初始化
init(channel);
} catch (Throwable t) {
if (channel != null) {
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
/**
* 这里config().group() 对应的是我们设置的boss-NioEventLoopGroup
* 其实就是把通道交给父线程组里面的一个线程去执行注册操作,
* 选择策略是通过 GenericEventExecutorChooser 提供的next()方法来获取一个 NioEventLoop
* {@link AbstractChannel.AbstractUnsafe#register(io.netty.channel.EventLoop, io.netty.channel.ChannelPromise)}
*/
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
这里也分成三步来看:
NioServerSocketChannel
channelFactory.newChannel()方法实际调用的是反射工厂ReflectiveChannelFactory的newChannel方法,而前面我们已经知道newChannel调用的是NioServerSocketChannel的构造方法,这里来看一下NioServerSocketChannel的构造方法将会做什么:
/**
* 这里会被ReflectiveChannelFactory通过反射的方式调用
*/
public NioServerSocketChannel() {
/**
* 1,newSocket(SelectorProvider provider) 会初始化一个ServerSocketChannel实例
* 2,继续向下调用构造方法 NioServerSocketChannel(SelectorProvider provider)
*/
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
- 这里有个关键点:newSocket将会获取一个JavaNIO的ServerSocketChannel
然后继续向下看:
public NioServerSocketChannel(ServerSocketChannel channel) {
/**
* 调用父类AbstractNioMessageChannel的构造方法
* 根据传进去的参数可以推测出:在内部将会为ServerSocketChannel绑定OP_ACCEPT事件
*/
super(null, channel, SelectionKey.OP_ACCEPT);
/**
* 为该服务端通道创建配置 NioServerSocketChannelConfig 是 NioServerSocketChannel 的内部类
* 1,javaChannel()方法返回的是上面的channel(ServerSocketChannel)
* ServerSocketChannel.socket() 方法会返回一个该channel对应的ServerSocket
* 2,new NioServerSocketChannelConfig 主要是初始化配置 并创建一个“分配器”:AdaptiveRecvByteBufAllocator
*/
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
这里我们可以看到一个熟悉的东西,就是在调用父类的构造时,传递了一个参数SelectionKey.OP_ACCEPT,这也代表后续这个通道关注的肯定是连接事件。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);// 这里传入的parent是null 在AbstractChannel中将会为该Channel实例化一个Pipeline
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
// ServerSocketChannel的初始化操作,设置为非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
这里主要分以下几个步骤:
- AbstractNioChannel调用父类AbstractChannel的构造方法,在AbstractChannel构造中将会初始化一个Pipeline(DefaultChannelPipeline)
- DefaultChannelPipeline初始化的时候会设置头尾两个处理器,分别是HeadContext和TailContext
- 然后调用nio的api将通道设置为非阻塞
完成了NioServerSocketChannel的实例化之后,下面执行init方法来做后续的的初始化操作,一起来看。
init(channel)
init抽象方法定义在AbstractBootstrap中,由子类Bootstrap做实现:
/**
* 该方法的主要目的是为NioServerSocketChannel中的pipeline设置处理器
* 1,设置demo中handler方法设置的处理器
* 2,ServerBootstrapAcceptor处理器,实例化时会传入通道
*/
@Override
void init(Channel channel) {
// 这两个分别是通过attr和options设置的参数
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
// 获取到通道
ChannelPipeline p = channel.pipeline();
// 子线程组对应的一些属性
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
/**
* 在pipeline初始化的时候会构建头尾两个处理器 HeadContext 和 TailContext
* 这里再将demo中设置的 LoggingHandler添加到pipeline中
* 这样就变成了 HeadContext -> LoggingHandler -> TailContext
*/
if (handler != null) {
pipeline.addLast(handler);
}
/**
* 后面的register方法会设置通道的执行线程 EventLoop
* 使用该线程异步执行为pipeline中添加一个处理器 ServerBootstrapAcceptor
*/
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// ServerBootstrapAcceptor 这个处理器是接收客户端连接请求的 这个后面再详细
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
这里是为通道添加处理器,需要注意的是:
- ServerBootstrapAcceptor在这里也会被添加到pipeline中(它的用处我们后面在说),当然还有我们demo中传入的Logginghandler
- 此时使用addLast方法添加处理器时,并不是实时的,而是先将处理器包装成一个AbstractChannelHandlerContext并添加到队列pendingHandlerCallbackHead中
至此算是为我们的channel对应的pipeline设置好了处理器,接下来继续。
config().group().register(channel)
这一步的主要目的是:将通道注册到线程组中某个线程的Selector上。
**config().group()**获取的是我们设置的父线程组(NioEventLoopGroup),然后调用register方法:
// MultithreadEventLoopGroup 的 register 方法
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
这里的next()方法是使用之前介绍的NioEventLoopGroup中初始化的选择器GenericEventExecutorChooser来选取一个NioEventLoop执行register方法,其调用的是父类SingleThreeadEventLoop的register方法:
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
然后到了io.netty.channel.AbstractChannel.AbstractUnsafe#register方法:
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 省略...
// 当前通道线程设置为eventLoop
AbstractChannel.this.eventLoop = eventLoop;
/**
* 判断当前运行的线程和异步线程eventLoop是否同一个
* 如果是同一个直接调用register0注册
* 如果不是就用eventLoop异步执行,因为我们是主线程启动,所以这里进入else
*/
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
// 省略...
}
}
}
这里会调用NioEventLoop的execute方法来执行register0方法,下面先来看这个execute方法做了什么:
SingleThreadEventExecutor.execute()
它并不像我们想像的简单的把任务交给一个线程去执行:
@Override
public void execute(Runnable task) {
ObjectUtil.checkNotNull(task, "task");// 校验任务是否为空
execute(task, !(task instanceof LazyRunnable) && wakesUpForTask(task));
}
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);// 将任务放到队列中
if (!inEventLoop) {// 如果不是当前线程就执行代码块中的方法
startThread();
if (isShutdown()) {
// 省略
}
}
if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}
前面我们知道NioEventLoopGroup在初始化的时候会为每个NioEventLoop创建一个多生产者单消费者形式的队列,这里的addTask()就是将任务放到这个队列中。
然后进入到if块当中,执行startThread方法:
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
然后是doStartThread方法:
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
// 省略...
try {
SingleThreadEventExecutor.this.run();// 关键是这一行
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
....
}
}
});
}
前面介绍NioEventLoopGroup初始化的时候已经知道ThreadPerTaskExecutor作为执行器会创建一个线程用来执行任务,我们这里的任务就是调用 SingleThreadEventExecutor.this.run() 方法。
所以这里就是由执行器创建出来一个线程,来调用NioEventLoop的run方法。
这里我们已经可以总结出NioEventLoop的一个作用了:作为一个线程,初始化时的一些任务将会塞到mpscQueue中由NioEventLoop的run方法执行。
而出于对Reactor模型的了解,不难想到这个父线程组必定也承担了处理客户端连接的作用。所以可以猜到在NioEventLoop的这个run方法中,也有对客户端连接的处理。这个我们后面再详细探究。
下面回到register0方法:
AbstractChannel.AbstractUnsafe#register0
private void register0(ChannelPromise promise) {
try {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
/**
* 这一步主要是执行注册操作 {@link AbstractNioChannel#doRegister()}
* 将通道注册到当前 eventLoop 对应的 unwrappedSelector 上
*/
doRegister();
registered = true;
// 添加之前设置的处理器
pipeline.invokeHandlerAddedIfNeeded();
// 设置promise为成功 这时执行完再调用isDone就是true
safeSetSuccess(promise);
// 触发通道注册完成事件 调用处理器们的 channelRegistered() 方法
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// 省略
beginRead();
}
}
} catch (Throwable t) {
// ...
}
}
首先执行AbstractNioChannel#doRegister()方法,该方法的内容比较简单:就是将通道注册到该EventLoop对应的selecor上,这里需要注意的是:注册时,指定的ops=0,所以猜测后面将会在某个地方对ops赋值为OP_ACCEPT。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
/*
* 然后将通道注册到该Selector上
* 这里可能会注意到一个问题这里注册时候ops=0(什么都不做)
* 所以猜测后面肯定有某个地方会修改ops的值
*/
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
// 省略...
}
}
}
接下来执行**pipeline.invokeHandlerAddedIfNeeded()**将之前定义的处理器添加到pipeline中。
接下来执行到 pipeline.fireChannelRegistered() 这行代码,目的是调用所有处理器对应的通道注册事件,也就是channelRegistered()方法。
至此,register0方法就执行完毕了,这里来总结一下register方法的作用:把通道(NioEventLoopGroup)交给线程组中的一个NioEventLoop,并注册到该EventLoop的Selector上。
需要注意的是,到此为止,我们看到了通道的注册,但是channel在selector上的关注事件还未设置,且通道还未绑定到具体的地址上。
然后回到doBind方法。
在doBind方法中,执行完initAndRegister方法之后返回异步结果ChannelFuture,然后执行doBind0方法:
doBind0方法
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
/*
* 前面的 register0 方法中,在执行channelRegistired方法之前,执行了safeSetSuccess.
* 然后会进入这个方法
*/
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {// 这里阻塞等待doRegister()执行完成
/**
* 1, 执行绑定操作
* 2,绑定之后添加绑定失败事件:ChannelFutureListener.CLOSE_ON_FAILURE
*/
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
System.out.println("绑定完成....");
} else {// 失败
promise.setFailure(regFuture.cause());
}
}
});
}
这里的绑定操作最终会来到 AbstractChannelHandlerContext#bind 方法,然后调用处理器链中每个处理器bind方法:
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
// ...
// 无关代码省略...
next.invokeBind(localAddress, promise);
return promise;
}
// 调用pipeline中每个处理器的bind方法
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
最终,在HeadContext的bind方法中,将会调用unsafe中的bind方法:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// 省略一下代码
boolean wasActive = isActive();
try {
doBind(localAddress);// 将Channel绑定到指定的端口上
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// 再封装一个任务调用通道的channelActive方法
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
在该方法中主要分两步:
- 使用javanio的api将channel绑定到指定的地址上,这个内部就比较简单不再详细说了
- 调用pipeline中处理器们的channelActive方法
到这里可能大家还有一个比较疑惑的点:OP_ACCEPT 被设置到哪里去了?
接下来看HeadContext的channelActive方法:
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
readIfIsAutoRead();
}
// 后面会到这里 io.netty.channel.DefaultChannelPipeline.HeadContext#read
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
// 然后最后到这里 io.netty.channel.nio.AbstractNioChannel#doBeginRead
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
上面的最后一行代码,selectionKey的值被重新设置。
至此,在nio层面的ServerSocketChannel就被初始化完成并绑定到了一个NioEventLoop的Selector上。
到这里,整个ServerBootstrap的初始化及启动流程也就介绍完了,后面我们将基于Reactor模型来一起探究NioEventLoop如何处理客户端请求。