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>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {log.debug("read completed...{}", result);buffer.flip();debug(buffer);}@Overridepublic 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();// 获取所有发生的事件的 SelectionKeySet<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。避免了数据在内核和用户空间之间的复制。

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

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

相关文章

Shiro有key但无回显利用链子-JRMP大法

前言 shiro在手天下我有&#xff0c;扫出key直接梭哈getshell&#xff0c;横扫内网。但要是像这种情况&#xff0c;直接下班拜拜跑路&#xff0c;没有链子玩毛线… 直到出现了这么一个工具可以通过JRMP协议探测是否存在漏洞&#xff0c;很显然上面工具是做不到的&#xff0c;实…

Cheat Engine 学习

文章目录 Exact Value scanning任务实现步骤Unknown initial value任务实现步骤原理说明Floating points任务实现步骤原理说明Code finder任务实现步骤原理说明Pointers任务实现步骤原理说明Change Pointer 操作:Active(活跃状态)和数值修改:Code Injection任务概述实现步骤…

2024年【四川省安全员C证】考试题及四川省安全员C证考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 四川省安全员C证考试题是安全生产模拟考试一点通总题库中生成的一套四川省安全员C证考试资料&#xff0c;安全生产模拟考试一点通上四川省安全员C证作业手机同步练习。2024年【四川省安全员C证】考试题及四川省安全员…

博客没人看啊?我分析是这些原因

1.封面 主题封面还是个性化封面&#xff1f;主题封面对系列化很友好&#xff0c;如下图左&#xff1a; 在目录中什么主题一目了然&#xff0c;个性化封面在目录中就略显杂乱。但是通过观察CSDN主页发现热榜文章清一色个性化封面。如果使文字封面就会显得很无聊。 所以从提高浏…

Jenkins三种构建类型

目录 传送门前言一、概念二、前置处理&#xff08;必做&#xff09;1、赋予777权限2、让jenkins用户拥有root用户的kill权限3、要运行jar包端口号需要大于1024 三、自由风格软件项目&#xff08;FreeStyle Project&#xff09;&#xff08;推荐&#xff09;三、Maven项目&#…

金融科技:推动保险行业数字化转型的引擎

随着科技的飞速发展&#xff0c;金融科技&#xff08;FinTech&#xff09;已经成为推动金融行业变革的重要力量。特别是在保险行业&#xff0c;金融科技正引领着一场深刻的数字化转型&#xff0c;为保险公司带来了前所未有的机遇与挑战。本文将探讨金融科技如何推动保险行业的数…

UDP 协议详解与实战

目录 简介什么是 UDP&#xff1f;UDP 与 TCP 的区别 UDP 数据传输方式单播 - Unicast&#xff08;1:1&#xff09;广播 - Broadcast&#xff08;1:n&#xff09;有限广播 - Limited Broadcast直接广播 - Directed Broadcast 组/多播 - Multicast&#xff08;n:m&#xff09;任播…

屹晶微EG3002 单通道功率MOSFET驱动芯片 贴片SOP8

EG3002作为一款功率MOSFET驱动芯片&#xff0c;它的应用领域主要取决于其技术参数和性能特点。根据之前提供的信息&#xff0c;EG3002可能适用于以下领域&#xff1a; 1. 电源管理&#xff1a;用于高效率电源转换器&#xff0c;如开关电源&#xff08;SMPS&#xff09;、电池充…

栈的实现详解

目录 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;一种特殊的…

【YashanDB知识库】PHP使用OCI接口使用数据库绑定参数功能异常

【问题分类】驱动使用 【关键字】OCI、驱动使用、PHP 【问题描述】 PHP使用OCI8连接yashan数据库&#xff0c;使用绑定参数获取数据时&#xff0c;出现报错 如果使用PDO_OCI接口连接数据库&#xff0c;未弹出异常&#xff0c;但是无法正确获取数据 【问题原因分析】 开启O…

张艺兴step新专开启自由驾驶新纪元

张艺兴《Step》新专&#xff0c;开启自由驾驶新纪元&#xff01;当音乐与驾驶相遇&#xff0c;会碰撞出怎样的火花&#xff1f;当实力派艺人张艺兴遇上全新英文专辑《Step》&#xff0c;便为我们解锁了一种前所未有的出行体验&#xff01;这不仅仅是一张音乐专辑&#xff0c;更…

Pandas AI:最棒的大模型数据分析神器!

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑&…

Java Opencv识别图片上的虫子

最近有个需求&#xff0c;希望识别图片上的虫子&#xff0c;对于java来说&#xff0c;图像识别不是很好做。在网上也搜索了很多&#xff0c;很多的代码都是不完整&#xff0c;或者下载下载报错&#xff0c;有的写的很长看不懂。所以自己试着用java的opencv写了一段代码。发现识…

Django+Vue.js怎么实现搜索功能

一.前言 类似这样的搜索功能 二.前端代码 <div class"form-container"><div class"form-group"><label for"departure-city">出发城市</label><select v-model"departureCity" id"departure-city&q…

把Vue项目从Window系统迁移到Mac系统的方案

不能启动vue ui 直接运行&#xff0c;会报错如下&#xff1a; failed to load config from /Users/xiaochen/IdeaProjects/ChatViewer-frontend/vite.config.tserror when starting dev server: Error: You installed esbuild for another platform than the one youre curre…

C++:STL容器-->set

使用set容器时需要导入头文件&#xff1a;#include <set> set和multiset区别&#xff1a; set不允许容器中有重复的元素 multiset允许容器中有重复的元素 1. 构造函数 set<T> st; set s(const &st); void printSet(set<int>& s) {for (set<int>…

Integer溢出问题

0. 背景 在刷 LeetCode 时&#xff0c;代码的执行结果与预期出现了偏差&#xff0c;原因是 Int 值超过了允许范围 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1 ] [−231,231−1]。工作中从来没有遇到过这种情况&#xff0c;之前的认知是如果 Int 中存储的值超过了允许范围也许…

【FreeRTOS】ARM架构汇编实例

目录 ARM架构简明教程1. ARM架构电脑的组成1.2 RISC1.2 提出问题1.3 CPU内部寄存器1.4 汇编指令 2. C函数的反汇编 学习视频 【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS&#xff08;FreeRTOS教程 基于STM32&#xff0c;以实际项目为导向&#xff09;】 https://www.…

Unity制作背包的格子

1.新建一个面板 2.点击面板并添加这个组件 3.点击UI创建一个原始图像&#xff0c;这样我们就会发现图像出现在了面板的左上角。 4.多复制几个并改变 Grid Layout Group的参数就可以实现下面的效果了

GraogGNSSLib学习

GraogGNSSLib学习 程序编译环境版本项目编译结果问题 程序编译 GraphGNSSLib 环境版本 程序开源是在ubuntu16.04-kinetic环境跑通的&#xff0c;但是我的环境是UBUNTU20.04&#xff0c;所以&#xff0c;先进行了ROS的安装&#xff0c;因为我的系统是ubuntu20.04所以&#xf…