Netty NioEventLoop详解

news2024/11/23 1:03:15

文章目录

  • 前言
  • 类图
  • 主要功能
  • NioEventLoop如何实现事件循环
  • NioEventLoop如何处理多路复用
  • Netty如何管理Channel和Selector
      • 管理Channel
      • 管理Selector
      • 注意事项


前言

Netty通过事件循环机制(EventLoop)处理IO事件和异步任务,简单来说,就是通过一个死循环,不断处理当前已发生的IO事件和待处理的异步任务。
① NioEventLoop是一个基于JDK NIO的异步事件循环类,它负责处理一个Channel的所有事件在这个Channel的生命周期期间。

② NioEventLoop的整个生命周期只会依赖于一个单一的线程来完成。一个NioEventLoop可以分配给多个Channel,NioEventLoop通过JDK Selector来实现I/O多路复用,以对多个Channel进行管理。

③ 如果调用Channel操作的线程是EventLoop所关联的线程,那么该操作会被立即执行。否则会将该操作封装成任务放入EventLoop的任务队列中。

④ 所有提交到NioEventLoop的任务都会先放入队列中,然后在线程中以有序(FIFO)/连续的方式执行所有提交的任务。

⑤ NioEventLoop的事件循环主要完成了:

  • 已经注册到Selector的Channel的监控,并在感兴趣的事件可执行时对其进行处理;
  • 完成任务队列(taskQueue)中的任务,以及对可执行的定时任务和周期性任务的处理(scheduledTaskQueue中的可执行的任务都会先放入taskQueue中后,再从taskQueue中依次取出执行)。

类图

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

主要功能

NioEventLoop 主要负责以下几个方面的功能:

  1. 事件循环NioEventLoop 是一个单线程的事件循环,它不断地轮询注册在其上的 Channel,看是否有就绪的 I/O 事件(如读、写、连接等),然后进行相应的处理。
  2. 任务执行:除了处理 I/O 事件外,NioEventLoop 还负责执行定时任务和普通任务。这些任务可以是用户自定义的,也可以是 Netty 框架内部的。
  3. Channel 的注册与注销:Netty 中的 Channel 在创建后需要注册到一个 EventLoop 上,这样它才能开始处理 I/O 事件。同样地,当 Channel 不再需要时,也需要从 EventLoop 上注销。
  4. Selector 的管理NioEventLoop 内部使用 Java NIO 的 Selector 来轮询 Channel 的就绪状态。它负责 Selector 的创建、销毁以及选择操作。

在 Netty 中,通常会有多个 NioEventLoop 实例,它们通常与多线程模型结合使用,以实现真正的并发处理。每个 NioEventLoop 负责处理一组 Channel 的 I/O 事件,这样可以将不同的 Channel 分布到不同的线程上,从而实现并发处理。

总的来说,NioEventLoop 是 Netty 中负责处理 I/O 事件和任务执行的核心组件,它使得 Netty 能够高效地处理大量的网络连接和 I/O 操作。

NioEventLoop如何实现事件循环

NioEventLoop在Netty中实现事件循环的过程是Netty网络框架的核心机制之一,它结合了Java NIO的非阻塞特性和事件驱动模型,以高效地处理网络事件。以下是NioEventLoop如何实现事件循环的详细过程:

  1. 轮询就绪事件
    在事件循环中,NioEventLoop调用Selectorselect()方法来等待Channel上就绪的事件。select()方法会阻塞,直到至少有一个Channel的事件就绪,或者超时。

  2. 处理就绪事件
    一旦select()方法返回,NioEventLoop会获取就绪的Channel集合,并遍历这些Channel。对于每个就绪的Channel,NioEventLoop会根据其感兴趣的事件类型调用相应的处理器(通常是ChannelPipeline中的ChannelInboundHandler)来处理这些事件。这可能包括读取数据、写入数据、处理连接等。

  3. 执行非I/O任务
    除了处理I/O事件外,NioEventLoop还负责执行提交给它的非I/O任务。这些任务可能包括用户自定义的业务逻辑、回调方法等。NioEventLoop通常有一个任务队列,用于存储待执行的任务。在事件循环中,它会检查任务队列,并依次执行其中的任务。

  4. 定时任务调度
    NioEventLoop还支持定时任务的调度。用户可以将定时任务提交给NioEventLoop,并指定任务的执行时间或执行间隔。NioEventLoop内部会维护一个定时任务列表,并根据任务的调度信息在合适的时间点执行这些任务。

通过这个事件循环机制,NioEventLoop能够高效地处理大量的网络连接和I/O事件,同时保持单线程的事件处理模型,简化了并发控制和线程安全的处理。此外,Netty还通过多线程模型和多个NioEventLoop实例的配合使用,实现了真正的并发处理,从而能够处理更大规模的并发连接和事件。

    @Override
    protected void run() {
        int selectCnt = 0;
        // 必须使用死循环不断进行事件轮询,获取任务和通道的 IO 事件
        for (;;) {
            try {
                int strategy;
                try {
                    /**
                     * 返回处理策略,就分为两种:
                     * 有任务 hasTasks() == true,就不能等待IO事件了,先直接调用 selectNow() 方法,
                     * 获取当前准备好IO 的通道channel 的数量(0 表示一个都没有),处理 IO 事件 和任务。
                     *
                     * 没有任务 hasTasks() == false,返回 SelectStrategy.SELECT (是负数),
                     * 没有要及时处理的任务,先阻塞等待 IO 事件
                     */
                    strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
                    switch (strategy) {
                    case SelectStrategy.CONTINUE:
                        continue;

                    case SelectStrategy.BUSY_WAIT:
                        // fall-through to SELECT since the busy-wait is not supported with NIO

                    case SelectStrategy.SELECT:
                        // 返回下一个计划任务准备运行的截止时间纳秒值
                        long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
                        if (curDeadlineNanos == -1L) {
                            // 返回 -1,说明没有下一个计划任务,
                            // 将 curDeadlineNanos 设置为 NONE,
                            // 调用 selector.select 方法时,就没有超时,
                            // 要无限等待了,除非被唤醒或者有准备好的 IO 事件。
                            curDeadlineNanos = NONE;
                        }
                        // 设置 超时等待时间
                        nextWakeupNanos.set(curDeadlineNanos);
                        try {
                            if (!hasTasks()) {
                                // 当前没有任务,那么就通过 selector 查看有没有 IO 事件
                                // 并设置超时时间,超时时间到了那么就要执行计划任务了
                                // 如果 curDeadlineNanos 是 NONE,就没有超时,无限等待。
                                strategy = select(curDeadlineNanos);
                            }
                        } finally {
                            // 这个更新只是为了帮助阻止不必要的选择器唤醒,
                            // 所以使用lazySet是可以的(没有竞争条件)
                            nextWakeupNanos.lazySet(AWAKE);
                        }
                        // fall through
                    default:
                    }
                } catch (IOException e) {
                    // 如果我们在这里接收到IOException,那是因为Selector搞错了。
                    // 让我们重新构建选择器并重试。
                    // https://github.com/netty/netty/issues/8566
                    rebuildSelector0();
                    selectCnt = 0;
                    handleLoopException(e);
                    continue;
                }
                /**
                 * 代码走到这里,
                 * 要么有 IO 事件,即 strategy >0
                 * 要么就是有任务要运行。
                 * 如果两个都不是,那么就有可能是 JDK 的 epoll 的空轮询 BUG
                 */

                selectCnt++;
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                boolean ranTasks;
                if (ioRatio == 100) {
                    // 如果 ioRatio
                    try {
                        if (strategy > 0) {
                            processSelectedKeys();
                        }
                    } finally {
                        // 确保运行了所有待执行任务,包括当前时间已经过期的计划任务
                        ranTasks = runAllTasks();
                    }
                } else if (strategy > 0) {
                    // strategy > 0 说明有 IO 事件,
                    // 那么需要调用 processSelectedKeys() 方法,执行 IO 时间
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        // 计算 IO 操作花费的时间
                        final long ioTime = System.nanoTime() - ioStartTime;
                        // 按照比例计算可以运行任务的超时时间 ioTime * (100 - ioRatio) / ioRatio,
                        // 超时时间到了,即使还有任务没有运行,也直接返回了,等下一个周期在运行这些任务
                        ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                } else {
                    // strategy == 0 说明没有 IO 事件,不用处理 IO 了
                    // 调用 runAllTasks(0) 方法,超时时间为0,这将运行最小数量的任务
                    ranTasks = runAllTasks(0);
                }

                if (ranTasks || strategy > 0) {
                    // 要么有任务运行,要么有 IO 事件处理
                    if (selectCnt > MIN_PREMATURE_SELECTOR_RETURNS && logger.isDebugEnabled()) {
                        logger.debug("Selector.select() returned prematurely {} times in a row for Selector {}.",
                                selectCnt - 1, selector);
                    }
                    selectCnt = 0;
                } else if (unexpectedSelectorWakeup(selectCnt)) {
                    // 即没有任务运行,也没有IO 事件处理,就有可能是 JDK 的 epoll 的空轮询 BUG
                    // 调用 unexpectedSelectorWakeup(selectCnt) 方法处理。
                    // 可能会重新建立 Select

                    selectCnt = 0;
                }
            } catch (CancelledKeyException e) {
                // Harmless exception - log anyway
                if (logger.isDebugEnabled()) {
                    logger.debug(CancelledKeyException.class.getSimpleName() + " raised by a Selector {} - JDK bug?",
                            selector, e);
                }
            } catch (Error e) {
                throw e;
            } catch (Throwable t) {
                handleLoopException(t);
            } finally {
                // Always handle shutdown even if the loop processing threw an exception.
                try {
                    if (isShuttingDown()) {
                        // 如果事件轮询器开始 shutdown,就要关闭 IO 资源
                        closeAll();
                        if (confirmShutdown()) {
                            return;
                        }
                    }
                } catch (Error e) {
                    throw e;
                } catch (Throwable t) {
                    handleLoopException(t);
                }
            }
        }
    }

NioEventLoop如何处理多路复用

NioEventLoop 在 Netty 中处理多路复用的方式主要是基于 Java NIO 的非阻塞特性和选择器(Selector)机制。多路复用允许单个线程同时处理多个通道(Channel)的 I/O 事件,从而提高了系统的吞吐量和响应速度。

以下是 NioEventLoop 如何处理多路复用的具体步骤:

  1. 初始化与注册

    • NioEventLoop 在初始化时创建一个 Selector 对象,用于监控多个 Channel 的状态。
    • 当一个 Channel 需要处理 I/O 事件时,它会被注册到 NioEventLoopSelector 上,并指定感兴趣的事件类型(如读、写等)。
  2. 轮询就绪事件

    • NioEventLoop 进入事件循环,调用 Selectorselect() 方法等待通道上就绪的事件。
    • select() 方法会阻塞,直到至少有一个 Channel 的事件就绪,或者超时。
  3. 处理就绪事件

    • select() 方法返回时,NioEventLoop 获取就绪的 Channel 集合。
    • 对于每个就绪的 ChannelNioEventLoop 根据其感兴趣的事件类型调用相应的处理器(如 ChannelInboundHandler)来处理这些事件。
  4. 多路复用核心

    • Selector 的核心作用在于它能够同时监控多个 Channel,并且只选择那些处于就绪状态的 Channel 进行处理。
    • 通过这种方式,NioEventLoop 可以使用单个线程高效地处理大量并发的 Channel,而无需为每个 Channel 创建一个独立的线程。
  5. 异步非阻塞通信

    • 由于 Netty 采用了异步通信模式,NioEventLoop 中的 IO 操作(如读、写)都是非阻塞的。
    • 这意味着当一个 IO 操作不能立即完成时(例如,等待数据从网络读取),线程不会被阻塞,而是可以继续处理其他任务或事件。
  6. 任务调度与执行

    • 除了处理 I/O 事件外,NioEventLoop 还负责执行提交给它的任务(如定时任务、用户自定义任务等)。
    • 这些任务与 I/O 事件一起,在 NioEventLoop 的事件循环中得到调度和执行。

Netty如何管理Channel和Selector

管理Channel

  1. 注册Channel
    当一个新的连接建立时,Netty会创建一个新的Channel实例(例如NioSocketChannelNioServerSocketChannel),并将其注册到NioEventLoop中的Selector上。注册过程包括将Channel添加到Selector的监控列表中,并设置其感兴趣的事件类型(如读、写、连接等)。

  2. 事件处理
    一旦Selector检测到某个Channel上有事件就绪(例如数据可读或可写),它会通知NioEventLoop。然后,NioEventLoop会调用相应的ChannelPipelineChannelHandler来处理这些事件。

  3. 关闭Channel
    当连接关闭时,Netty会注销Channel并从Selector的监控列表中移除它。同时,相关的资源也会被释放。

管理Selector

  1. 创建Selector
    NioEventLoop初始化时,它会创建一个Selector实例。这个Selector用于监控所有注册到该NioEventLoopChannel

  2. 轮询就绪事件
    NioEventLoop在事件循环中不断调用Selectorselect()方法来等待Channel上的事件就绪。一旦有事件就绪,Selector会返回这些事件的集合。

  3. 处理就绪事件
    NioEventLoop遍历Selector返回的就绪事件集合,并为每个事件调用相应的处理器。这通常涉及到调用ChannelPipeline中的ChannelHandler来处理这些事件。

  4. 优化Selector的使用
    Netty可能会使用多个SelectorNioEventLoop实例来优化性能,特别是在处理大量并发连接时。通过分散负载到多个线程和Selector上,Netty能够充分利用多核CPU的资源,提高吞吐量。

注意事项

  • 通常情况下,你不需要直接管理Selector。Netty的NioEventLoopChannel抽象为你提供了高级的API来处理I/O事件和连接。
  • 在编写自定义的ChannelHandler时,你需要关注如何处理事件,而不是如何管理ChannelSelector
  • 如果你需要执行定时任务或自定义的后台任务,可以使用NioEventLoop的任务队列来提交这些任务。这些任务会在事件循环的适当时候得到执行。

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

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

相关文章

23年坚守,只为打造高品质立秀膨体,索康让品质为中国说话

2024年3月23日,第二十三届上海国际整形美容外科大会(以下简称“大会”)在上海召开,本次大会由张涤生整形外科发展基金会主办,上海交通大学附属第九人民医院整复外科、Chinese Journal of Plastic and Reconstructive S…

性能优化 - 你知道CSS有哪些优化方案吗

难度级别:中高级及以上 提问概率:70% CSS是前端开发工作中必不可少的技能之一,同时也是网页开发中必不可少的重要元素之一。但很多人所开发的项目本身对性能要求并不高,再加上项目周期紧张,久而久之,也就容易养成不考虑细节的习惯,觉得C…

NzN的数据结构--二叉树part2

上一章我们介绍了二叉树入门的一些内容,本章我们就要正式开始学习二叉树的实现方法,先三连后看是好习惯!!! 目录 一、二叉树的顺序结构及实现 1. 二叉树的顺序结构 2. 堆的概念及结构 3. 堆的实现 3.1 堆的创建 …

04-12 周五基于VS code + Python实现CSDN发布文章的自动生成

简介 之前曾经说过,在撰写文章之后,需要,同样需要将外链的图像转换为的形式,因此,可以参考 04-12 周五 基于VS Code Python 实现单词的自动提取 配置步骤 配置task 在vscode的命令面板configure task。配置如下的任…

python知识点汇总(十一)

python知识点总结 1、当Python退出时,是否会清除所有分配的内存?2、Python的优势有哪些?3、什么是元组的解封装4、Python中如何动态获取和设置对象的属性?5、创建删除操作系统上的文件6、主动抛出异常7、help() 函数和 dir() 函数…

人工智能基础部分26-基于知识推理引擎KIE算法的原理介绍与实际应用

大家好,我是微学AI,今天给大家介绍一下人工智能基础部分26-基于知识推理引擎KIE算法的原理介绍与实际应用。知识推理引擎(Knowledge Inference Engine, KIE)是一种人工智能技术,其核心原理是基于规则和逻辑的方法来处理复杂的问题。它构建在业…

从前端角度防范XSS攻击的策略与实践

XSS(Cross-Site Scripting,跨站脚本攻击)是一种常见的网络安全威胁,它允许攻击者将恶意脚本注入到正常的网页中,从而在其他用户的浏览器上执行这些脚本。这可能导致数据泄露、会话劫持、甚至是对受害者计算机的完全控制…

探新路建“枢纽” 湖南深耕中非经贸合作“试验田”

湖南作为中国与非洲经贸合作的重要窗口,积极推动中非经贸关系的发展和深化。通过构建覆盖全产业链的高效运作模式,湖南企业能够在一周内将肯尼亚干制鳀鱼加工成为麻辣鲜香的劲仔深海小鱼并投入中国市场。此外,湖南还致力于推动非洲优质农产品…

【vue】watchEffect 自动侦听器

watchEffect&#xff1a;自动监听值的变化 获取旧值时&#xff0c;不是很方便&#xff0c;建议用watch <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevic…

数学基础:深度学习的语言

数学基础&#xff1a;深度学习的语言 概述 在深度学习的世界里&#xff0c;数学不仅仅是一套工具&#xff0c;它是构建、理解和优化深度学习模型的基石。从向量空间的概念到复杂的优化算法&#xff0c;数学的每一个分支都在深度学习的发展中扮演着关键角色。本文的目标是通过深…

解决cmd输入py文件路径不能执行,使用anaconda prompt 能执行

究其原因&#xff0c;是因为没有配置环境&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 第一步&#xff1a;配置环境变量 操作步骤如下&#xff1a; 1、右击此电脑 ---->属性 2、高级系统设置 3、点击环境变量 4、选择 …

网工内推 | 网络工程师,13薪,周末双休,华三、华为认证优先

01 路邦远大 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、配合市场销售人员&#xff0c;做好产品的售后服务工作&#xff1b; 2、负责项目方案安装调试指导以及日常客户使用培训&#xff0c;对客户提出的问题提出解决方案&#xff1b; 3、为客户提供专业、规范的…

简析OpenHarmony软总线能力

分布式软总线是 OpenHarmony 的重要能力&#xff0c;设计目标是实现多设备间的通信方式。分布式软总线是分布式硬件和分布式软总线的重要基础&#xff0c;分布式软总线提供一种不区分链路的设备间发现、组网和传输的能力&#xff1a; 发现&#xff1a;应用 WiFi&#xff0c;蓝…

Baichuan-7B-chat WebDemo 部署调用

Baichuan-7B-chat WebDemo 部署调用 Baichuan2 介绍 Baichuan 2 是百川智能推出的新一代开源大语言模型&#xff0c;采用 2.6 万亿 Tokens 的高质量语料训练。在多个权威的中文、英文和多语言的通用、领域 benchmark 上取得同尺寸最佳的效果。 环境准备 在autodl平台中租一…

高并发高性能接口中,异步打印并采集业务日志的实现方案

一、背景 高并发接口中&#xff0c;为了提高接口的高性能&#xff0c;在需要保存审计及操作记录的时候&#xff0c;往往有以下常见方案&#xff1a; 保存到redis数据库异步保存到mysql/mongodb/es等数据库logger打印业务日志&#xff0c;采集与展示则交由elk模块 对于第一种…

【MATLAB源码-第4期】基于MATLAB的1024QAM误码率曲线,以及星座图展示。

1、算法描述 正交幅度调制&#xff08;QAM&#xff0c;Quadrature Amplitude Modulation&#xff09;是一种在两个正交载波上进行幅度调制的调制方式。这两个载波通常是相位差为90度&#xff08;π/2&#xff09;的正弦波&#xff0c;因此被称作正交载波。这种调制方式因此而得…

福建单航次最大批量汽车“出海”

3月12日这一天&#xff0c;在福州海关的严密监管下&#xff0c;共有4000辆上汽名爵品牌的汽车被高效有序地装载到“安吉智慧”号滚装船上&#xff0c;这批车辆即将启程前往荷兰、埃及、英国等多个海外市场。在这批出口汽车中&#xff0c;新能源车型占据了显著的比例&#xff0c…

FFmpeg 框架分析

1 概述 按照DirectShow 对播放器模块的划分&#xff0c;一个完整的播放器应该需要获得以下四个模块支持&#xff1a; Source Filter : 数据源&#xff0c;可以是本地文件fopen, 也可以是网络文件,http,rtp,rtmp 等等 Demux Fliter: 解复用&#xff0c;下载的数据是带容器封装…

Java | Leetcode Java题解之第18题四数之和

题目&#xff1a; 题解&#xff1a; class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> quadruplets new ArrayList<List<Integer>>();if (nums null || nums.length < 4) {return…

循环单链表算法库

学习贺老师数据结构 数据结构之自建算法库——循环单链表_循环单链表 csdn-CSDN博客​​​​​​ 整理总结出的循环单链表算法库 v1.0 : 基本实现功能 v2.0(2024.4.6): 修复Delete_SpecificLocate_CyclicList()删除节点函数bug,添加验证删除节点是否超范围判断 目录 1.主要功能…