Netty 源码解读-服务端启动过程分析

Netty 源码解读-服务端启动过程分析_第1张图片

简述

在使用Netty进行服务端程序开发时,主要涉及端口监听、EventLoop线程池创建、NioServerSocketChannel和 ClannelPipeline初始化等。

netty是一款基于NIO的高性能、异步事件驱动的网络程序框架,它对JDK中的NIO做了封装和优化,提高了性能的同时降低了使用的难度。

作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的。
Netty是对NIO的封装,通过ChannelPipeline责任链实现,将InBound和outBound执行过程。

Executor

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。

netty服务启动过程

Netty的启动流程中,涉及到多个操作,比如register、bind、处理对应事件等,为了不影响main线程执行,这些工作以task的形式提交给NioEventLoop,由NioEventLoop来执行这些task。

  1. 创建ServerBootstrap实例,设置启动参数group(boss、worker)、option、设置channel通道类型NioServerSocketChannel、绑定端口、启动服务。
  2. 设置channel通道类型:指定使用NioServerSocketChannel来建立请求连接。
  3. 创建server对应的channel,创建各大组件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等。
  4. 构造一系列channelHandler处理链来组成ChannelPipeline。ChannelPipeline控制着ChannelHandler的流转。
  5. option用来配置一些channel的参数,配置的参数会被ChannelConfig使用。
  6. 编写的TestServerHandler可以继承ChannelInboundHandlerAdapter,复写了channelRead()和exceptionCaught()方法。
  7. 初始化channel,设置attr及options等,给channel添加接入器,触发register事件,调用handler方法中绑定的channelhandler的channelActive、channelAdd、channelRegister等方法。
  8. 绑定监听端口并启动服务端,select()轮询。I/O读写等网络事件通知。自定义ChannelHandler的调度和执行。
    (调用到jdk底层做端口绑定,并触发active事件,active触发的时候,真正做服务端口绑定)

EventLoopGroup 创建 NioEventLoopGroup

EventLoopGroup是基于JDK的线程池进行封装的实现,它包含一组 EventLoop,Channel 通过注册到 EventLoop的Selector 中执行操作。

EventLoopGroup创建NioEventLoopGroup就是创建了2个线程池boss和worker。boss负责接收连接请求;worker负责处理具体的IO操作等,初始线程池大小默认是CPU核数* 2。

SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个priavate static volatile Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。

NioEventLoopGroup
NioEventLoopGroup 是一个基于Reactor模型的线程池循环处理器,是一个多线程事件驱动IO操作类

NioEventLoopGroup 初始化的基本过程:

  1. EventLoopGroup(其实是MultithreadEventExecutorGroup)内部维护一个类为EventExecutor[] children 数组,其大小是nThreads;
  2. 在MultithreadEventExecutorGroup 中会调用newChild()抽象方法来初始化children 数组;
  3. 在NioEventLoopGroup 中具体实现newChild()方法,该方法返回一个NioEventLoop 实例。
  4. 初始化NioEventLoop 主要属性:
    4.1 provider:在NioEventLoopGroup 构造器中通过SelectorProvider 的provider()方法获取SelectorProvider。
    4.2 selector:在NioEventLoop 构造器中调用selector = provider.openSelector()方法获取Selector 对象。

NioEventLoop
NioEventLoop 的继承关系:
NioEventLoop->SingleThreadEventLoop->SingleThreadEventExecutor->AbstractScheduledEventExecutor->实现 EventLoop 接口。

EventLoop 任务执行者
NioEventLoop(准确来说是SingleThreadEventExecutor)中包含了private volatile Thread thread,该thread变量的初始化是在new的线程第一次执行run方式时才赋值的,这种形式挺新颖的。

SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个priavate volatile Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。

在AbstractScheduledEventExecutor 中, Netty 实现了NioEventLoop 的schedule 功能,即我们可以通过调用一个NioEventLoop 实例的schedule 方法来运行一些定时任务。而在SingleThreadEventLoop 中,也实现了任务队列的功能,通过它,我们可以调用一个NioEventLoop 实例的execute()方法来向任务队列中添加一个task,并由NioEventLoop进行调度执行。

一个EventLoop对应一个线程,其内部包含一个FIFO的taskQueue和Selector,负责处理客户端请求和内部任务,内部任务如ServerSocketChannel注册和ServerSocket绑定操作等。

NioEventLoop 创建时就会初始化一个Reactor,包括selector和taskQueue。
它负责两件事:
第一个作为IO 线程,执行与Channel 相关的IO 操作,包括调用Selector 等待就绪的IO 事件、读写数据与数据的处理等;
第二个作为任务队列,执行taskQueue 中的任务,例如用户调用eventLoop.schedule 提交的定时任务也是这个线程执行的。

NioEventLoop 具有执行 IO 和业务操作的能力,通常情况为了避免 IO 和业务处理互相影响,添加 handler 会指定线程池。
//

NioEventLoop 的实例化过程
SingleThreadEventExecutor 启动时会调用doStartThread()方法,然后调用executor.execute()方法,将当前线程赋值给thread。在这个线程中所做的主要就是调用SingleThreadEventExecutor.this.run()方法,而因为NioEventLoop 实现了这个方法,其实调用的是NioEventLoop.run()方法。

Selector
Selector是一个多路复用器,它负责管理被注册到其上的SelectableChannel。Selector的实现根据操作系统的不同而不同,目前多路复用IO实现主要包括四种:select、poll、epoll、kqueue。
SelectorProvider不同操作系统的I/O多路复用选择器各自内核实现不同,目前有select、poll、epoll、kqueue四种实现,JDK里与之对应的实现也随着操作系统的不同而不同。

总结一句话
当EventLoop 的execute()第一次被调用时,就会触发startThread()方法的调用,进而导致EventLoop所对应的Java 本地线程启动。

EventLoopGroup创建完成后,启动的第一步就算完成了,接下来该进行bind、listen操作了。

ServerBootstrap

ServerBootstrap的bind流程涉及到NioChannel的创建、初始化和注册(到Selector),启动NioEventLoop,之后就可以对外提供服务了。
这里涉及到2个操作,一个是channel的创建、初始化、注册;另一个是bind操作。

channel 创建、初始化、注册、绑定操作
初始化channel,将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。

初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer,注意,ChannelInitializer是在channel注册到selector之后被回调的。

channel初始化之后就该将其注册到selector,即下面的register流程:
bind操作是在register之后进行的,因为register0()方法是由NioEventLoop执行的,所以main线程需要先判断下future是否完成,如果完成直接进行doBind即可,否则添加listener回调进行doBind。

bind操作及后续初始化操作(channelActive回调、设置监听事件)

ChannelPipeline、ChannelHandler、ChannelHandlerContext 三者关系
一个ChannelPipeline中可以有多个ChannelHandler实例,而每一个ChannelHandler实例与ChannelPipeline之间的桥梁就是ChannelHandlerContext实例。
ChannelHandlerContext里就包含着ChannelHandler中的上下文信息,每一个ChannelHandler被添加都ChannelPipeline中都会创建一个与其对应的ChannelHandlerContext。ChannelHandlerContext的功能就是用来管理它所关联的ChannelHandler和在同一个ChannelPipeline中ChannelHandler的交互。

initAndRegister() 初始化和注册
首先Channel创建、初始化、注册,由于注册是一个异步的过程,所以initAndRegister()方法返回的是一个ChannelFuture。

在initAndRegister()方法中先通过反射创建了一个NioServerSocketChannel实例,接着调用init()方法初始化该NioServerSocketChannel对象。

将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。

初始化channel流程
初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer。
将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。
注意,ChannelInitializer是在channel注册到Selector之后被回调的。

init()方法

init()方法最后一段代码,它注册了一个ChannelInitializer到Channelpipline上,该ChannelInitializer的initChannel()方法创建了一个ServerBootstrapAcceptor注册到channel的ChannelPipeline中,设置对外监听。

注册
将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。
注册完成后, netty 会触发一个invokeHandlerAddedIfNeeded()方法, 从而调用fireHandlerAdded()将首次调用 ChannelInitializer.initChannel(), 将 ServerBootstrapAcceptor 添加到pipeline进来。
绑定
doBind方法是真正执行绑定操作的方法,是调用java的的ServerSocket#bind()方法。
到这里为止整个netty启动流程就基本接近尾声,可以对外提供服务了。

Netty工作流程图

Netty的启动流程中,涉及到多个操作,比如register、bind、注册对应事件等,为了不影响main线程执行,这些工作以task的形式提交给NioEventLoop,由NioEventLoop来执行这些task,也就是register、bind、注册事件等操作。
Netty启动流程图如下所示:
Netty 源码解读-服务端启动过程分析_第2张图片

Netty启动过程分析

Netty的启动流程,就是创建NioEventLoopGroup和实例化ServerBootstrap实例,并进行bind的过程。

    public static void main(String[] args)
    {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap server = new ServerBootstrap();//启动类
            server.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {
                            sc.pipeline().addLast(new TestServerHandler());
                        }
                    });
            ChannelFuture cf = server.bind(9090).sync();
            cf.channel().closeFuture().sync();
        }
        catch(Exception e) {
            e.printStackTrace();
        }finally {
            logger.info("server shutdown.");
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }}

一 EventLoopGroup

EventLoopGroup中会包含了多个EventLoop,EventLoop是一个Reactor模型的事件处理器。
一个EventLoop对应一个线程,其内部会维护一个taskQueue和Selector,负责处理客户端请求和内部任务,内部任务如ServerSocketChannel注册和ServerSocket绑定操作等。

EventLoopGroup 的类结构图

Netty 源码解读-服务端启动过程分析_第3张图片

NioEventLoopGroup

NioEventLoopGroup.class
Netty 源码解读-服务端启动过程分析_第4张图片
NioEventLoopGroup 初始化的时序图
Netty 源码解读-服务端启动过程分析_第5张图片
基本步骤如下:

  1. EventLoopGroup(其实是MultithreadEventExecutorGroup)内部维护一个类为EventExecutor children 数组,其大小是nThreads,这样就初始化了一个线程池。
  2. 如果我们在实例化NioEventLoopGroup 时,如果指定线程池大小,则nThreads 就是指定的值,否则是CPU核数* 2。
  3. 在MultithreadEventExecutorGroup 中会调用newChild()抽象方法来初始化children 数组.
  4. 抽象方法newChild()实际是在NioEventLoopGroup 中实现的,由它返回一个NioEventLoop 实例。
  5. 初始化NioEventLoop 主要属性:
    5.1 provider:在NioEventLoopGroup 构造器中通过SelectorProvider 的provider()方法获取SelectorProvider。
    5.2 selector:在NioEventLoop 构造器中调用selector = provider.openSelector()方法获取Selector 对象。

NioEventLoop

EventLoopGroup创建多个NioEventLoop,这里创建NioEventLoop就是初始化一个Reactor,包括创建taskQueue和初始化Selector。
Netty 源码解读-服务端启动过程分析_第6张图片
NioEventLoop架构图
Netty 源码解读-服务端启动过程分析_第7张图片
NioEventLoop 的类层次结构图还是有些复杂的,不过我们只需要关注几个重要点即可。
NioEventLoop 的继承关系:
NioEventLoop->SingleThreadEventLoop->SingleThreadEventExecutor->AbstractScheduledEventExecutor->实现EventLoop接口。

在AbstractScheduledEventExecutor 中, Netty 实现了NioEventLoop 的schedule 功能,即我们可以通过调用一个NioEventLoop 实例的schedule 方法来运行一些定时任务。而在SingleThreadEventLoop 中,又实现了任务队列的功能,通过它,我们可以调用一个NioEventLoop 实例的execute()方法来向任务队列中添加一个task,并由NioEventLoop进行调度执行。

NioEventLoop 负责两件事:

  1. 第一个是作为IO 线程,执行与Channel 相关的IO 操作,包括调用Selector 等待就绪的IO 事件、读写数据与数据的处理等;
  2. 第二是作为任务队列,执行taskQueue 中的任务,例如用户调用eventLoop.schedule 提交的定时任务也是这个线程执行的。

主要逻辑如下:

public final class NioEventLoop extends SingleThreadEventLoop {

    NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
                 SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
		// 创建taskQueue
		super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
		// 是不是很熟悉,java nio selector操作
		provider = selectorProvider;
		final SelectorTuple selectorTuple = openSelector();
		selector = selectorTuple.selector;
		unwrappedSelector = selectorTuple.unwrappedSelector;
		selectStrategy = strategy;
	}}

Selector SelectorProvider

Selector是一个多路复用器,它负责管理被注册到其上的SelectableChannel。Selector的实现根据操作系统的不同而不同,目前多路复用IO实现主要包括四种:select、poll、epoll、kqueue。
SelectorProvider不同操作系统的I/O多路复用选择器各自内核实现不同,目前有select、poll、epoll、kqueue四种实现,JDK里与之对应的实现也随着操作系统的不同而不同。

绑定NioEventLoop

Netty 源码解读-服务端启动过程分析_第8张图片

MultithreadEventExecutorGroup.class

Netty 源码解读-服务端启动过程分析_第9张图片

新连接接入通过chooser绑定一个NioEventLoop

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args){
	// 创建NioEventLoop实例
	this.children = new EventExecutor[nThreads];
	
	// 初始化NioEventLoop,实际调用的是NioEventLoopGroup.newChild方法
	for (int i = 0; i < nThreads; i ++) {
		children[i] = newChild(executor, args);
	}
	// 多个NioEventLoop中选择策略
	chooser = chooserFactory.newChooser(children);
}

chooser
Netty 源码解读-服务端启动过程分析_第10张图片

NioEventLoop 的实例化过程

SingleThreadEventExecutor 启动时会调用doStartThread()方法,然后调用executor.execute()方法,将当前线程赋值给thread。在这个线程中所做的主要就是调用SingleThreadEventExecutor.this.run()方法,而因为NioEventLoop 实现了这个方法,其实调用的是NioEventLoop.run()方法。

EventLoop 任务执行者

NioEventLoop 继承自SingleThreadEventLoop,而SingleThreadEventLoop 又继承自SingleThreadEventExecutor。而SingleThreadEventExecutor 是Netty 中对本地线程的抽象,它内部有一个Thread thread 属性,存储了一个本地Java线程。因此我们可以简单地认为,一个NioEventLoop 其实就是和一个特定的线程绑定,并且在其生命周期内,绑定的线程都不会再改变。

EventLoopGroup创建完成后,启动的第一步就算完成了,接下来该进行bind、listen操作了。

二 ServerBootstrap

server端启动流程可以理解成创建ServerBootstrap实例的过程

ServerBootstrap.class
Netty 源码解读-服务端启动过程分析_第11张图片

ServerBootstrap 和 BootStrap 均继承AbstractBootStrap,这里AbstractBootStrap成员变量group对应bossgroup,负责处理客户端认证,ServerBootStrap中的成员变量childGroup即为线程模型中的workGroup线程池,负责处理认证成功的IO事件。其他参数也对应着bossgroup和workgroup中相应设置。

类名 ServerBootStrap AbstractBootStrap
Channel连接配置 childOptions options
参数 childAttrs Attrs
线程组 childGroup group
业务处理 childHandler handler

设置通道类型

设置通道类型NioServerSocketChannel.class,通过反射

//AbstractBootstrap.class
    public B channel(Class<? extends C> channelClass) {
        return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory((Class)ObjectUtil.checkNotNull(channelClass, "channelClass"))));
    }


    public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
        return this.channelFactory((ChannelFactory)channelFactory);
    }
    
    @Deprecated
    public B channelFactory(ChannelFactory<? extends C> channelFactory) {
        ObjectUtil.checkNotNull(channelFactory, "channelFactory");
        if (this.channelFactory != null) {
            throw new IllegalStateException("channelFactory set already");
        } else {
            this.channelFactory = channelFactory;
            return this.self();
        }
    }

bind、doBbind

这里涉及到2个操作,一个是Channel的创建、初始化、注册操作,另一个是bind操作。
bind操作是ServerBootstrap流程重要的一环,这里是对NIO的封装,bind流程涉及到NioChannel的创建、初始化和注册(到Selector),启动NioEventLoop,之后就可以对外提供服务了。
Netty 源码解读-服务端启动过程分析_第12张图片
注意,这里如果main线程执行到regFuture.isDone()时,register还未完成,那么main线程是不会直接调用bind操作的,而是往regFuture上注册一个Listenner,这样channel register完成(注册到Selector并且调用了invokeHandlerAddedIfNeeded)之后,会调用safeSetSuccess,触发各个ChannelFutureListener,最终会调用到这里的operationComplete方法,进而在执行bind操作。

Channel 创建、初始化、注册

EventLoop 与Channel 的关联:
Netty 源码解读-服务端启动过程分析_第13张图片

1 创建

initAndRegister

首先Channel创建、初始化、注册,由于注册是一个异步的过程,所以initAndRegister()方法返回的是一个ChannelFuture。
在initAndRegister()方法中先通过反射创建了一个NioServerSocketChannel实例,接着调用init()方法初始化该NioServerSocketChannel对象。
Netty 源码解读-服务端启动过程分析_第14张图片

2 初始化

初始化channel流程,主要操作是设置channel属性、设置channel.pipeline的ChannelInitializer。
将channel、childHandler的参数设置到对应对象上,然后将childHandler添加到channel的pipleline中。
注意,ChannelInitializer是在channel注册到Selector之后被回调的。

init()

前面都是在初始化属性,重要的最后一段代码,它注册了一个ChannelInitializer到channelpipline上,该ChannelInitializer的initChannel方法创建了一个ServerBootstrapAcceptor注册到channel上,这个ServerBootstrapAcceptor会在服务器处理连接请求时使用。
this.init(Channel channel) 源码如下:
Netty 源码解读-服务端启动过程分析_第15张图片

ChannelInitializer

ChannelPipeline p = channel.pipeline();
head和tail是同一个对象,addLast()是一个双向链表的添加。
Netty 源码解读-服务端启动过程分析_第16张图片

/**
 * 初始channel属性,也就是ChannelOption对应socket的各种属性。
 * 比如 SO_KEEPALIVE SO_RCVBUF ... 可以与Linux中的setsockopt函数对应起来。
 * 最后将ServerBootstrapAcceptor添加到对应channel的ChannelPipeline中。
 */
@Override
void init(Channel channel) throws Exception {
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }
	
    ChannelPipeline p = channel.pipeline();
    // 获取childGroup和childHandler,传递给ServerBootstrapAcceptor
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
    }
 
    p.addLast(new ChannelInitializer<Channel>() {
        //在register0中,将channel注册到Selector之后,会调用invokeHandlerAddedIfNeeded,
        //进而调用到这里的initChannel方法
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            // 这里注册一个添加ServerBootstrapAcceptor的任务
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 添加ServerBootstrapAcceptor
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}
initChannel()

调用了 initChannel 方法。这里调用的并不是我们重写的 initChannel 方法,因为参数不是同一个类型,是ChannelHandlerContext,所以还需要再调用一层。
Netty 源码解读-服务端启动过程分析_第17张图片
Netty 源码解读-服务端启动过程分析_第18张图片

ServerBootstrapAcceptor

再回到init()方法,将ServerBootstrapAcceptor添加到对应channel的ChannelPipeline中,设置对外监听。
Netty 源码解读-服务端启动过程分析_第19张图片
ServerBootstrapAcceptor 构造方法
Netty 源码解读-服务端启动过程分析_第20张图片

DefaultChannelPipeline.class

Netty 源码解读-服务端启动过程分析_第21张图片
addLast()
Netty 源码解读-服务端启动过程分析_第22张图片
Netty 源码解读-服务端启动过程分析_第23张图片
this.callHandlerAdded0(newCtx);
Netty 源码解读-服务端启动过程分析_第24张图片
callHandlerAdded()
Netty 源码解读-服务端启动过程分析_第25张图片

移除重复的

Netty 源码解读-服务端启动过程分析_第26张图片

pipeline.fireChannelRegistered()
pipeline.fireChannelRegistered() 的方法。按照之前的 pipeline 的路子想一下,会如何执行?pipeline 作为管道,其中有我们设置的 handler 链表,这里肯定会顺序执行 main 方法中的 childerHandler。
Netty 源码解读-服务端启动过程分析_第27张图片
该方法会继续调用 Context 的 fireChannelRegistered 方法,Context 包装的就是我们自定义的 handler。当然我们没有重写该方法。我们只重写了 initChannel 方法。

3 注册

【注册channel,绑定eventloop线程】
经过前面两步, channel已经创建好和初始化好了, 但还没有看到 eventLoop 的影子. 实际上eventloop和channel间就差一个注册了.

回到initAndRegister()方法,channel初始化之后就该将其注册到Selector,即下面的register流程:
ChannelFuture regFuture = config().group().register(channel); //此处的group 即 是 bossGroup
Netty 源码解读-服务端启动过程分析_第28张图片

MultithreadEventLoopGroup.class
Netty 源码解读-服务端启动过程分析_第29张图片

SingleThreadEventLoop.class
Netty 源码解读-服务端启动过程分析_第30张图片

register

注册channel,绑定eventloop线程。
注册channel 的过程中,最终会在AbstractChannel$AbstractUnsafe的 register()方法中调用eventLoop.execute()方法,在EventLoop 中进行Channel 注册代码的执行。

开始真正的异步,线程开始启动。
如果是 boss 线程,而不是 worder 线程,所以eventLoop.inEventLoop()肯定无法通过判断。
Netty 源码解读-服务端启动过程分析_第31张图片
先判断当前线程是否是线程池的线程,如果是则直接执行注册方法,否则提交任务到线程池。为什么要这样做呢?
《Netty权威指南 第二版》中是这样说到——首先判断是否是NioEventLoop自身发起的操作。如果是,则不存在并发操作,直接执行Channel注册;如果由其他线程发起,则封装成一个Task放入消息队列中异步执行。此处,由于是由ServerBootstrap所在线程执行的注册操作,所以会将其封装成Task投递到NioEventLoop中执行。

startThread

从Bootstrap 的bind()方法跟踪到AbstractChannel$AbstractUnsafe 的register()方法,整个代码都是在主线程中运行的,因此eventLoop.inEventLoop()返回为false,于是进入到else 分支,调用eventLoop.execute()方法。

而NioEventLoop 没有实现execute()方法,因此调用的是SingleThreadEventExecutor 的execute()方法:inEventLoop == false,因此执行到else 分支,在这里就调用startThread() 方法来启动SingleThreadEventExecutor 内部关联的Java 本地线程了。
Netty 源码解读-服务端启动过程分析_第32张图片

doStartThread()

当调用AbstractChannel$AbstractUnsafe.register() 方法后, 会将一个EventLoop 赋值给AbstractChannel 内部的eventLoop 字段,这句代码就是完成EventLoop 与Channel 的关联过程。

总结:当EventLoop 的execute()第一次被调用时,就会触发startThread()方法的调用,进而导致EventLoop所对应的Java 本地线程启动。
Netty 源码解读-服务端启动过程分析_第33张图片

register0

register0()方法是由NioEventLoop执行的。进入到异步线程中查看 register0 方法。
Netty 源码解读-服务端启动过程分析_第34张图片

doRegister

AbstractNioChannel.class
将channel注册到执行当前操作的eventLoop的多路复用器上,并且将channel设置为附件,注册的操作为0。也就是不关心任何操作。

《Netty权威指南 第二版》中是这样说到——注册方法是多台的,它既可以被NioServerSocketChannel用来监听客户端的连接接入,也可以注册socketChannel用来监听网络读或者写操作。
那么什么时候会将操作设置为正确的值呢?Netty 源码解读-服务端启动过程分析_第35张图片

小结:回到 register0 方法中,该方法在成功注册到 selector 的读事件后,继续执行管道中可能存在的任务。

注册完成

doRegister() 操作之后伴随着多个回调及listener的触发:
pipeline.invokeHandlerAddedIfNeeded()
pipeline.fireChannelActive() //触发通道激活

// AbstractChannel$AbstractUnsafe
private void register0(ChannelPromise promise) {
    boolean firstRegistration = neverRegistered;
    // 这里调用的是AbstractNioChannel.doRegister
    // 这里将channel注册上去,并没有关注对应的事件(read/write事件)
    doRegister();
    neverRegistered = false;
    registered = true;
 
    // 调用handlerAdd事件,这里就会调用initChannel方法,设置channel.pipeline,也就是添加 ServerBootstrapAcceptor
    pipeline.invokeHandlerAddedIfNeeded();
 
    // 调用operationComplete回调
    safeSetSuccess(promise);
	// 回调fireChannelRegistered
    pipeline.fireChannelRegistered();
    // Only fire a channelActive if the channel has never been registered. This prevents firing
    // multiple channel actives if the channel is deregistered and re-registered.
    if (isActive()) {
        if (firstRegistration) {
			// 回调fireChannelActive
            pipeline.fireChannelActive();
        } else if (config().isAutoRead()) {
            beginRead();
        }
    }
}

注册完成后, netty 会触发一个invokeHandlerAddedIfNeeded()方法, 从而调用fireHandlerAdded()将首次调用 ChannelInitializer.initChannel(), 将 ServerBootstrapAcceptor 添加到pipeline进来。
Netty 源码解读-服务端启动过程分析_第36张图片
Netty 源码解读-服务端启动过程分析_第37张图片

这个 pendingHandlerCallbackHead 变量来自我们 addLast 的时候,如果该 pipeline 还没有注册到这个 eventLoop 上,则将这个包装过 handler 的 context 放进变量 pendingHandlerCallbackHead 中,事实上,这个 pendingHandlerCallbackHead 就是个链表的表头,后面的 Context 会被包装成一个任务,追加到链表的尾部。
Netty 源码解读-服务端启动过程分析_第38张图片

4 绑定

bind操作是在register之后进行的,因为register0是由NioEventLoop执行的,所以main线程需要先判断下future是否完成,如果完成直接进行doBind即可,否则添加listener回调进行doBind。

Netty 源码解读-服务端启动过程分析_第39张图片
Netty 源码解读-服务端启动过程分析_第40张图片

dobind

将绑定操作提交到线程池中,这样做的原因与注册操作是一样的。
Netty 源码解读-服务端启动过程分析_第41张图片
在这里插入图片描述
AbstractChannel.this.doBind()
Netty 源码解读-服务端启动过程分析_第42张图片

doBind0

Netty 源码解读-服务端启动过程分析_第43张图片

SocketUtils.class

Netty 源码解读-服务端启动过程分析_第44张图片

执行绑定

doBind方法是真正执行绑定操作的方法,是调用java的的ServerSocket#bind()方法。
到这里为止整个netty启动流程就基本接近尾声,可以对外提供服务了。
Netty 源码解读-服务端启动过程分析_第45张图片
Netty 源码解读-服务端启动过程分析_第46张图片

doBeginRead

绑定成功之后判断如果是第一次注册,则通知channelActive事件(channelActive回调、设置监听事件)。
通知完channelActive事件后会进行判断,channel是否是自动读,该值默认为true,所以会默认调用channel.read方法,该方法最终会调用AbstractNioUnsafe#doBeginRead()方法

Netty 源码解读-服务端启动过程分析_第47张图片

111

Netty 源码解读-服务端启动过程分析_第48张图片

xxx

触发childGroup把客户端child中的channel执行操作,这是Netty监听器实现核心原理。
Netty 源码解读-服务端启动过程分析_第49张图片

参考资料

Netty服务端启动流程
https://www.cnblogs.com/ouhaitao/p/12876027.html
Netty之EventLoop
https://www.cnblogs.com/wuzhenzhao/p/11221189.html

Netty 接受请求过程源码分析
https://www.cnblogs.com/stateis0/p/9062141.html

你可能感兴趣的:(netty,netty)