netty源码--ServerBootstrap的初始化及启动流程

5 篇文章 0 订阅
4 篇文章 0 订阅

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中有几个关键的步骤:

  1. 执行initAndRegister方法获取异步对象ChannelFuture-regFuture
  2. 获取regFuture的通道channel(这里不难想到获取的肯定是实例化好的NioServerSocketChannel)
  3. 为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);
    }
}

这里主要分以下几个步骤:

  1. AbstractNioChannel调用父类AbstractChannel的构造方法,在AbstractChannel构造中将会初始化一个Pipeline(DefaultChannelPipeline)
    1. DefaultChannelPipeline初始化的时候会设置头尾两个处理器,分别是HeadContext和TailContext
  2. 然后调用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));
                }
            });
        }
    });
}

这里是为通道添加处理器,需要注意的是:

  1. ServerBootstrapAcceptor在这里也会被添加到pipeline中(它的用处我们后面在说),当然还有我们demo中传入的Logginghandler
  2. 此时使用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);
}

在该方法中主要分两步:

  1. 使用javanio的api将channel绑定到指定的地址上,这个内部就比较简单不再详细说了
  2. 调用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如何处理客户端请求。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值