【Netty】六、服务端启动流程 - 源码解读

一、前言

在实际开发过程中,通过Netty提供的高度封装的Api,我们可以很容易地构建出自己的服务端程序,如下例

public static void main(String[] args) throws Exception {

        // 实例化bossGroup和workerGroup
        // bossGroup传入参数1,表示只包含一个EventLoop
        // workerGroup使用无参构造函数,默认实例化 (CPU核数 * 2)个EventLoop
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)                         // 指定Channel的类型,该例子是服务端,所以使用NioServerSocketChannel
             .option(ChannelOption.SO_BACKLOG, 100)                         // 设置NioServerSocketChannel的option选项
             .handler(new LoggingHandler(LogLevel.INFO))                    // 设置NioServerSocketChannel对应的ChannelPipeline流水线上的ChannelHandler
             .childHandler(new ChannelInitializer() {        // 设置NioSocketChannel对应的ChannelPipeline流水线上的ChannelHandler
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new EchoServerHandler());    // 添加EchoServerHandler处理器
                 }
             });

            // 启动服务器
            ChannelFuture f = b.bind(PORT).sync();

            // 阻塞直到NioServerSocketChannel关闭
            f.channel().closeFuture().sync();
        } finally {
            // 关闭所有EventLoop来终止所有线程
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
  • 这里的bossGroup只包含一个NioEventLoop,该NioEventLoop主要用于处理ACCEPT事件
  • workerGroup默认实例化 (CPU核数 * 2) 个NioEventLoop

上面这是个源码中的简单例子EchoServer,作用是接收客户端发送过来的消息,并写回客户端。借这个例子,来对服务端的启动流程进行解读

二、服务端的启动流程

服务端的启动主要与两个线程有关,分别是main线程bossGroup的NioEventLoop对应的线程,这里面假定该线程名为NioEventLoopGroup2-1。下面来看下这两个线程的执行流程。

2.1 main线程的执行流程

首先我们看到main方法中是通过ServerBootstrap的bind()方法来启动服务器的,所以这里就是入口。下面是main线程的执行流程图
【Netty】六、服务端启动流程 - 源码解读_第1张图片

可以看到,main方法主要做了两件事情:

  1. initAndRegister:初始化NioServerSocketChannel,并注册到bossGroup的NioEventLoop上(由于这里bossGroup只有一个EventLoop,所以就是固定的那一个),并返回ChannelFuture(因为是异步注册)
  2. addListener:给异步注册返回的ChannelFuture对象添加监听器,在注册成功之后,会通知该监听器,该监听器内部会执行doBind0方法来提交【绑定端口】任务给该NioEventLoop

现在来看一下它们的具体步骤

1)initAndRegister

【Netty】六、服务端启动流程 - 源码解读_第2张图片

可以看到,其内部主要有3个步骤:

  1. 通过ReflectiveChannelFactory类的newChannel()方法来实例化NioServerSocketChannel(对ServerSocketChannel的封装)
    【Netty】六、服务端启动流程 - 源码解读_第3张图片
  2. 调用init方法来初始化NioServerSocketChannel,设置它的options和attributes,并且调用ChannelPipeline类的addLast方法,将ChannelInitializer添加到它的ChannelPipeline流水线
    【Netty】六、服务端启动流程 - 源码解读_第4张图片
  3. 提交任务【NioServerSocketChannel注册】给bossGroup的NioEventLoop上
    【Netty】六、服务端启动流程 - 源码解读_第5张图片

2)addListener

上一步initAndRegister执行之后会返回一个名为regFuture的ChannelFuture对象,如果该regFuture已完成,直接执行doBind0方法,否则添加监听器,当regFuture完成时得到通知,执行doBind0(该方法内部是提交【绑定端口】任务给该NioEventLoop),如下
【Netty】六、服务端启动流程 - 源码解读_第6张图片

到这里,main线程的启动流程就结束了,接下来来看下bossGroup的NioEventLoopGroup2-1线程做了哪些事情?

2.2 【bossGroup】NioEventLoopGroup2-1线程的执行流程

在上面main线程的执行过程中,提交了一个【NioServerSocketChannel注册】任务给bossGroup的NioEventLoop,此时该NioEventLoop接收到了第一个任务,开始进行初始化工作,启动线程并绑定,开始执行NioEventLoop类的run方法,run方法的步骤如下:
【Netty】六、服务端启动流程 - 源码解读_第7张图片

在之前的文章中说过,NioEventLoop本身是死循环来不断处理接收到的任务,所以这里也是一样,开始执行NioEventLoop的run方法,其内部执行了runAllTasks方法来执行所有任务(注意:在执行任务的过程中也可以提交任务给该NioEventLoop

接下来就来看下runAllTasks方法的执行流程图
【Netty】六、服务端启动流程 - 源码解读_第8张图片

可以看到,它的主要内容是执行了4个任务,分别是

  1. 执行任务【NioServerSocketChannel注册】
  2. 执行任务【ChannelPipeline添加ServerBootstrapAcceptor】
  3. 执行任务【绑定端口】
  4. 执行任务【激活NioServerSocketChannel】

那大家可能会有疑问,为什么main线程里明明只提交了一个任务,这里却要执行4个任务?答案就是在任务执行的过程中,又提交了新的任务给该NioEventLoop。先来看下执行第一个任务

1)执行任务【NioServerSocketChannel注册】

【Netty】六、服务端启动流程 - 源码解读_第9张图片

执行任务【NioServerSocketChannel注册】,主要有4个步骤

  1. 调用AbstractNioChannel的doRegister方法,将该Channel内部java nio的ServerSocketChannel注册到该NioEventLoop的Selector
    【Netty】六、服务端启动流程 - 源码解读_第10张图片
  2. 之前main线程的执行过程中将ChannelInitializer添加到ChannelPipeline中,这里调用该ChannelInitializer的handlerAdded方法,将配置的LoggingHandler添加到ChannelPipeline中,并在ChannelPipeline中移除自身,并提交任务【ChannelPipeline添加ServerBootstrapAcceptor入站处理器】
    所以当前的ChannelPipeline里的顺序是这样的:HeadContext -> LoggingHandler -> TailContext
    【Netty】六、服务端启动流程 - 源码解读_第11张图片
  3. 设置任务执行成功,因为main线程中对regFuture添加了监听器,所以这里会通知到监听器,监听器内部提交任务【绑定端口】
  4. 最后一步,调用当前ChannelPipeline的fireChannelRegistered方法,从ChannelPipeline的head开始遍历,依次往后找重写了channelRegistered方法的ChannelHandler,如果在channelRegistered方法里调用了ctx.fireChannelRegistered(),则继续往后遍历

到这里,第一个任务【NioServerSocketChannel注册】就执行完成了。

2)执行任务【ChannelPipeline添加ServerBootstrapAcceptor】

clipboard.png

第二个任务是往ChannelPipeline添加ServerBootstrapAcceptor处理器,该处理器作用是接收新连接并注册到workerGroup的NioEventLoop上,后面会使用到。

3)执行任务【绑定端口】

【Netty】六、服务端启动流程 - 源码解读_第12张图片

第三个任务是绑定端口,会先从ChannelPipeline的tail开始遍历,依次往前查找重写了bind方法的ChannelHandler,如果在bind方法里调用ctx.bind(),则继续往前遍历。在最后调用了HeadContext的bind方法,执行了ServerSocketChannel.bind()来绑定端口,并且提交任务【激活NioServerSocketChannel】
【Netty】六、服务端启动流程 - 源码解读_第13张图片

4)执行任务【激活NioServerSocketChannel】

【Netty】六、服务端启动流程 - 源码解读_第14张图片

激活NioServerSocketChannel的最终目的就是设置SelectionKey的监听事件,NioServerSocketChannel是ACCEPT事件。具体步骤是

  1. 最先调用ChannelPipeline的fireChannelActive方法,从ChannelPipeline的head开始遍历,依次往后调用ChannelHandler的channelActive方法
  2. 而在HeadContext的channelActive方法中,执行了readIfIsAutoRead方法,又调用了ChannelPipeline的read方法,从tail开始遍历,依次往前调用ChannelHandler的read方法
  3. 在HeadContext的read方法里,底层通过SelectionKey.interestOps来设置NioServerSocketChannel的监听事件
    【Netty】六、服务端启动流程 - 源码解读_第15张图片

runAllTasks方法执行完之后,NioEventLoop的run方法回到循环体头部,计算策略之后通过调用Selector的select()方法来阻塞当前线程当有任务被添加时,会通过Selector的wakeUp方法来主动唤醒该Selector

三、总结

至此,服务端的启动流程就告一段落,后续会继续讲解该bossGroup的NioEventLoop会如何处理接收到的连接,以及这些新的连接是如何注册到workerGroup的NioEventLoop上的。

你可能感兴趣的:(javanetty)