NettyのNIOBIO原理解析

1、IO模型

        在IO模型中,主要可分为同步与异步操作:

        在同步 I/O 模型中,I/O 操作是阻塞的,当一个进程或线程执行 I/O 操作时,它会一直等待这个操作完成才继续执行后续的代码。

        在异步 I/O 模型中,I/O 操作是非阻塞的,这意味着进程或线程在发起 I/O 操作后会立即返回,可以继续执行其他任务。当 I/O 操作完成时,系统会通过回调函数、事件通知或信号等方式通知进程或线程。

      

        1.1、用户态和内核态

          当调用一次 channel.read 或 stream.read 后,会切换至操作系统内核态来完成真正数据读取,是因为实际的 I/O 操作,如从磁盘读取数据或从网络套接字接收数据,是由操作系统内核管理的。

        用户态:是应用程序代码运行的地方,受到严格的访问限制,不能直接操作硬件或内存中的某些关键区域。

        内核态:操作系统内核运行的地方,拥有对硬件和系统资源的完全访问权限,负责执行系统调用、管理硬件设备、内存和进程调度等任务。

        当用户调用channel.read 或 stream.read时,最初的代码执行是在用户态,read 方法内部会触发一个系统调用导致上下文切换,从用户态切换到内核态

        在内核态,操作系统内核会执行实际的 I/O 操作:

  • 文件读取:如果是从文件读取,内核会检查文件系统的缓存,如果缓存中有数据则直接返回;否则,会从磁盘读取数据到内核缓冲区,再复制到用户缓冲区。
  • 网络读取:如果是从网络套接字读取,内核会检查网络缓冲区,如果有数据则直接返回;否则,会等待网络数据到达。

        可见在内核态,实际的I/O操作也是分为了等待数据复制数据两步。

        一旦内核完成数据读取操作,它会将读取到的数据复制到用户态缓冲区,然后返回结果。系统调用完成,切换回用户态

        1.2、阻塞IO

        传统的阻塞IO,当一个进程或线程发起 I/O 操作(如读取或写入数据)时,操作会一直等待,直到这个 I/O 操作完成才会继续执行后续的代码。

        阻塞IO的工作机制:

  • 发起 I/O 请求:进程或线程调用 I/O 操作函数,例如读取文件、从网络套接字读取数据等。
  • 进入阻塞状态:如果数据还没有准备好,进程或线程将进入阻塞状态,等待数据准备完成。这意味着 CPU 的控制权会暂时被操作系统收回,直到 I/O 操作完成。(例如调用read方法,客户端还没有准备好信息
  • 内核态处理 I/O:操作系统内核处理实际的 I/O 操作,如从磁盘读取数据或等待网络数据到达。(从用户态转换到内核态
  • 数据准备就绪:一旦数据准备好,操作系统会将数据从内核缓冲区复制到用户缓冲区。
  • 返回结果:I/O 操作完成,进程或线程从阻塞状态恢复,继续执行后续代码。

 

        1.3、非阻塞IO

        例如前篇提到的ServerSocketChannel,就可以设置模式为非阻塞。当设置为非阻塞时,客户端没有发送消息,服务端不会在read方法处陷入阻塞,而是会立刻返回,如果是在循环中则会不停地进行重试:

    

        1.4、多路复用

        多路复用实际上也是属于同步阻塞模式的一种,体现在调用selector.select();方法时,如果没有监听到任何事件则会发生阻塞,直到有事件发生才恢复运行。


         以上三种,均可归类为单线程同步模式

        1.5、异步IO

        在异步IO中,通常会涉及到多线程,即线程一负责Read操作,而不必一直阻塞等待结果,可以继续执行后面的操作。当Read方法得到结果后,由另一个线程通知线程一读取的结果。

        所以异步IO是多线程异步模式的体现。

        附文件异步IO的使用案例:

@Slf4j
public class AioDemo1 {
    public static void main(String[] args) throws IOException {
        try{
            AsynchronousFileChannel s = 
                AsynchronousFileChannel.open(
                	Paths.get("1.txt"), StandardOpenOption.READ);
            ByteBuffer buffer = ByteBuffer.allocate(2);
            log.debug("begin...");
            s.read(buffer, 0, null, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    log.debug("read completed...{}", result);
                    buffer.flip();
                    debug(buffer);
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    log.debug("read failed...");
                }
            });

        } catch (IOException e) {
            e.printStackTrace();
        }
        log.debug("do other things...");
        System.in.read();
    }
}

 

        4.6、多路复用与阻塞/非阻塞IO

        上面的案例都是以单事件举例,无法看出多路复用相比较于传统阻塞/非阻塞IO的优势。

        假设我们建立了两个连接,在处理第二个连接的read事件前,必须要先将第一个连接的read事件处理完成,如果第一个连接的read方法在阻塞,那么第二个连接的read则永远无法执行。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(true);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        List<SocketChannel> clients = new ArrayList<>();

        while (true){
            SocketChannel channel = serverSocketChannel.accept();
            if (channel != null){
                clients.add(channel);
            }

            Iterator<SocketChannel> iterator = clients.iterator();
            while (iterator.hasNext()) {
                SocketChannel client = iterator.next();
                try {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int bytesRead = client.read(buffer);
                    if (bytesRead == -1) {
                        // 客户端关闭连接
                    } else if (bytesRead > 0) {
                        // 处理读取到的数据
                    }
                } catch (IOException e) {
                    // 处理异常,关闭通道
                    iterator.remove();
                    try {
                        client.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }

        }

        而使用Selector实现多路复用进行改进,假设和上面发生一样的场景,连接一的read没有接收到客户端的信息,但是连接二接收到了,那么selector.select();方法就会解除阻塞,直接执行连接二的可读事件。无需等待连接一接收到客户端的信息。

        // 创建 ServerSocketChannel 并配置为非阻塞模式
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        // 打开 Selector 并将 ServerSocketChannel 注册到 Selector 上,监听连接事件
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 等待事件发生(阻塞直到有事件发生或超时)
            selector.select();

            // 获取所有发生的事件的 SelectionKey
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();

                // 移除当前 SelectionKey,避免重复处理
                iterator.remove();

                try {
                    // 处理事件
                    if (key.isAcceptable()) {
                        // 有新连接请求,接受连接并配置为非阻塞模式
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = server.accept();
                        socketChannel.configureBlocking(false);
                        // 将新的 SocketChannel 注册到 Selector 上,监听读事件
                        socketChannel.register(selector, SelectionKey.OP_READ);
                      
                    } else if (key.isReadable()) {
                        // 有数据可读,读取数据
                       
                        }
                    }
                } catch (IOException e) {
                    // 处理异常并关闭通道
                    key.cancel();
                    key.channel().close();
                }
            }
        }

2、零拷贝

        零拷贝的目的是减少数据在应用程序和操作系统之间传输时的复制次数,从而提高系统性能。
        在传统的IO中数据从磁盘传输到网络,并非直接一步到位,而是要经历以下的过程:

  1. 磁盘到内核缓冲区:操作系统将数据从磁盘读取到内核空间的缓冲区。
  2. 内核缓冲区到用户缓冲区:数据从内核缓冲区复制到用户空间的缓冲区(应用程序缓冲区)。
  3. 用户缓冲区到内核缓冲区:应用程序处理数据后,将数据从用户空间的缓冲区复制回内核空间的缓冲区,以便通过网络发送。
  4. 内核缓冲区到网络:最后,数据从内核空间的缓冲区传输到网络设备,发送到目的地。

        在上面的传输过程中,涉及到了内核缓冲区用户缓冲区  切换的问题,总共切换了三次,同时也经历了四次拷贝操作。

  • 用户缓冲区:位于应用程序的地址空间内,可以直接被应用程序访问和操作,由应用程序负责分配和释放内存。可以通过普通的内存读写操作来访问数据。
  • 内核缓冲区:位于操作系统的内核空间内,普通应用程序无法直接访问。由操作系统内核管理和控制,包括内存的分配和释放。应用程序无法直接访问内核缓冲区的数据。

        用户态与内核态的切换越频繁,拷贝操作越多,效率就越低。


        NIO的ByteBuffer,就对其进行了优化:

        ByteBuffer具体有两个实现:

        简单的说,HeapByteBuffer的数据存储在 Java 堆内存中,由 Java 虚拟机(JVM)进行管理。而DirectByteBuffer的数据存储在 JVM 之外的直接内存中,通过本地方法库(Native Libraries)进行管理。

        DirectByteBuffer 在实现零拷贝时具有明显的优势:

        文件传输:在文件传输时,可以使用 FileChannel的TransferTo或 TransferFrom方法,直接将文件内容传输到网络套接字或另一个文件通道,这些方法在内部使用了零拷贝技术,通过直接内存避免了数据复制。        

        网络传输:在网络传输时,可以利用 DirectByteBuffer 直接将缓冲区数据传输到 SocketChannel,而无需将数据先复制到 JVM 堆内存中,然后再传输,从而提高了传输效率和性能。

        共同点在于省去了将数据从内核缓冲区->用户缓冲区->内核缓冲区的步骤,而是直接在两个内核缓冲区之间进行转换。

        此外还有使用DMA进行零拷贝优化的:

        DMA 是硬件支持的技术,可以让外设(如磁盘或网络接口卡)直接将数据传输到内存,而不需要经过 CPU。避免了数据在内核和用户空间之间的复制。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Google Earth Engine(GEE)——在控制台上答应出一个button按钮

函数: ui.Button(label, onClick, disabled, style) A clickable button with a text label. Arguments: label (String, optional): The buttons label. Defaults to an empty string. onClick (Function, optional): A callback fired when the button is clicked. T…

栈的实现详解

目录 1. 栈1.1 栈的概念及结构1.2 栈的实现方式1.3 栈的应用场景 2. 栈的实现2.1 结构体2.2 初始化2.3 销毁2.4 入栈2.5 出栈2.6 获取栈顶元素2.7 判空2.8 获取个数 3. test主函数4. Stack.c文件5. Stack.h文件6. 运行展示 1. 栈 1.1 栈的概念及结构 栈&#xff1a;一种特殊的…

2024.6.14 作业 xyt

使用手动连接&#xff0c;将登录框中的取消按钮使用第二中连接方式&#xff0c;右击转到槽&#xff0c;在该槽函数中&#xff0c;调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c…

AI Agent智能应用从0到1定制开发(完结)

在数字化时代的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;代理智能应用如同星辰般璀璨&#xff0c;引领着技术革新的潮流。从零开始定制开发一款AI Agent智能应用&#xff0c;就像是在无垠的宇宙中绘制一颗新星的轨迹&#xff0c;每一步都充满了挑战与创新的火花。…

【Photoshop】PS修改文字内容

Photoshop(PS)修改图片上文字内容&#xff0c;网上教材不少&#xff0c;本人整理实践过的方法&#xff0c;分享给各位。本人实践方法&#xff1a; 内容识别填充&#xff1a;适用于背景色复杂的图片内容修补工具&#xff1a;适用于背景色为纯色的图片 方式一&#xff1a;内容识…

HAL库开发--STM32的HAL环境搭建

知不足而奋进 望远山而前行 目录 文章目录 前言 下载 安装 解压 安装 添加开发包 修改仓库路径 下载软件开发包&#xff08;慢&#xff0c;不推荐&#xff09; 解压已有软件开发包&#xff08;快&#xff0c;推荐&#xff09; 总结 前言 在嵌入式系统开发中&#x…

【数据的增值之路】全生命周期的数据演化过程

引言&#xff1a;随着云计算、大数据、人工智能、区块链等新一代信息技术的快速发展&#xff0c;数据已经成为推动经济增长的重要生产要素。数据量的爆炸式增长&#xff0c;为挖掘数据价值、推动数字经济发展提供了丰富的资源基础。重要概念解析&#xff1a; 数据经济&#xf…

【内存管理之指针艺术】

1.记住&#xff1a;不同的类型具有不同的内存大小 2.指针只是存储地址的变量 3.指针艺术&#xff1a;增长1 4.重来 5.指针艺术&#xff1a;减小1 6.指针艺术 6.下标运算符[] 7.指针图解 7.指向对象的指针 7.指向指针的指针

Java—读取properties配置文件

编写配置文件 usernameroot password123456 urljdbc:mysql://localhost:3306/myDatabase driverClassNamecom.mysql.cj.jdbc.Driver 编写测试类 import java.io.FileInputStream; import java.io.IOException; import java.util.Enumeration; import java.util.Properties;/*…

CCAA质量管理【学习笔记】​​ 备考知识点笔记(四)

第四节 质量非数据资料分析的基础工具 1 关 联 图 2.1 概念 所谓关联图&#xff0c;就是对关系复杂而相互纠缠的问题&#xff0c;依据原因—结果或目的一手段等关系&#xff0c; 在逻辑上用箭头把各要素之间的因果关系连接起来&#xff0c;厘清复杂问题、整理语言文字资料…

神舟电脑文件误删怎么办?这些恢复方法助你轻松解决

神舟电脑文件误删怎么办&#xff1f;在信息爆炸的时代&#xff0c;电脑已经成为我们日常生活和工作中不可或缺的重要工具。然而&#xff0c;有时我们会因为一些不小心的操作&#xff0c;误删了电脑中的重要文件&#xff0c;尤其是在使用神舟电脑这类高性能设备时&#xff0c;文…

编译一个叫:未来的IDE-Zed编辑器(Windows平台)

一、前言 截止到2024-6-15&#xff0c;Zed官方并未给出Windows的二进制安装包&#xff0c;如果想在Windows平台使用的话需要自己编译&#xff0c;我是如何编译的请随我道来&#xff0c;有兴趣的码友可以尝试下&#xff0c;在下可不敢保证各位码友按我这方法能100%编译出来&…

RK3568技术笔记八 开发环境的搭建

先按照前面的方法将 ubuntu 安装在 PC 机上。 编译开发Linux系统&#xff0c;虚拟机Ubuntu 系统要求&#xff1a; 64位系统&#xff0c;硬盘空间大于或等于200G&#xff0c;内存不小于6GB。 建议使用 Ubuntu18.04 系统进行编译。 光盘资料&#xff1a;SAIL-RK3568开发板光盘…

SQL编程基础常见题型练习

SQL编程基础常见题型练习 1. 基础查询1.1. 基础查询1.2. 简单处理查询结果 2. 条件查询2.1. 基础排序2.2. 基础操作符2.3. 高级操作符 3. 高级查询3.1. 计算函数3.2. 分组查询 4. 多表查询4.1. 子查询4.2. 链接查询4.3. 组合查询 5. 必会的常用函数5.1. 条件函数5.2. 日期函数 …

将Jar用三种方式生成Windows的安装程序

无论是WEB(spring boot)的JAR,还是JavaFX以及swing的Jar,要生成windows方式。 打包成Windows可执行文件&#xff08;.exe&#xff09;&#xff0c;你可以使用以下三种方法&#xff1a; ### 方法1&#xff1a;使用Inno Setup 1. **构建JavaFX应用程序**&#xff1a; 使用M…

大模型企业落地:制造业可以选择的应用场景

前言 在当今制造业快速发展的背景下&#xff0c;设备稳定运行对于企业的发展至关重要。然而&#xff0c;传统的设备维修模式已无法满足现代企业的需求。为此&#xff0c;引入智能化、数字化的设备维修解决方案成为必然趋势。本文将探讨如何利用大模型技术&#xff0c;构建企业…

Netflix 机器学习科学家的提示词优化经验分享

编者按&#xff1a; 如何充分发挥大模型的潜能&#xff0c;用好大模型&#xff0c;关键在于如何优化向它们发送的提示词&#xff08;prompt&#xff09;&#xff0c;是为提示词工程&#xff08;prompt engineering&#xff09;。 本文Netflix 机器学习科学家Cameron R. Wolfe的…

FPGA - 全局时钟资源

全局时钟资源是指FPGA内部为实现系统时钟到达FPGA内部各 CLB、IOB&#xff0c;以及BSRAM&#xff08;Block Select RAM&#xff0c;选择性BRAM&#xff09;等基本逻辑单元的延时和抖动最小化&#xff0c;采用全铜层工艺设计和实现的专用缓冲与驱动结构。 由于全局时钟资源的布线…

HAL库开发--串口

知不足而奋进 望远山而前行 目录 文章目录 前言 学习目标 学习内容 开发流程 串口功能配置 串口功能开启 串口中断配置 串口参数配置 查询配置结果 发送功能测试 中断接收功能测试 printf配置 DMA收发 配置 DMA发送 DMA接收(方式1) DMA接收(方式2) 总结 前言…

简单了解MySql以及一些简单的应用MySql

MySql基础篇 1、数据模型概述 关系型数据库 概念&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护使用SQL语言操作&#xff0c;标准统一&#xff0c;使用方便 数…