乐趣区

关于java: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<SocketChannel>() {        // 设置 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 线程的执行流程图

能够看到,main 办法次要做了两件事件:

  1. initAndRegister:初始化 NioServerSocketChannel,并注册到 bossGroup 的 NioEventLoop 上(因为这里 bossGroup 只有一个 EventLoop,所以就是固定的那一个),并返回 ChannelFuture(因为是异步注册)
  2. addListener:给异步注册返回的 ChannelFuture 对象 增加监听器 ,在注册胜利之后,会告诉该监听器,该监听器外部会执行 doBind0 办法来 提交【绑定端口】工作给该 NioEventLoop

当初来看一下它们的具体步骤

1)initAndRegister

能够看到,其外部次要有 3 个步骤:

  1. 通过 ReflectiveChannelFactory 类的 newChannel()办法来 实例化 NioServerSocketChannel(对 ServerSocketChannel 的封装)
  2. 调用 init 办法来初始化 NioServerSocketChannel,设置它的 options 和 attributes,并且调用 ChannelPipeline 类的 addLast 办法,将 ChannelInitializer 增加到它的 ChannelPipeline 流水线
  3. 提交工作【NioServerSocketChannel 注册】给 bossGroup 的 NioEventLoop 上

2)addListener

上一步 initAndRegister 执行之后会返回一个名为 regFuture 的 ChannelFuture 对象,如果该 regFuture 已实现,间接执行 doBind0 办法,否则增加监听器,当 regFuture 实现时失去告诉,执行 doBind0(该办法外部是提交【绑定端口】工作给该 NioEventLoop),如下

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

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

在下面 main 线程的执行过程中,提交了一个【NioServerSocketChannel 注册】工作给 bossGroup 的 NioEventLoop,此时该 NioEventLoop 接管到了第一个工作,开始进行初始化工作,启动线程并绑定,开始执行 NioEventLoop 类的 run 办法,run 办法的步骤如下:

在之前的文章中说过,NioEventLoop 自身是死循环来一直解决接管到的工作 ,所以这里也是一样,开始执行 NioEventLoop 的 run 办法,其外部执行了 runAllTasks 办法来执行所有工作(留神: 在执行工作的过程中也能够提交工作给该 NioEventLoop

接下来就来看下 runAllTasks 办法的执行流程图

能够看到,它的次要内容是执行了 4 个工作,别离是

  1. 执行工作【NioServerSocketChannel 注册】
  2. 执行工作【ChannelPipeline 增加 ServerBootstrapAcceptor】
  3. 执行工作【绑定端口】
  4. 执行工作【激活 NioServerSocketChannel】

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

1)执行工作【NioServerSocketChannel 注册】

执行工作【NioServerSocketChannel 注册】,次要有 4 个步骤

  1. 调用 AbstractNioChannel 的 doRegister 办法,将该 Channel 外部java nio 的 ServerSocketChannel 注册到该 NioEventLoop 的 Selector
  2. 之前 main 线程的执行过程中将 ChannelInitializer 增加到 ChannelPipeline 中,这里调用该 ChannelInitializer 的 handlerAdded 办法,将配置的 LoggingHandler 增加到 ChannelPipeline 中,并在 ChannelPipeline 中移除本身 ,并 提交工作【ChannelPipeline 增加 ServerBootstrapAcceptor 入站处理器】
    所以以后的 ChannelPipeline 里的程序是这样的:HeadContext -> LoggingHandler -> TailContext
  3. 设置工作执行胜利,因为 main 线程中对 regFuture 增加了监听器,所以这里会告诉到监听器,监听器外部 提交工作【绑定端口】
  4. 最初一步,调用以后 ChannelPipeline 的 fireChannelRegistered 办法,从 ChannelPipeline 的 head 开始遍历,顺次往后找重写了 channelRegistered 办法的 ChannelHandler,如果在 channelRegistered 办法里调用了 ctx.fireChannelRegistered(),则持续往后遍历

到这里,第一个工作【NioServerSocketChannel 注册】就执行实现了。

2)执行工作【ChannelPipeline 增加 ServerBootstrapAcceptor】

第二个工作是 往 ChannelPipeline 增加 ServerBootstrapAcceptor 处理器 ,该处理器作用是 接管新连贯并注册到 workerGroup 的 NioEventLoop 上,前面会应用到。

3)执行工作【绑定端口】

第三个工作是绑定端口,会先从 ChannelPipeline 的 tail 开始遍历,顺次往前查找重写了 bind 办法的 ChannelHandler,如果在 bind 办法里调用 ctx.bind(),则持续往前遍历。在最初调用了 HeadContext 的 bind 办法,执行了 ServerSocketChannel.bind()来绑定端口 ,并且 提交工作【激活 NioServerSocketChannel】

4)执行工作【激活 NioServerSocketChannel】

激活 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 的监听事件

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

三、总结

至此,服务端的启动流程就告一段落,后续会持续解说该 bossGroup 的 NioEventLoop 会 如何解决接管到的连贯 ,以及这些新的连贯是 如何注册到 workerGroup 的 NioEventLoop 上 的。

退出移动版