[netty源码分析]--服务端启动的工作流程分析

服务端

1.首先是实例化boss线程池和worker线程池

实例化的就是 NioEventLoopGroup;这里我假设boss线程池初始化为1个线程,worker线程初始化为 2*CPU个数的线程数。

说一下主要做了什么工作:

(1)指定了线程池中线程数、线程池的执行器是ThreadPerTaskExecutor;

(2)线程池中每个线程其实就是一个NioEventLoop,线程池指定了每个NioEventLoop都需要的参数:

SelectorProvider, SelectStrategyFactory, RejectedExecutionHandlers.reject()

并将这三个参数封装成 args 参数传入NioEventLoop 的构造器中。

(3)核心的实例化过程其实在NioEventLoopGroup的父类MultithreadEventExecutorGroup中,下面的就是介绍MultithreadEventExecutorGroup构造器中主要做了的工作:

(4)实例化线程池的线程执行器executor,默认是ThreadPerTaskExecutor;

(5)对于线程池中的线程数组 children 进行初始化,初始化时传入了 executor 和 args;初始化的函数核心就是new NioEventLoop对象,如下:

new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);

从上面可知,线程池中所有的线程公用executor, SelectorProvider, SelectStrategyFactory以及RejectedExecutionHandler。


这里需要说明的一点是,线程池中所有的线程都公用的SelectorProvider, 而SelectorProvider是通过NioEventLoopGroup里面的构造器里面的
SelectorProvider.provider(); 方式获取的, 而这个方法返回的是一个单例的SelectorProvider, 所以所有的NioEventLoop公用同一个单例SelectorProvider。

这个地方从一定的侧面能够说明一些问题,也就是说,对于boss线程池,如果在单机情况下,即时配置了多个线程,但是多个线程公用的是一个SelectorProvider。

(6)下面说明NioEventLoop 线程初始化时候做的核心工作:

  • 通过 SelectorProvider 给线程构造一个selector;
  • 给每个 NioEventLoop线程构造一个 任务队列taskQueue,默认是一个无界队列。
  • 指定当前NioEventLoop 所属于的 NioEventLoopGroup

注意,自此还没有启动服务端的线程模型,仅仅只是初始化了线程池。

2.配置服务端ServerBootstrap

实例化ServerBootstrap时候并没有做什么工作,主要是通过ServerBootstrap这个引导类来配置netty的服务端。下面以netty的官方示例 EchoServer为例,来解释一下:

try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)
     .channel(NioServerSocketChannel.class)
     .option(ChannelOption.SO_BACKLOG, 100)
     .handler(new LoggingHandler(LogLevel.INFO))
     .childHandler(new ChannelInitializer<SocketChannel>() {
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             p.addLast(new EchoServerHandler());
         }
     });

    //调用sync()方法会一直阻塞等待channel的停止
    ChannelFuture f = b.bind(PORT).sync();

    // Wait until the server socket is closed
    f.channel().closeFuture().sync();
} finally {
    // Shut down all event loops to terminate all threads.
    bossGroup.shutdownGracefully();
    workerGroup.shutdownGracefully();
}

(1)调用group(bossGroup, workerGroup)设置服务端的线程模型:
这里将boss线程池是设置给了 AbstractBootstrap 类里面的group属性;
将worker线程池设置给了 ServerBootstrap 类里面的childGroup属性,表示子线程池,也就是IO和业务处理线程池。

(2)调用channel(NioServerSocketChannel.class)方法:
这个方法里面主要实例化了 AbstractBootstrap里面的类属性channelFactory,也就是创建Channel的工厂,默认是ReflectiveChannelFactory。

(3)调用了option(ChannelOption.SO_BACKLOG, 100),设置AbstractBootstrap类里面的 option 属性。

(4)调用handler(new LoggingHandler(LogLevel.INFO)) 设置AbstractBootstrap类里面的 handler属性

(5)调用childHandler(childHandler) 设置了ServerBootstrap 类里面的 childrenHandler 属性。

(6)服务端调用b.bind(PORT) 来绑定本地的端口,这一步就是最重要的步骤了,下面仔细说说这个函数主要做了哪些工作:

(1)调用 AbstractBootstrap.bind(int inetPort) –>

(2)调用 doBind(localAddress);这个类首先初始化服务端的通道,然后做更底层的绑定操作。下面解释一下doBind()函数做了什么:

(3)调用initAndRegister()函数,主要是功能如下:实例化服务端的NioServerSocketChannel 并对Channel进行初始化; 将channel 注册到boss线程池的 NIOEventLoopGroup中。

NioServerSocketChannel实例化过程

创建NioServerSocketChannel是通过channelFactory.newChannel() 实现的,通过调用 NioServerSocketChannel 的无参构造器。 注意这里就与 Java的NIO联系起来了, 在 NioServerSocketChannel 构造器中,通过NIO的SelectorProvider创建了java.nio.channels.ServerSocketChannel;

这里先给出NioServerSocketChannel 的主要继承关系:
NioServerSocketChannel –》
AbstractNioMessageChannel –》
AbstractNioChannel –》
AbstractChannel

在NioServerSocketChannel 类中主要是利用 SelectorProvider 来创建了创建了一个 ServerSocketChannel 并作为参数调用父类构造器:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

上面也设置了 当前通道感兴趣的事件是OP_ACCEPT 接收连接的事件;

在AbstractNioMessageChannel类构造其中,直接调用父类构造器

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

在 AbstractNioChannel 类构造器中,主要是将ServerSocketChannel赋值给AbstractNioChannel属性,设置ServerSocketChannel为非阻塞;

在AbstractChannel类构造器中就做了相当重要的工作,这里必须贴出源码:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

其中也就是一个非常重要的属性pipeline,对于pipeline的初始化其实没有太对内容,就是构建了一个由AbstractChannelHandlerContext 为结点的双向链表,来容纳ChannelHandler,这里先说一句,AbstractChannelHandlerContext与 channelHandler也是一一对应的关系。所以pipeline可以理解成Channelhandler的容器。

pipeline = newChannelPipeline();

这里也就说明了,每一个Channel与一个 Channelpipeline 是一一对应,也就是一一绑定的。


NioServerSocketChannel初始化

初始化过程其实也就是调用 ServerBootstrap.inti(channel),主要做了以下工作:
- 绑定TCP可选参数;
- 绑定附加参数;
- 对于worker线程池: 为Channel绑定的pipeline容器添加 各种handler

值得注意的是,在init()里面第一次调用了 NioEventLoop的execute(Runnable())方法,在研究NioEventLoop的源码的时候我们就知道了,第一次调用NioEventLoop的execute()方法的时候,会启动NioEventLoop线程。

将NioServerSocketChannel绑定到boss线程池中

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

其实就是调用SingleThreadEventLoop.register(final ChannelPromise promise)

调用:

promise.channel().unsafe().register(this, promise);

这里实际上就是调用 AbstractChannel.AbstractUnsafe.register()方法了,将这个channel注册到 一个 NioEventLoop 中,也可以理解成一个 Channel与一个NioEventLoop的绑定。

底层就是继续将调用 pipeline的fireChannelRegistered();方法, 这里也就是进入了pipeline 容器里面了,前面我们就说过pipeline里面有一个ChannelHandler双链表,这时就是从head指针开始在handler中执行了。

到这里doBind()函数里面的 initAndRegister()函数已经分析完毕了。

接下来就是等待注册完毕,当注册完毕之后,就会调用:
doBind0(regFuture, channel, localAddress, promise);

在doBind0()中将由channel所属于的NIoEventLoop来执行一个task,不过doBind0()将会在channelRegistered() 方法之前被调用,主要是为用户handler提供在其channelRegistered()实现中设置管道的机会。在doBind0()中主要是为channel绑定一个Channel关闭失败监听器。

在bind()函数的最后就是同步等待关闭通道了。

©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页