Netty,认识和深入(四),Netty线程模型

Netty,认识和深入(四),Netty线程模型

Netty线程模式

线程模型基本介绍

不同的线程模式,对程序的性能有很大影响,Netty线程模式和其他的线程模式进行对比有区别

传统阻塞I/O服务模型

如下图:
在这里插入图片描述

工作原理:

黄色的框表示对象,蓝色的框表示线程,白色的框表示方法(APi)

模型特点:

  1. 采用阻塞IO模式获取输入的数据
  2. 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回

缺点:

  1. 当并发数很大,就会创建大量的线程,占用很大的系统资源
  2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源浪费。
Reactor模式

基于I/O 复用模型: 多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务

根据Reactor的数量和处理资源池的数量不同,有3种典型的实现:

单Reactor 单线程,前台接待员和服务员是同一个人,全程为顾客服务

单Reactor 多线程,1个前台接待员,多个服务员,接待员只负责接待

主从Reactor 多线程, 多个前台接待员,多个服务生

I/O 复用 结合线程池,就是Reactor 模式基本设计思想,如下图:

在这里插入图片描述

  1. Reactor模式,通过一个或者多个输入同时传递给服务处理器的模式(基于事件驱动)
  2. 服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,因此Reactor模式也叫Dispatcher模式
  3. Reactor模式使用IO复用监听事件,收到事件后,分发给某个线程,这点就是网络服务器并发处理关键

如下以下有点

  1. 响应快,不必为单个同步时间所阻塞,虽然Reactor 本身依然是同步的
  2. 可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程 /进程的切换开销
  3. 扩展性好,可以方便的通过增加Reactor实例个数来充分利用cpu资源
  4. 复用性好,Reactor 模型本身 与具体事件处理逻辑无关,具有很高的复用性
单Reactor单线程

基本原理如下:

在这里插入图片描述

  1. select 是前面 I/O 复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求;
  2. Reactor 对象通过Select 监控客户端请求事件,收到事件后通过 disatch 分发;
  3. 如果是建立连接请求事件,则由Accept 通过Accept 连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理
  4. 如果不是建立连接事件,则 Recotor 会分发调用连接对应的Handler来响应
  5. Handler会完成 读写业务处理的完成业务流程

服务端用一个线程通过多路复用搞定所有的IO操作(包括连接读写等), 但是如果客户端连接数量较多,将无法支撑,前面的NIO的例子就属于这种模型

优点

模型简单,没有多线程,进程通信,竞争的问题,全部都在一个线程中完成

缺点

性能问题,只有一个线程,无法完全发挥多核CPU的性能,Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈

可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障

使用场景

客户端的数量有限,业务处理非常快速,比如redis在业务处理的时间复杂度0(1)的情况

单Reactor多线程

大致原理图如下:

在这里插入图片描述

  1. Reactor 对象通过select 监控客户端请求事件,收到事件后,通过dispatch进行分发
  2. 如果建立连接请求,则由Accept 通过,accept处理连接请求,然后创建一个Handler对象处理完成连接后的各种事件
  3. 如果不是连接请求,则由reactor分发调用连接对应的handler来处理
  4. handler 只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务
  5. worker线程池会分配独立的线程完成真正的业务,并将结果返回给handler

优点:可以充分的利用多核cpu的处理能力

缺点:多线程数据共享和访问比较复杂,reactor处理所有的事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈

主从Reactor多线程

针对单 Reactor多线程模型中,Reactor在单线程中运行,高并发场景下容易成为性能瓶颈,可以让Reactor在多线程中运行。

大致原理图如下:

在这里插入图片描述

  1. Reactor 主线程MainReactor 对象 通过select 监听连接事件,收到事件后,通过Acceptor处理连接事件

  2. 当Acceptor 处理连接事件后,MainReactor 将连接分配给SubReactor

  3. subreactor 将连接加入到连接队列进行监听,并创建handler 进行各种事件处理

  4. 当有新事件发生时,subreactor就会调用对应的Handler处理

  5. hand通过read读取数据,分发给后面的worker线程处理

  6. worker 线程池分配独立的worker 线程进行业务处理,并返回结果

  7. handler收到响应结果后,再通过send 将结果返回给client

  8. Reactor 主线程可以对应多个Reactor子线程

优点:

父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。

父线程与子线程的数据交互简单,Reactor主线程只需要把新连接传给子线程,子线程无需返回数据

缺点:编程复杂度较高

应用场景:这种模型在许多项目中广泛使用,包括Nginx主从Reactor多线程模型,Memcached主从多线程,Netty主从多线程模型的支持。

Netty模型

Netty简易线程模式

(Netty主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)

在这里插入图片描述

  1. BossGroup 线程维护Selector , 只关注Accept
  2. 当接收到 Accept 事件,获取到对应的 SocketChannel ,封装成NIOSocketChannel并注册到 Worker 线程(事件循环),并进行维护
  3. 当work线程监听到 selector 中通道发生自己感兴趣的事件后,就进行处理

Netty 工作原理示意详细图

在这里插入图片描述

  1. Netty 抽象出两组线程池BossGroup 专门 负责接受客户端的连接,workergroup 专门用来负责网络的读写
  2. BoosGroup 和 WorkerGruop 类型都是 NioEventLoopGroup
  3. NioEventLoopGroup 相当于一个事件循环组线程组,,这个组中包含有多个事件循环,每一个事件循环是NioEventLoop
  4. NioEventLoop 表示一个不断循环的执行处理任务的线程,每个NioEventLoop 都有一个 selector,用于监听绑定在其上的socket的网络通讯
  5. NioEventLoopGruop 可以有多个线程,即可以含有多个NioEventLop
  6. 每个Boss NioEventLoop 循环执行的步骤有3步
    1. 轮询accept事件
    2. 处理accept事件,与client建立俩连接,生成NioScocketChannel,并将其注册到某个worker NioEventLoop 上的selector
    3. 处理任务队列的任务,即runAllTasks
  7. 每个worker NIOEventLoop循环执行的步骤
    1. 轮询 read write 事件
    2. 处理 i/o 事件,即 read, write 事件,在对应 NioScocketChannel处理
    3. 处理任务队列的任务,即runAllTasks
  8. 每个Worker NioEventLoop 处理业务时,会使用pipeline 管道,pipeline 中包含了channel,即通过pipeline 可以获取到对应通道,管道中维护了很多的处理器

代码示例:

  • 服务器端
package com.kelecc.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.NettyRuntime;

/**
 * Netty 服务器端
 *
 * @Author keLe
 * @Date 2022/4/25
 */
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //boosGroup 只处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
        //两个都是无限循环
        //bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        // 默认实际cpu 核数*2
        NioEventLoopGroup boosGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        //验证一下
        System.out.println(NettyRuntime.availableProcessors());
        //创建服务器端的启动对象,配置参数
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        //1,设置两个线程组
        //2,使用NioSocketChannel 作为服务器的通道实现
        //3,设置线程队列得到连接个数
        //4,设置保持活动连接转台
        //5,创建一个通道测试对象 (匿名对象)
        serverBootstrap.group(boosGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG,128)
                .childOption(ChannelOption.SO_KEEPALIVE,true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //给pipeline 设置处理器
                        ch.pipeline().addLast(new NettyServerHandler());
                    }
                });
        System.out.println(".....服务器  is ready....");
        ChannelFuture cf = serverBootstrap.bind(6666).sync();

        //对关闭通道进行监听
        cf.channel().closeFuture().sync();

    }
}

自定义服务器端入站处理器

package com.kelecc.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;

/**
 * 功能描述: 自定义处理器
 *
 * @Author keLe
 * @Date 2022/4/25
 */
public class NettyServerHandler  extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器 读取线程:"+ Thread.currentThread().getName());
        System.out.println("server ctx ="+ctx);
        ByteBuf buffer = (ByteBuf) msg;
        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();


        System.out.println("客户端发送消息是:"+buffer.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:"+ctx.channel().remoteAddress());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入到缓存,并刷新
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客服端~",CharsetUtil.UTF_8));
    }

    /**
     * 功能描述: 发送异常,关闭通道
     * @Author keLe
     * @Date 2022/4/25
     * @param  ctx 上下文
     * @param  cause 异常原因
     * @return void
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端

package com.kelecc.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * 功能描述: Netty 客户端
 *
 * @Author keLe
 * @Date 2022/4/25
 */
public class NettyClient {
    public static void main(String[] args){

        EventLoopGroup eventExecutors = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //加入自己的处理器
                        ch.pipeline().addLast(new NettyClientHandler());
                    }
                });
        System.out.println("客户端 ....ok");

        try {
            ChannelFuture cf = bootstrap.connect("127.0.0.1", 6666).sync();
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
    }
}

客户端适配器

package com.kelecc.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * 功能描述: 自定义Netty 客户端 处理器
 *
 * @Author keLe
 * @Date 2022/4/26
 */
public class NettyClientHandler  extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(" client" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello ,server ", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Netty线程模型源码分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yq5eR4Pj-1657545602863)(C:\Users\wuchao\AppData\Roaming\Typora\typora-user-images\image-20220428000210581.png)]

NioEventLoopGroup类

在这里插入图片描述

我们通过上述代码可以得知,在初始化NioEventLoopGroup类时,会创建很对实例,那么我们开始进入源码

  //boosGroup 只处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
        //两个都是无限循环
        //bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数
        // 默认实际cpu 核数*2
        NioEventLoopGroup boosGroup = new NioEventLoopGroup();

在这里插入图片描述

在这里插入图片描述

其中每个 NioEventLoop 中都包含有 selector 和 selectedkeys ,

selector:

  1. Netty的IO线程NIOEventLoop 聚合了 Selector (选择器也叫多路复用器),可以同时并发处理成百上千个客户端连接。
  2. 当线程从某客户端Socket通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。
  3. 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道
  4. 由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频率I/O阻塞导致的线程挂起
  5. 一个I/O 线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O 连接 线程模型,架构的性能,弹性伸缩能力和可靠性都得到了极大的提升

selectedkeys表示 Selector 和网络通道的注册关系

去调用构造方法,请看上面类图,

在这里插入图片描述

开始 调用 父类 MultithreadEventLoopGroup 类

在这里插入图片描述

MultithreadEventLoopGroup

由 NioEventLoopGroup继续this方法,源码到 MultithreadEventLoopGroup 类,我们可以看到,当传入的 int nThreads=0 那么使用默认值,

在这里插入图片描述

bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数,默认实际cpu 核数*2 ,而我的cpu核数是16,那么乘以2就是32个子线程的个数;

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

由此 DEFAULT_EVENT_LOOP_THREADS 默认实际cpu 核数*2

在这里插入图片描述

MultithreadEventExecutorGroup

继续super 调用父类 源码来到 MultithreadEventExecutorGroup类,

在这里插入图片描述

MultithreadEventExecutorGroup 类,这个核心构造的方法:

 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
       //这里检查子线程个数是否小于等于0,满足条件就抛出非法参数异常   ,子线程的个数应该大于0
        checkPositive(nThreads, "nThreads");
       //如果此执行器为null,
        if (executor == null) {
            //会创建一个线程执行器,包含线程的个数定义的
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
		// 创建一个执行器的数组,长度为cpu核数*2
        children = new EventExecutor[nThreads];
		//这个循环就是根据cpu的核数 去线程
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                //创建一个NioEventLoop 放到数组里面
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                //如果不为true 关闭所有
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
     
        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
 }   

这段代码执行逻辑可以理解为:

  • 通过 ThreadPerTaskExecutor 构造一个 Executor 执行器,后面会细说,里面包含了线程执行的 execute() 方法
  • 接着创建一个 EventExecutor 数组对象,大小为传递进来的 threads 数量,这个所谓的 EventExecutor 可以理解为我们的 EventLoop,在这个 demo 中就是 NioEventLoop 对象
  • 最后调用 newChild 方法逐个初始化 EventLoopGroup 中的 EventLoop 对象

再回到 MultithreadEventExecutorGroup 中的构造方法入参中,有个 EventExecutorChooserFactory 对象,这里面是有个很亮眼的细节设计,通过它我们来洞悉 Netty 的良苦用心。

在这里插入图片描述

DefaultEventExecutorChooserFactory

EventExecutorChooserFactory 这个类的作用是用来选择 EventLoop 执行器的,我们知道 EventLoopGroup 是一个包含了 CPU * 2 个数量的 EventLoop 数组对象,那每次选择 EventLoop 来执行任务是选择数组中的哪一个呢?

在这里插入图片描述

DefaultEventExecutorChooserFactory 是一个选择器工厂类,调用里面的 next() 方法达到一个轮询选择的目的。

数组的长度是 length,执行第 n 次,取数组中的哪个元素就是对 length 取余

在这里插入图片描述

这里的优化就是在于先通过 isPowerOfTwo() 方法判断数组的长度是否为 2 的 n 次幂,判断的方式很巧妙,使用 val & -val == val
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iu4YzDOJ-1657545602867)(C:\Users\wuchao\AppData\Roaming\Typora\typora-user-images\image-20220428215911881.png)]

这里为什么要去煞费苦心的判断数组的长度是 2 的 n 次幂?

不知道小伙伴们是否还记得 HashMap?一般我们要求 HashMap 数组的长度需要是 2 的 n 次幂,因为在 key 值寻找数组位置的方法:(n - 1) & hash n 是数组长度,这里如果数组长度是 2 的 n 次幂就可以通过位运算来提升性能,当 length 为 2 的 n 次幂时下面公式是等价的:

n & (length - 1) <=> n % length

还记得上面说过,数组的长度默认都是 CPU * 2,而一般服务器 CPU 核心数都是 2、4、8、16 等等,所以这一个小优化就很实用了,再仔细想想,原来数组长度的初始化也是很讲究的。这里位运算的好处就是效率远远高于与运算,Netty 针对于这个小细节都做了优化,真是太棒了

ThreadPerTaskExecutor

在这里插入图片描述

传递进来的 threadFactoryDefaultThreadFactory,这里面会构造 NioEventLoop 线程命名规则为 nioEventLoop-1-xxx,我们就不细看这个了。当线程执行的时候会调用 execute() 方法,这里会创建一个 FastThreadLocalThread 线程,具体看代码

在这里插入图片描述

这里通过 newThread() 来创建一个线程,然后初始化线程对象数据,最终会调用到 Thread.init()

EventLoop 初始化

去调用 NioEventLoopGroup 类, 判断后面的参数如果 ==4 则放回一个 事件循环任务队列工厂 否则 就返回空的,并创建一个 NioEventLoop对象

children[i] = newChild(executor, args);

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

其实就是实例化一个 NioEventLoop 对象,然后返回。NioEventLoop 构造函数中会保存 provider 和事件轮询器 selector,在其父类中还会创建一个 MpscQueue队列,然后保存线程执行器 executor

再回过头来想一想,MultithreadEventExecutorGroup 内部维护了一个 EventExecutor[] children 数组, NettyEventLoopGroup 的实现机制其实就建立在 MultithreadEventExecutorGroup 之上。

每当 Netty 需要一个 EventLoop 时,会调用 next() 方法从 EventLoopGroup 数组中获取一个可用的 EventLoop 对象。其中 next 方法的实现是通过 NioEventLoopGroup.next() 来完成的,就是用的上面有过讲解的通过轮询算法来计算得出的。

最后总结一下整个 EventLoopGroup 的初始化过程:
在这里插入图片描述

  • EventLoopGroup(其实是 MultithreadEventExecutorGroup) 内部维护一个类型为 EventExecutor children 数组,数组长度是 nThreads
  • 如果我们在实例化 NioEventLoopGroup 时,如果指定线程池大小,则 nThreads 就是指定的值,反之是处理器核心数 * 2
  • MultithreadEventExecutorGroup 中会调用 newChild 抽象方法来初始化 children 数组
  • 抽象方法 newChild 是在 NioEventLoopGroup 中实现的,它返回一个 NioEventLoop 实例.
  • NioEventLoop 属性:SelectorProvider provider 属性: NioEventLoopGroup 构造器中通过 SelectorProvider.provider() 获取一个 SelectorProvider``Selector selector 属性: NioEventLoop 构造器中通过调用通过 selector = provider.openSelector() 获取一个 selector 对象.

在这里插入图片描述

总结

从上面源码我们得知,其实就是实例化一个 NioEventLoop 对象,然后返回。NioEventLoop 构造函数中会保存 provider 和事件轮询器 selector,在其父类中还会创建一个 MpscQueue队列,然后保存线程执行器 executor

再回过头来想一想,MultithreadEventExecutorGroup 内部维护了一个 EventExecutor[] children 数组, NettyEventLoopGroup 的实现机制其实就建立在 MultithreadEventExecutorGroup 之上。

每当 Netty 需要一个 EventLoop 时,会调用 next() 方法从 EventLoopGroup 数组中获取一个可用的 EventLoop 对象。其中 next 方法的实现是通过 NioEventLoopGroup.next() 来完成的,就是用的上面有过讲解的通过轮询算法来计算得出的。

Netty模块组件

【Bootstrap、ServerBootstrap】:

Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程 序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端 启动引导类。

【Future、ChannelFuture】:

在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。 但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事 件。

【Channel】:

Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel 为用户提供:

  1. 当前网络连接的通道的状态(例如是否打开?是否已连接?)

  2. 网络连接的配置参数 (例如接收缓冲区大小)

  3. 提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即 返回,并且不保证在调用结束时所请求的 I/O 操作已完成。

  4. 调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成 功、失败或取消时回调通知调用方。

  5. 支持关联 I/O 操作与对应的处理程序。 不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

下面是一些常用的 Channel 类型:

  1. NioSocketChannel,异步的客户端 TCP Socket 连接。

  2. NioServerSocketChannel,异步的服务器端 TCP Socket 连接。

  3. NioDatagramChannel,异步的 UDP 连接。

  4. NioSctpChannel,异步的客户端 Sctp 连接。

  5. NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文 件 IO。

【Selector】:

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。 当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册 的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单 地使用一个线程高效地管理多个 Channel 。

【NioEventLoop】:

NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务: I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触 发。

【NioEventLoopGroup】:

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组 线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线 程。

【ChannelHandler】:

ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。

ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,

可以继承它的子类:

  1. ChannelInboundHandler 用于处理入站 I/O 事件。

  2. ChannelOutboundHandler 用于处理出站 I/O 操作。

或者使用以下适配器类:

  1. ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。

  2. ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。

【ChannelHandlerContext】:

保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

【ChannelPipline】:

保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。 ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以 及 Channel 中各个的 ChannelHandler 如何相互交互。 在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,它们的组成关系如下:

在这里插入图片描述

一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。 read事件(入站事件)和write事件(出站事件)在一个双向链表中,入站事件会从链表 head 往后传递到最 后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

ByteBuf详解

从结构上来说,ByteBuf 由一串字节数组构成。数组中每个字节用来存放信息。 ByteBuf 提供了两个索引,一个用于读取数据,一个用于写入数据。这两个索引通过在字节数 组中移动,来定位需要读或者写信息的位置。 当从 ByteBuf 读取时,它的 readerIndex(读索引)将会根据读取的字节数递增。 同样,当写 ByteBuf 时,它 writerIndex 也会根据写入的字节数进行递增。

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可乐cc呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值