Netty源码初探---服务端启动源码简析

为了加深对netty的理解,主要对netty的主要源码进行了debug,分析,有些细节没去深究,主要目的是通过源码流程加深理解。

一、Netty模型简介

netty的reactor模型如图1.1所示:

Netty源码初探---服务端启动源码简析_第1张图片

图1.1 netty reactor模型

对图1.1进行一个简要说明:

1)  BossGroup 负责接收客户端的连接, WorkerGroup 负责网络的读写,两者的类型均为 NioEventLoopGroup

2) NioEventLoopGroup 相当于一个事件循环组, 包含多个NioEventLoop事件循环

3) NioEventLoop 表示一个不断循环的执行处理任务的线程, 每个 NioEventLoop 都有一个selector , 用于监听绑定在其上的socket的网络通讯

4) NioEventLoopGroup 可以有多个线程, 即可以含有多个NioEventLoop

5) Boss NioEventLoop 循环执行的步骤分为3步

        i.轮询accept 事件

        ii. 处理accept 事件 , 与client建立连接 , 生成NioScocketChannel , 并将其注册到某个worker NIOEventLoop上的selector

        iii.处理任务队列的任务 ,即 runAllTasks

6) 每个 Worker NIOEventLoop 循环执行的步骤

        i. 轮询读写事件

        ii. 在对应NioScocketChannel 处理读写事件

        iii. 处理任务队列的任务 , 即 runAllTasks

7) 每个Worker NIOEventLoop 处理业务时,会使用pipeline(管道), pipeline中含有多个context,每个context中含有channelHandler,通过责任链的方式处理出站、入站(后续分析pipeline源码的时候再细讲这部分)

二 启动源码分析

demo示例:

Netty源码初探---服务端启动源码简析_第2张图片

 图2.1 demo示例

 bossGroup和workerGroup都是NioEventLoopGroup(),debug追到new NioEventLoopGroup()中,先追到线程数的创建,如图2.2所示:

Netty源码初探---服务端启动源码简析_第3张图片

 图2.2

   在demo中,new NioEventLoopGroup()中可以指定线程数量,如果不指定会通过

DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

设置线程数量,默认为cpu核心数的双倍。继续往下追代码,如图2.3

Netty源码初探---服务端启动源码简析_第4张图片

 图 2.3

    从图2.3可知,new了一个 EventExecutor数组,这个数组的大小就是创建的线程数量的大小,我的cpu为16核,因此这里children为32。接下来会通过for循环通过newChild(executor, args)给children每个元素赋值,图中可知newChild(executor, args)得到的就是NioEventLoop。

注:newChild(executor, args)没有跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析

     接下来简要说下finally中的事情,如图2.4所示

Netty源码初探---服务端启动源码简析_第5张图片

 图 2.4

     如果给children创建NioEventLoop失败,会关闭终止NioEventLoop。如果children创建成功,会为每个NioEventLoop添加监听器,并把children加入到childrenSet集合中,如图2.5

Netty源码初探---服务端启动源码简析_第6张图片

 图2.5

 注:如何关闭的线程,创建的监听没有跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析

    接下来回到demo中的ServerBootstrap b = new ServerBootstrap()跟进分析,如图2.6

Netty源码初探---服务端启动源码简析_第7张图片

 图 2.6

     将bossGroup(parentGroup)赋值给group属性,将workerGroup(childGroup)赋值给childGroup属性。

注:后续链式添加channel,option,handler等这里不跟进分析,有兴趣的读者可以自行debug,这里只对服务端启动流程进行总体的源码分析。

     ServerBootstrap创建好后,接下来跟进分析b.bind,这是服务器启动的核心代码。

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

跟进到bind中,首先是进行了基本的启动校验,然后调用doBind()方法。

Netty源码初探---服务端启动源码简析_第8张图片

 图 2.7

     进入到doBind方法,去掉一些细枝末节,这里最核心的两个方法是initAndRegister()和doBind0()。首先分析initAndRegister()

Netty源码初探---服务端启动源码简析_第9张图片

 图 2.8

 initAndRegister()

Netty源码初探---服务端启动源码简析_第10张图片

 图 2.9

    initAndRegister()主要做了三件事情:通过工厂方法new了一个channel;初始化channel;将channel进行注册。

什么是channel:

官方描述为:A nexus to a network socket or a component which is capable of I/O * operations such as read, write, connect, and bind.即能够进行读,写,连接,绑定等IO操作的套接字或组件

     这个channelFactory是啥,debug可以发现,这个channel是一个ReflectiveChannelFactory,并且给这个ReflectiveChannelFactory传入了一个NioServerSocketChannel.class。这个就是demo中调用.channel传入的。

Netty源码初探---服务端启动源码简析_第11张图片

 图2.10

     跟进到channel = channelFactory.newChannel(),通过反射将我们传入的NioServerSocketChannel.class创建出来。

Netty源码初探---服务端启动源码简析_第12张图片

 图2.11

     我们创建bossGroup的时候,都会给bossGroup添加一个NioServerSocketChannel,有必要深入跟进这个NioServerSocketChannel是个啥。在NioServerSocketChannel打断点,层层跟进,如下所示:

 /**
     * Create a new instance
 */
  public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
/**
     * Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

Netty源码初探---服务端启动源码简析_第13张图片

 图2.12

     最终会进到AbstractNioChannel方法中,将SelectableChannel设置为非阻塞。注意这个readInteresOp方法,该方法是调用super(null, channel, SelectionKey.OP_ACCEPT);传入的,可知,从SelectionKey.OP_ACCEPT可知NioServerSocketChannel负责监听的事件就是accept连接事件,这就是为什么bossGroup只负责处理连接的原因。

    分析下AbstractNioChannel调用的父类方法,进入父类方法,设置了三个重要的属性,分别为ChannelId,Unsafe,DefaultChannelPipeline。

Netty源码初探---服务端启动源码简析_第14张图片 839e42b7895343608e47f2d64f0d23b9.png

 图2.13

     这里不对这三个属性进行深入的分析,仅简要说明其含义。

官方解释:

ChannelId:Represents the globally unique identifier of a {@link Channel}。channel全局唯一id

Unsafe:Unsafe operations that should never be called from user-code,These methods  are only provided to implement the actual transport,and must be invoked from an I/O thread:不能由用户调用的不安全操作。这些操作只能实现实际的传输并且必须由IO线程调用。

DefaultChannelPipeline implements ChannelPipeline:A list of {@link ChannelHandler}s which handles or intercepts inbound events and outbound operations of a * {@link Channel}:进行出站,入站事件操作用的

     这里稍微说下DefaultChannelPipeline,为后续讲解pipeline做准备。

Netty源码初探---服务端启动源码简析_第15张图片

 图 2.14

     每一个pipeline都会默认初始化一个头结点和尾节点,且头尾节点是不会变化的。并且头结点实现了ChannelOutboundHandler和ChannelInboundHandler,尾节点只实现了ChannelInboundHandler。

/ A special catch-all handler that handles both bytes and messages.
    final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler

final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler

总结下:创建channel主要就是通过反射的方式new一个NioServerSocketChannel对象,在new的过程找那个会创建一系列组件,且NioServerSocketChannel只负责监听accpet的事件。

init (channel):

接下来分析init(channel),这个方法比较长,先看前半段,如图:

Netty源码初探---服务端启动源码简析_第16张图片

    图 2.15

首先是赋值了options属性和attrs属性,由于我们是讨论服务器是如何启动的,这里不对这两个属性进行进一步说明。

    Netty源码初探---服务端启动源码简析_第17张图片

 图 2.16

     这里主要就是给DefaultChannelPipeline中加入new ServerBootstrapAcceptor接入器,这个接入器接受新的请求,新的请求通过事件循环器来处理。这里不深入跟进。

 ChannelFuture regFuture = config().group().register(channel):

跟进代码:

1.
@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

2.
@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
3.
@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

    Netty源码初探---服务端启动源码简析_第18张图片

 

 图 2.17

     最终通过一个NioMessageUnsafe的register方法将EventLoop事件循环器绑定到NioServerSocketChannel上,然后调用register0()方法,该方法中会进行pipeline中handler链的调用,这里不展开说了,后面单独将pipeline的时候再说。

Netty源码初探---服务端启动源码简析_第19张图片

 图 2.18

doBind0() :

    initAndRegister()执行完毕后,后续就要执行到最关键的doBind0()方法:

Netty源码初探---服务端启动源码简析_第20张图片

 图 2.19

     跟进代码,发现通过eventLoop起来一个线程执行绑定,如果成功就执行channel.bind,如果失败就给promise中设置错误信息(读者可自行了解promise编程,这里不展开说)。

Netty源码初探---服务端启动源码简析_第21张图片

 图 2.20

      跟进到channel.bind中:、

1160245039fe48b3a35ced46682d4179.png

 这里我们只关注bind, 看看到底是哪里绑定端口的。一步一步debug。如果

1.

bc16ded4c81b4230aed06568f47ea499.png

 2.

fade22e118f04b13b8259354ef8668e1.png

 3.

Netty源码初探---服务端启动源码简析_第22张图片

4.

Netty源码初探---服务端启动源码简析_第23张图片

 5.

Netty源码初探---服务端启动源码简析_第24张图片

6.

 Netty源码初探---服务端启动源码简析_第25张图片

 7.

Netty源码初探---服务端启动源码简析_第26张图片

 8.

Netty源码初探---服务端启动源码简析_第27张图片

 9.

Netty源码初探---服务端启动源码简析_第28张图片

10.

Netty源码初探---服务端启动源码简析_第29张图片

 图 2.21

 

    bind的调用比较深,不去展开分析。调到最后发现最终调到了java nio的方法,通过nio调用bind对端口进行了最终的绑定。端口绑定完后,最终会执行到safeSetSuccess(promise)告知promise已经成功创建。可以开始循环监听了

Netty源码初探---服务端启动源码简析_第30张图片

 图 2.22

    最终会一直在NioEventLoop中进行循环的监听:

Netty源码初探---服务端启动源码简析_第31张图片

 图 2.23

    至此,Netty服务端启动的代码分析到此结束了,源码中只对整体流程进行跟进,有些细节没有深入了解,有兴趣的读者可以执行debug跟进。后续讲到netty的pipeline, reactor源码进行分析。

     Netty服务端启动总结:

    1.创建2个EventLoopGroup,一个是bossGroup, 一个是workerGroup。

    2.在bind方法中调用了initAndRegister和dobind两个重要方法

    3.initAndRegister通过反射来创建,NioServerSocketChannel,并创建了一些重要属性,如pipeline,unsafe等。

    4.在register0()方法成功调用后,在dobind方法中调用dobind0方法,最终会调用java nio中的bind方法去绑定端口,来完成服务端的启动。

    5.最后NioEventLoop会一直循环监听事件。

 

你可能感兴趣的:(java,开发语言,后端,netty)