Netty—Reactor线程模型详解

news2024/11/21 0:35:00

文章目录

  • 前言
  • 线程模型基本介绍
    • 线程模型分类
    • Reactor线程模型介绍
    • Netty线程模型:
  • 传统阻塞IO的缺点
  • Reactor线程模型
    • 单Reactor单线程模式
    • 单Reactor多线程模式
    • 主从Reactor多线程
    • Reactor 模式小结
  • Netty 线程模型
    • 案例说明:
    • Netty核心组件简介
      • ChannelPipeline与Channel的关系
  • 参考文献

前言

前面我们说了Netty中的NIO:Netty—NIO万字详解 ;其中涉及到一个重要的底层模型—Reactor线程模型,而Netty线程模型就是基于Reactor线程模型的改进,它也是Netty中个人认为是最重要的一个知识点,在我看来只有掌握了Netty线程模型才算是真正意义上的理解了Netty!!

所以本文将详细说明Reactor线程模型,以及Netty线程模型,帮助大家更好的理解Netty

线程模型基本介绍

线程模型是指在操作系统中,线程是如何被创建、调度和管理的

线程模型分类

  1. 单线程模型:如Redis,它采用单线程来处理命令,所有命令都回进入到一个队列中,然后逐个执行。
  2. 多线程模型:也被称为并发多线程模型。

如果细分的话有以下几种:

  1. 单线程模型: 在单线程模型中,程序只有一个执行线程,所有的任务按照顺序执行。这种模型的优点是简单,易于理解和调试。然而,缺点是无法充分利用多核处理器的优势,因为在任何时刻只有一个任务在执行。
  2. 多线程模型: 多线程模型允许程序同时执行多个线程,每个线程独立执行不同的任务。这可以提高程序的并发性和性能,特别是在多核处理器上。但多线程编程也引入了一些复杂性,例如需要处理线程同步、死锁等问题。
  3. 分布式模型: 在分布式模型中,任务被分布到多个计算节点上执行。这可以通过网络进行通信,每个节点独立运行。分布式模型通常用于大规模系统,以提高可伸缩性和容错性。
  4. Reactor模型: Reactor模型是一种事件驱动的模型,其中一个主线程负责监听和分发事件,而多个工作者线程负责处理事件。这样可以有效地处理大量并发连接,例如在网络编程中。
  5. Proactor模型: Proactor模型也是一种事件驱动的模型,但与Reactor不同,Proactor模型中事件处理由操作系统负责,而应用程序只需提供回调函数。这样可以实现异步非阻塞的I/O操作。
  6. Actor模型: Actor模型是一种并发计算模型,其中每个"Actor"都是独立的实体,通过消息传递进行通信。每个Actor都有自己的状态和行为,可以并发地执行。

Reactor线程模型介绍

Reactor线程模型是一种事件驱动的编程模式,主要用于处理高并发网络I/O操作。它的核心思想是使用一个或多个反应器(Reactor)来监听和分发网络事件,然后将这些事件分配给相应的处理器(Handler)或者线程池进行处理。

主要组成部分和工作流程:

  1. Reactor:这是模型的核心组件,负责监听所有的网络连接和相应事件。它通过使用I/O多路复用技术(如Linux的epoll、Java的NIO Selector等)在一个单独的线程或者一组线程中监控多个socket的活动,这样可以避免传统阻塞I/O模型中为每个连接创建新线程带来的资源开销。
  2. Dispatcher:在某些实现中,dispatcher是一个逻辑角色,负责根据事件的类型将事件转发给合适的处理器。例如,新的连接请求可能被转发给Acceptor处理,而读写事件则被转发给对应的Handler
  3. Acceptor:在接收到新的连接请求时,Acceptor负责接受这个连接,并将其配置为非阻塞模式,然后将其添加到Reactor的监听列表中。
  4. Handler(也称为Worker):当Reactor检测到某个socket上有读写事件时,它会将事件转发给对应的Handler进行处理。Handler负责实际的业务逻辑,如读取数据、解析协议、执行操作并返回响应。

而根据Reactor和线程池的数量,Reactor线程模型可以有以下几种变体:

  • 单Reactor单线程模型:所有事件的监听、分发和处理都在一个线程中完成。
  • 单Reactor多线程模型:一个Reactor线程负责监听和分发事件,而事件的处理则由一个线程池完成。
  • 多Reactor多线程模型:多个Reactor线程并行监听和分发事件,每个Reactor都有自己的线程池来处理事件。

这种模型的优势在于其高并发性和低资源消耗。通过复用线程和高效地调度事件,Reactor线程模型能够在处理大量并发连接时保持良好的性能和可扩展性。然而,其复杂性也较高,需要精心设计以确保线程安全和高效的事件处理。在许多现代高性能网络框架和库中,如Java的Netty和Python的Twisted,都采用了Reactor线程模型或者其变体。

举例说明:

假设我们有一个餐厅,这个餐厅有很多的桌子,每个桌子上有一个人在等待食物。当食物准备好后,服务员会通知对应桌子的人来取食物。在这个过程中,餐厅就相当于Reactor,而桌子上的客人就是Handlers。当食物准备好后,餐厅会通知对应的桌子来取食物,也就是将事件分发给对应的处理程序来处理。

Netty线程模型:

虽然说Netty线程模型是由主从Reactor线程模型改进的,但Netty线程模型模型并不是一成不变的,它实际取决于启动参数配置。通过设置不同的启动参数来支持 Reactor 不同的线程模型。

  • Netty单线程模型—> Reactor单线程模型—> 传统阻塞IO+多路复用器
  • Netty多线程模型—> Reactor多线程模型—> Reactor单线程模型+线程池
  • 主从Netty多线程模型—> 主从Reactor多线程模型—>Reactor多线程模型+线程池

后面会详细说明!!

传统阻塞IO的缺点

传统阻塞IO很容易理解,这里不做过多的解释,直接说缺点,然后后面看Reactor线程模式是怎么解决这些缺点的,先看图:

20200421213026276

缺点主要包括以下3点:

  1. 性能低下:在等待I/O操作完成期间,线程会被阻塞,无法执行其他任务。这会导致CPU资源的浪费,尤其是在需要处理大量I/O操作的情况下。
  2. 无法处理大量并发连接:由于每个I/O操作都需要等待,因此无法高效地处理大量并发连接。这限制了系统的吞吐量和并发性。
  3. 无法充分利用多核资源:由于线程在等待I/O操作完成时会被阻塞,因此无法充分利用多核资源。这限制了系统的性能和并发性。

Reactor线程模型

针对传统阻塞IO服务模型的缺点,Reactor线程模型的解决办法是:

1、基于I/O复用模型:

Reactor线程模型采用I/O复用技术,允许多个连接共享同一个阻塞对象。这意味着应用程序只需要在一个阻塞对象上等待,无需为每个连接都创建一个独立的线程。当某个连接有新的数据可以处理时,操作系统会通知应用程序,线程从阻塞状态返回,开始进行业务处理。

crm客户资料表关系 (1)

对上图说明:

这里很像我们前面讲过的NIO,在案例七中,就是采用基于I/0复用模型,Selector就相当于一个IO复用器;但这种模式是有问题的?它还是无法充分利用多核CPU的性能,也会有性能瓶颈,因为虽然IO复用了,但还是一个线程去处理所有事物

2、基于线程池复用线程资源

Reactor线程模型通过线程池来复用线程资源。这意味着不必再为每个连接创建新的线程,而是将连接完成后的业务处理任务分配给线程池中的线程进行处理。一个线程可以处理多个连接的业务,提高了系统的并发性和效率。

crm客户资料表关系 (2)

对上图说明:

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

用前文说得NIO举例就是在传统的NIO处理读写事件时加入了线程池,如下:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ReactorMultiThread {

    public static void main(String[] args) throws IOException {
        // 创建一个ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 绑定到端口8080
        serverSocketChannel.bind(new InetSocketAddress(8080));
        // 创建一个Selector
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到Selector上,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        while (true) {
            // 轮询Selector,等待事件发生
            int readyChannels = selector.select();
            if (readyChannels == 0) {
                continue;
            }

            // 处理发生的事件
            for (SelectionKey key : selector.selectedKeys()) {
                // 处理连接事件
                if (key.isAcceptable()) {
                    // 接受连接
                    ServerSocketChannel acceptChannel = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = acceptChannel.accept();
                    // 将SocketChannel注册到Selector上,监听读写事件
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                }
                // 处理读写事件
                if (key.isReadable() || key.isWritable()) {
                    // 提交给线程池处理
                    executorService.submit(() -> {
                        // 处理读写事件
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        // 读取数据
                        byte[] buffer = new byte[1024];
                        int readBytes = socketChannel.read(buffer);
                        if (readBytes > 0) {
                            // 处理读取到的数据
                        }
                        // 写数据
                        socketChannel.write(buffer);
                    });
                }
            }
            // 清空已处理的事件
            selector.selectedKeys().clear();
        }
    }
}

Reactor线程模型就是通过 单个线程 使用Java NIO包中的Selectorselect()方法,进行监听。当获取到事件(如accept、read等)后,就会分配(dispatch)事件进行相应的事件处理(handle)。

如果要给 Reactor线程模型 下一个更明确的定义,应该是:

Reactor线程模式 = Reactor(I/O多路复用)+ 线程池

单Reactor单线程模式

Reactor单线程

  1. Select是前面1/0复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求
  2. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发
  3. 如果是建立连接请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务处理
  4. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应
  5. Handler 会完成 Read→业务处理→Send 的完整业务流程
  1. 结合实例:服务器端用一个线程通过多路复用搞定所有的10操作(包括连接,读、写等),编码简单,清晰明了,但是如果客户端连接数量较多,将无法支撑,上篇文章中的的NIO案例就属于这种模型。

  2. 无论是事件监听、事件分发、还是事件处理,都始终只有 一个线程 执行所有的事情

优点

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

缺点

  1. 性能问题,只有一个线程,无法完全发挥多核 CPU 的性能。Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈
  2. 可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障

使用场景

  1. 低负载的应用程序:当应用程序的负载较低,同时并发连接数也相对较少时,使用 Reactor 单线程模式可以满足需求,并且可以简化代码的编写和维护。
  2. 短时间处理的任务:如果应用程序的任务处理时间较短,不会导致单线程阻塞过长时间,那么使用 Reactor 单线程模式可以获得良好的性能。
  3. 资源有限的环境:在一些资源有限的环境中,如嵌入式设备或特定的服务器环境,使用 Reactor 单线程模式可以减少线程创建和上下文切换的开销,提高系统的效率。

比如Redis在业务处理的时间复杂度O(1)的情况


单Reactor多线程模式

为了提高性能,我们可以把复杂的事件处理handler交给线程池,那就可以演进为 「单Reactor多线程模型」

Reactor多线程模式 = Reactor单线程模式 + 线程池

Reactor多线程模型

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

  • 缺点:在极个别特殊场景中,一个Reactor线程负责监听和处理所有的客户端连接可能会存在性能问题。例如并发百万客户端连接(双十一、春运抢票)

适用于以下场景:

  1. 高并发的应用程序:当应用程序需要处理大量并发连接时,使用单Reactor多线程模式可以提高并发处理能力,每个连接都可以在独立的线程中进行处理,避免了线程之间的竞争。
  2. 长时间处理的任务:如果应用程序需要处理耗时较长的任务,使用单Reactor多线程模式可以避免阻塞主线程,提高系统的响应性能。主线程负责接收和分发事件,而具体的任务处理则由其他线程完成。
  3. 资源充足的环境:在资源充足的环境下,可以使用单Reactor多线程模式来充分利用多核处理器的性能优势,提高系统的吞吐量。 需要注意的是,单Reactor多线程模式需要合理配置线程池的大小,避免线程数过多导致资源浪费或线程数过少导致性能瓶颈。此外,多线程模式下需要注意线程安全性和资源共享的问题。

主从Reactor多线程

主从Reactor线程模型 (2)

  1. Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
  2. Acceptor处理连接事件后,MainReactor将连接分配给SubReactor
  3. subreactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
  4. 当有新事件发生时,subreactor就会调用对应的handler处理
  5. handler通过read读取数据,分发给后面的worker线程处理
  6. worker线程池分配独立的worker线程进行业务处理,并返回结果
  7. handler收到响应的结果后,再通过send将结果返回给client
  8. Reactor主线程可以对应多个Reactor子线程,即MainRecator可以关联多个SubReactor

Reactor 模式小结

用生活中的例子说明:

  1. 单Reactor单线程模型,前台和服务员都是一个人,全称为客户服务
  2. 单Reactor多线程模型,一个前台接待员和多个服务员,前台只负责接待
  3. 主从Recator多线程模型,多个前台,多个服务员

Reactor线程模型的优点:

  1. 高效事件处理:通过I/O多路复用技术,Reactor能够同时监控多个连接,减少了系统的阻塞情况,提高了系统的并发性能。
  2. 非阻塞IO:Reactor模型基于异步非阻塞IO,使得线程在等待网络IO操作完成时,可以去处理其他任务,提高了资源利用率。
  3. 职责分离:Reactor模型将事件的分发和处理分离,父线程(或主Reactor)负责监听和分发事件,子线程(或工作线程、从Reactor)负责具体的业务处理,这种分工使得系统结构清晰,易于维护和扩展。
  4. 可伸缩性:根据系统的负载情况,可以通过增加或减少工作线程来调整系统的处理能力,具有良好的水平扩展性。
  5. 避免数据竞争:通过线程间的通信机制,如队列或者直接的数据传递,可以有效地避免多线程环境中的数据竞争问题。
  6. 降低上下文切换开销:由于父线程主要负责事件分发,子线程主要负责业务处理,这种分工可以减少不必要的上下文切换,提高系统效率。
  7. 支持高并发:尤其在高并发场景下,Reactor线程模型能够更好地处理大量的客户端请求,保持系统的响应能力和稳定性。
  8. 灵活的线程模型选择:Reactor模型有多种变体,如单线程、多线程和主从多线程模型,可以根据具体的应用需求和性能目标选择最适合的线程模型。
  9. 易于实现和使用:许多成熟的网络框架,如Netty,已经实现了Reactor线程模型,开发者可以直接使用这些框架,而无需从头开始实现复杂的网络编程逻辑。

其实这三种模型的演变就一句话:

没有什么是加一层解决不了的,如果有,那就再加一层!!!

Netty 线程模型

Netty的线程模型是基于主从Reactor多线程模型做了一定改进的,能理解Reactor线程模型,Netty线程模型基本也就理解了,只是要懂各个组件之间的功能和组合,先看图:

Netty工作流程图

不是说Netty线程模型只能是主从Reactor线程模型,而是可以根据传入的参数,来改变它的线程模式的!!!

  1. Nety抽象出两组线程池BossGroup (Boos NioEventLoopGroup)专门负责接收客户端的连接,WorkerGroup专门负责网络的读写

  2. BossGroup和WorkerGroup类型都是NioEventLoopGroup

  3. NioEventLoopGroup相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是NioEventLoop

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

  5. NioEventLoopGroup可以有多个线程,即可以含有多个NioEventLoop

  6. 每个Boss NioEventLoop循环执行的步骤有3步

    • 轮询accept事件

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

    • 处理任务队列的任务,即unAllTasks T)

  7. 每个Worker NIOEventLoop循环执行的3个步骤

    • 轮询read,write事件
    • 处理IO事件,即read,write事件,在对应NioScocketChannel处理
    • 处理任务队列的任务,即runAllTasks
  8. 每个Worker NIOEventLoop处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获取到对应通道,管道中维护了很多的处理器

案例说明:

服务端:

/**
 * com.atguigu.netty
 *
 * @author co_wb_junjie_qiu
 * @version Id: NettyServer, v 0.1 2023/12/19 9:23 co_wb_junjie_qiu Exp $
 */
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {

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

        try {
            // 服务端的辅助程序
            // 创建服务器端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boosGroup, workerGroup) // 设置两个线程组
                    // 设置服务器用于接受新的连接的通道为NioServerSocketChannel类。这是一个非阻塞的NIO服务器套接字通道。
                    .channel(NioServerSocketChannel.class)
                    // 设置服务器的TCP参数,SO_BACKLOG表示在拒绝新的连接之前,服务器可以排队等待的连接数。
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 为接受的新连接设置TCP参数,开启keep-alive机制,以避免长时间的不活动导致的连接中断(保持长连接)。
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    // 设置处理新连接的初始化程序。当有新连接到达时,这个初始化程序会被用来配置新连接的管道。
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        // 创建一个通道初始化对象
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 给Pipeline 设置处理器(自定义)
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });// 给workerGroup 的 EventLoop 对应的管道设置处理器

            // 启动服务器,并绑定端口
            ChannelFuture sync = bootstrap.bind(6668).sync();

            // 对关闭通道进行监听
            sync.channel().closeFuture().sync();
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服务端处理器:

/**
 * com.atguigu.netty
 *
 * 说明
 * 1.我们自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
 * 2.这时我们自定义一个Handler,才能称为一个handler
 *
 * @author co_wb_junjie_qiu
 * @version Id: NettyServerHandler, v 0.1 2023/12/25 15:10 co_wb_junjie_qiu Exp $
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 读取通道数据
     * @param ctx 上下文对象,含有 管道pipeline,通道 channel 、地址
     * @param msg 客户端发送过来的数据,默认Object
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器读取线程 "+ Thread.currentThread().getName());
        ChannelPipeline pipeline = ctx.pipeline();
        System.out.println("pipeline 本身是一个双向列表" + pipeline);

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

    /**
     * 数据读取完成之后
     * 一般来说,我们会对这个发送的数据进行编码
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // writeAndFlush 是 写入和刷新
        // 将数据写入缓存并刷新
        // 一般讲,我们队这个发送的数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello 客户端",CharsetUtil.UTF_8));
    }

    /**
     * 处理异常,一般需要关闭通道
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端:

/**
 * com.atguigu.netty
 *
 * @author co_wb_junjie_qiu
 * @version Id: NettyClient, v 0.1 2023/12/25 15:20 co_wb_junjie_qiu Exp $
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

客户端处理器:

/**
 * com.atguigu.netty
 *
 * @author co_wb_junjie_qiu
 * @version Id: NettyClientHandler, v 0.1 2023/12/25 15:30 co_wb_junjie_qiu Exp $
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 当通道就绪就会触发该方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client channelActive");
        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核心组件简介

  1. Channel
    • ChannelNetty 中的核心概念,代表了应用程序和网络之间的连接。
    • 它封装了网络套接字,并提供了读写数据、绑定端口、连接远程地址、关闭连接等基本的 I/O 操作。
    • Channel 提供了非阻塞的 I/O 操作,使得 Netty 能够在单个线程中处理多个连接。
  2. EventLoop
    • EventLoopNetty 中负责处理 Channel 事件的组件。
    • 每个 EventLoop 负责监听和处理其关联的 Channel 上的事件,包括读写操作、连接建立、连接关闭等。
    • 一个 EventLoop 在其生命周期内通常会与一个或多个 Channel 关联,确保 Channel 的所有操作都在同一个线程上下文中执行,从而避免了线程安全问题。
  3. ChannelHandler
    • ChannelHandler 是 Netty 中处理 Channel 事件的接口或者类。
    • 应用程序通过实现 ChannelHandler 接口或者扩展 ChannelInboundHandlerChannelOutboundHandler 类来定制自己的业务逻辑。
    • ChannelHandler 可以参与到 Channel 的生命周期中的多个阶段,如连接建立、数据读取、数据写入、连接关闭等。
  4. ChannelPipeline
    • ChannelPipeline 是一个处理链,包含了多个 ChannelHandler
    • 当 Channel 上发生事件时,这些事件会在 ChannelPipeline按照一定的顺序传递给各个 ChannelHandler 进行处理。
    • ChannelPipeline 允许开发者灵活地配置和组织 ChannelHandler,以实现复杂的业务逻辑和协议解析。
  5. EventLoopGroup
    • EventLoopGroup 是一组 EventLoop 的集合。
    • Netty 通常会为服务器和客户端分别创建一个 EventLoopGroup
    • EventLoopGroup 负责分配 EventLoop 给新的 Channel,以及管理它们的生命周期。
  6. ChannelFuture
    • ChannelFuture 表示一个异步操作的结果。
    • 当在 Channel 上执行诸如连接、写入数据等异步操作时,会返回一个 ChannelFuture。
    • 通过注册 ChannelFutureListener,应用程序可以监听操作的完成、失败或者取消等事件。
  7. Bootstrap、ServerBootstrap(引导器):
    • Bootstrap是启动Netty应用程序的入口,用于配置Channel和其他相关的参数。
    • 通过Bootstrap可以配置客户端和服务器,包括线程模型、Channel类型、ChannelHandler等。
  8. ByteBuf(字节缓冲区):
    • ByteBuf是Netty中用于处理字节数据的缓冲区,提供了灵活的API用于读取、写入和操作字节数据。
    • ByteBuf具有池化和零拷贝等优化特性,使得数据的处理更加高效。
  9. Codec(编解码器):
    • Netty提供了丰富的编解码器,用于处理字节数据的编码和解码,包括StringDecoderStringEncoderObjectDecoderObjectEncoder等。
    • 编解码器可以简化开发者的工作,实现对各种协议的支持

其中最核心的是前4个 ,可以说其他组件都是为了辅助它们的。

  1. Channel:通道是 Netty 中的基本组件,它表示一个网络连接。
  2. EventLoop:事件循环是 Netty 中负责处理 I/O 事件的组件。
  3. ChannelHandler:通道处理器是 Netty 中负责处理 I/O 事件的组件。
  4. ChannelPipeline:通道管道是 Netty 中负责将 I/O 事件从 Channel 传递到 ChannelHandler 的组件。

ChannelPipeline与Channel的关系

其中比较难理解是ChannelPipeline通道与Channel管道,这两个概念,下面将详细说明:

  • 每个 Channel 都有一个与之关联的 ChannelPipeline
  • ChannelPipelineChannel 的一部分,它负责处理所有与该 Channel 相关的事件和数据。
  • Channel 接收到数据或者发生其他事件时,这些事件会被自动地传递到其关联的 ChannelPipeline 中进行处理。
  • 开发者可以通过操作 ChannelPipeline 来添加、删除或者替换 ChannelHandler,从而改变事件处理的逻辑。

生活中的例子说明:

  • 假设你正在经营一家快递公司,Channel 可以类比为公司的运输车辆,它负责实际的包裹运输。

  • ChannelPipeline 则可以看作是包裹从发出到接收过程中的一系列处理环节。例如,当一个包裹(相当于网络数据)进入你的公司时,它首先会经过收件区(相当于 ChannelPipeline 中的第一个 ChannelHandler),在那里进行登记和初步检查。然后,包裹被送到分拣区(第二个 ChannelHandler),根据目的地进行分类。接着,包裹被装载到相应的运输车辆(Channel)上,运往目标城市。到达目标城市后,包裹再次经过一系列的处理环节(更多的 ChannelHandler),如卸货、本地配送等,最终送达客户手中。

  • 在这个例子中,每辆运输车辆(Channel)都有自己的运输流程(ChannelPipeline),包括了各个环节的处理步骤(ChannelHandler)。通过调整运输流程中的处理环节,你可以优化整体的运输效率和服务质量。同样,在 Netty 中,通过配置和操作 ChannelPipeline,你可以定制网络通信的处理逻辑,提高系统的性能和灵活性。

参考文献

尚硅谷Netty教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1337580.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

条件覆盖和条件组合覆盖测试设计-实验八例题

目录 条件覆盖 判定-条件覆盖 条件组合覆盖 实验内容&#xff1a; 以银行内部转账为实例&#xff0c;针对内部转账业务逻辑代码进行分析&#xff0c;运用条件覆盖和条件组合覆盖进行测试用例设计。 实验过程&#xff1a; 条件覆盖 条件覆盖&#xff08;Condition Cover…

官宣定了!2024年举办4次PMP认证考试,每个季度一次

就在刚刚&#xff0c;2023年12月26日14:05分&#xff0c;PMI和中国国际人才交流基金会通过官微&#xff0c;联合发布了2024年PMI认证考试计划的通知&#xff0c;正式宣告了2024年的PMP考试初步安排。 这个通知发布之后&#xff0c;有一些伙伴问华研荟一些细节问题&#xff0c;…

挑战Python100题(6)

100+ Python challenging programming exercises 6 Question 51 Define a class named American and its subclass NewYorker. Hints: Use class Subclass(ParentClass) to define a subclass. 定义一个名为American的类及其子类NewYorker。 提示:使用class Subclass(Paren…

vue-awesome-swiper轮播组件

安装版本&#xff1a;"swiper": "^6.0.0", 安装版本&#xff1a;"vue-awesome-swiper": "^4.1.1", <div class"swiper_conter"><swiper class"swiper" :options"swiperOption" ref"mySw…

怎么实现Servlet的自动加载

在实际开发时&#xff0c;有时候会希望某些Servlet程序可以在Tomcat启动时随即启动。但在默认情况下&#xff0c;第一次访问servlet的时候&#xff0c;才创建servlet对象。 如果servlet构造函数里面的代码或者init方法里面的代码比较多&#xff0c;就会导致用户第一次访问serv…

浅谈能效管理平台在污水处理厂中的应用

摘要&#xff1a;《“十四五”城镇污水处理及资源化利用发展规划》指出&#xff0c;2021—2025 年合理减缓我国城镇污水收集处理设施发展不平衡不充分的矛盾&#xff0c;系统推动补短板强弱项&#xff0c;全方面提升污水收集处理效能&#xff0c;加速推进污水资源化利用&#x…

溴乙腈,2028年将以4.5%左右的复合年增长率增长

溴乙腈是一种化合物&#xff0c;主要用作合成各种药物、农用化学品和其他特种化学品的中间体。近年来&#xff0c;受医疗保健、农业和化学制造等各种最终用途行业对溴乙腈的需求不断增加的推动&#xff0c;全球溴乙腈市场一直在稳步增长。全球市场分析&#xff1a; 在制药和农业…

【Unity地形】使用地形工具创建场景环境-Terrain

如上图Unity的地形工具可以让我们实现创建复杂、丰富的3D室外环境。 我们创建地形很简单&#xff0c;在层级面板中右键-3Dobject-Terrain 就可以创建一个默认的地形模型&#xff01;这个模型是Unity内置的。 接下来的地形编辑功能全部集中在这个地形的组件上 主要功能如下&…

怎么修复MSVCR110.dll文件?全面解析MSVCR110.dll缺失修复方法

MSVCR110.dll文件缺失问题在Windows操作系统用户中相当普遍&#xff0c;经常导致应用程序启动失败或崩溃。MSVCR110.dll是Microsoft Visual C Redistributable for Visual Studio 2012的一部分&#xff0c;且应用程序通常依赖这个DLL文件来执行C库中的代码。文件的丢失可能源自…

Rabbit加密算法

一、引言 随着信息技术的快速发展&#xff0c;数据安全已成为越来越受到重视的领域。加密算法作为保障数据安全的重要技术手段&#xff0c;在通信、存储等领域得到了广泛应用。Rabbit加密算法作为一种新型的加密算法&#xff0c;凭借其简单易懂的原理、高速的运算性能以及良好…

isEmpty 和 isBlank 的用法区别,居然一半的人答不上来.....

亲爱的小伙伴们&#xff0c;由于微信公众号改版&#xff0c;打乱了发布时间&#xff0c;为了保证大家可以及时收到文章的推送&#xff0c;可以点击上方蓝字关注测试工程师成长之路&#xff0c;并设为星标就可以第一时间收到推送哦&#xff01; 也许你两个都不知道,也许你除了is…

MuJava提供的方法级别的7类变异算子总结

MuJava简洁 Java (muJava) 是 Java 程序的变异系统。 它自动生成用于传统突变测试和类级别突变测试的突变体。 Java 可以测试单个类和多个类的包。 用户以对封装在单独 JUnit 类的方法中的被测类的方法调用序列的形式提供测试。 官网地址&#xff1a;Java Home Page 归档表格…

【Mybatis】深入学习MyBatis:概述、主要特性以及配置与映射

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a; Mybatis ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 一、概述 MyBatis简介 主要特性 1. 动态SQL 2.结果映射 3 .插件机制 二、MyBatis配置文件 1.配置文件结构 数据库连…

力扣-收集足够苹果的最小花园周长[思维+组合数]

题目链接 题意&#xff1a; 给你一个用无限二维网格表示的花园&#xff0c;每一个 整数坐标处都有一棵苹果树。整数坐标 (i, j) 处的苹果树有 |i| |j| 个苹果。 你将会买下正中心坐标是 (0, 0) 的一块 正方形土地 &#xff0c;且每条边都与两条坐标轴之一平行。 给你一个整…

labelme目标检测数据类型转换

1. labelme数据类型 LabelMe是一个开源的在线图像标注工具&#xff0c;旨在帮助用户创建和标记图像数据集。它提供了一个用户友好的界面&#xff0c;让用户可以直观地在图像上绘制标记框、多边形、线条等&#xff0c;以标识和注释图像中的对象或区域。 GitHub&#xff1a;http…

[linux]高级IO

文章目录 高级IO0. IO介绍1. 五种IO模型1.0 感性理解(故事版)1.1 阻塞IO1.2 非阻塞IO1.3 信号驱动IO1.4 IO多路转接1.5 异步IO 2. 高级IO重要概念2.1 同步通信 vs 异步通信2.2 阻塞 vs 非阻塞 3. 其他高级IO4. 非阻塞IO4.1 fcntl4.2 实现函数SetNoBlock4.2.0 阻塞方式读取标准输…

Shell 脚本应用(四)

正则表达式概述 正则表达式又称正规表达式&#xff0c;常规表达式。在代码中常简写为regex&#xff0c;regexp 或RE.正则表达式 是使用单个字符串来描述&#xff0c;匹配一系列符合某个句法规则的字符串&#xff0c;简单来说&#xff0c;是一种匹配字符串 的方法&#xff0c;通…

矩估计定义及例题

一、总体、样本、统计量 总体&#xff1a;在一个统计问题中&#xff0c;我们把研究对象的全体称为总体&#xff0c;构成总体的每个成员称为个体。 样本&#xff1a;在总体中抽取样本的过程称之为抽样&#xff0c;得到样本。 统计量&#xff1a; 二、矩估计 1.矩估计原理 估…

IRIS、Cache系统类汉化

文章目录 系统类汉化简介标签说明汉化系统包说明效果展示类分类%Library包下的类重点类非重点类弃用类数据类型类工具类 使用说明 系统类汉化 简介 帮助小伙伴更加容易理解后台系统程序方法使用&#xff0c;降低代码的难度。符合本土化中文环境的开发和维护&#xff0c;有助于…

浅析兼容性测试主要过程

兼容性测试是软件开发的重要环节&#xff0c;旨在验证软件在不同操作系统、浏览器、设备以及其他相关组件之间的兼容性和互操作性。下面是兼容性测试的主要过程&#xff1a; 1. 确定测试对象&#xff1a;需要确定要进行兼容性测试的软件或应用程序。这可以是一个网站、移动应用…