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

简述

在使用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操作。

客户端连接建立时,新建通道NioSocketChannel 通过pipeline转发给ServerBootstrapAcceptor,这个过程由线程池bossGroup分配的线程负责。

当ServerBootstrapAcceptor收到新建立的通道NioSocketChannel时与workGroup分配的线程绑定,并将用户添加的childHandler加入到该channel的pipeline,注册OP_READ读事件到Selector。该过程由线程池workGroup分配的线程负责。

当有数据可读时由线程池workGroup分配的线程处理,并一路通过pipeline回调到我们自己加入的TestChannelHandler

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启动过程分析

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 的类结构图

在这里插入图片描述

NioEventLoopGroup

NioEventLoopGroup.class
在这里插入图片描述
NioEventLoopGroup 初始化的时序图
在这里插入图片描述
基本步骤如下:

  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。
在这里插入图片描述
NioEventLoop架构图
在这里插入图片描述
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

在这里插入图片描述

MultithreadEventExecutorGroup.class

在这里插入图片描述

新连接接入通过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
在这里插入图片描述

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
在这里插入图片描述

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

类名ServerBootStrapAbstractBootStrap
Channel连接配置childOptionsoptions
参数childAttrsAttrs
线程组childGroupgroup
业务处理childHandlerhandler

设置通道类型

设置通道类型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,之后就可以对外提供服务了。
在这里插入图片描述
注意,这里如果main线程执行到regFuture.isDone()时,register还未完成,那么main线程是不会直接调用bind操作的,而是往regFuture上注册一个Listenner,这样channel register完成(注册到Selector并且调用了invokeHandlerAddedIfNeeded)之后,会调用safeSetSuccess,触发各个ChannelFutureListener,最终会调用到这里的operationComplete方法,进而在执行bind操作。

Channel 创建、初始化、注册

EventLoop 与Channel 的关联:
在这里插入图片描述

1 创建

initAndRegister

首先Channel创建、初始化、注册,由于注册是一个异步的过程,所以initAndRegister()方法返回的是一个ChannelFuture。
在initAndRegister()方法中先通过反射创建了一个NioServerSocketChannel实例,接着调用init()方法初始化该NioServerSocketChannel对象。
在这里插入图片描述

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) 源码如下:
在这里插入图片描述

ChannelInitializer

ChannelPipeline p = channel.pipeline();
head和tail是同一个对象,addLast()是一个双向链表的添加。
在这里插入图片描述

/**
 * 初始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,所以还需要再调用一层。
在这里插入图片描述
在这里插入图片描述

ServerBootstrapAcceptor

再回到init()方法,将ServerBootstrapAcceptor添加到对应channel的ChannelPipeline中,设置对外监听。
在这里插入图片描述
ServerBootstrapAcceptor 构造方法
在这里插入图片描述

DefaultChannelPipeline.class

在这里插入图片描述
addLast()
在这里插入图片描述
在这里插入图片描述
this.callHandlerAdded0(newCtx);
在这里插入图片描述
callHandlerAdded()
在这里插入图片描述

移除重复的

在这里插入图片描述

pipeline.fireChannelRegistered()
pipeline.fireChannelRegistered() 的方法。按照之前的 pipeline 的路子想一下,会如何执行?pipeline 作为管道,其中有我们设置的 handler 链表,这里肯定会顺序执行 main 方法中的 childerHandler。
在这里插入图片描述
该方法会继续调用 Context 的 fireChannelRegistered 方法,Context 包装的就是我们自定义的 handler。当然我们没有重写该方法。我们只重写了 initChannel 方法。

3 注册

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

回到initAndRegister()方法,channel初始化之后就该将其注册到Selector,即下面的register流程:
ChannelFuture regFuture = config().group().register(channel); //此处的group 即 是 bossGroup
在这里插入图片描述

MultithreadEventLoopGroup.class
在这里插入图片描述

SingleThreadEventLoop.class
在这里插入图片描述

register

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

开始真正的异步,线程开始启动。
如果是 boss 线程,而不是 worder 线程,所以eventLoop.inEventLoop()肯定无法通过判断。
在这里插入图片描述
先判断当前线程是否是线程池的线程,如果是则直接执行注册方法,否则提交任务到线程池。为什么要这样做呢?
《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 本地线程了。
在这里插入图片描述

doStartThread()

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

总结:当EventLoop 的execute()第一次被调用时,就会触发startThread()方法的调用,进而导致EventLoop所对应的Java 本地线程启动。
在这里插入图片描述

register0

register0()方法是由NioEventLoop执行的。进入到异步线程中查看 register0 方法。
在这里插入图片描述

doRegister

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

《Netty权威指南 第二版》中是这样说到——注册方法是多台的,它既可以被NioServerSocketChannel用来监听客户端的连接接入,也可以注册socketChannel用来监听网络读或者写操作。
那么什么时候会将操作设置为正确的值呢?在这里插入图片描述

小结:回到 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进来。
在这里插入图片描述
在这里插入图片描述

这个 pendingHandlerCallbackHead 变量来自我们 addLast 的时候,如果该 pipeline 还没有注册到这个 eventLoop 上,则将这个包装过 handler 的 context 放进变量 pendingHandlerCallbackHead 中,事实上,这个 pendingHandlerCallbackHead 就是个链表的表头,后面的 Context 会被包装成一个任务,追加到链表的尾部。
在这里插入图片描述

4 绑定

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

在这里插入图片描述
在这里插入图片描述

dobind

将绑定操作提交到线程池中,这样做的原因与注册操作是一样的。
在这里插入图片描述
在这里插入图片描述
AbstractChannel.this.doBind()
在这里插入图片描述

doBind0

在这里插入图片描述

SocketUtils.class

在这里插入图片描述

执行绑定

doBind方法是真正执行绑定操作的方法,是调用java的的ServerSocket#bind()方法。
到这里为止整个netty启动流程就基本接近尾声,可以对外提供服务了。
在这里插入图片描述
在这里插入图片描述

doBeginRead

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

在这里插入图片描述

111

在这里插入图片描述

xxx

触发childGroup把客户端child中的channel执行操作,这是Netty监听器实现核心原理。
在这里插入图片描述

参考资料

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值