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证】考试题及四川省安全员…

算法训练 | 动态规划Part2 | 62.不同路径、63.不同路径 II

目录 62.不同路径 动态规划法 63. 不同路径 II 动态规划法 62.不同路径 题目链接&#xff1a;62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 动态规划法 解题思路 机器人从(0 , 0) 位置出发&#xff0c;到(m - 1, n - 1)终点。…

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

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

赶紧收藏!2024 年最常见 20道并发编程面试题(七)

上一篇地址&#xff1a;赶紧收藏&#xff01;2024 年最常见 20道并发编程面试题&#xff08;六&#xff09;-CSDN博客 十三、什么是线程局部存储&#xff08;Thread-Local Storage&#xff09;&#xff1f; 线程局部存储&#xff08;Thread-Local Storage&#xff0c;简称TLS…

[渗透测试学习] IClean-HackTheBox

IClean-HackTheBox 信息搜集 nmap扫描一下 nmap -sV -v 10.10.11.12 -Pn扫描结果 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.52 ((Ubuntu)) Service Info:…

Ubuntu 22.04,把软件更新源更改为阿里或者清华镜像

通常这应该是装上系统后做的第一件事&#xff0c;阿里/清华二选一&#xff0c;本人亲测&#xff0c;可以成功 可以通过修改系统的/etc/apt/sources.list文件来完成。以下是详细步骤&#xff1a; 修改/etc/apt/sources.list 打开终端。 备份当前的/etc/apt/sources.list文件&…

WPF第三方开源UI框架:打造独特体验的魔法师

引言 在WPF&#xff08;Windows Presentation Foundation&#xff09;的世界中&#xff0c;除了微软提供的原生控件&#xff0c;还有许多第三方开源UI框架为开发者提供了更广阔的天地。这些框架以其创新和灵活性&#xff0c;帮助开发者打造出与众不同的用户体验。本文将带您走进…

HashMap 源码解析

1. 基本结构 HashMap 的核心是一个数组&#xff0c;每个数组元素是一个链表或红黑树&#xff08;JDK 1.8 及以后&#xff09;。当哈希冲突发生时&#xff0c;链表或红黑树用于存储多个键值对。 // HashMap的基本结构 public class HashMap<K, V> extends AbstractMap&l…

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

基础排序算法详解与对比分析

排序算法是计算机科学中最基础和重要的算法之一。本文将详细介绍几种经典的排序算法&#xff0c;包括选择排序、插入排序、希尔排序、堆排序和归并排序&#xff0c;并进行代码实现和对比分析。 选择排序&#xff08;Selection Sort&#xff09; 选择排序的基本思想是每次从未…

Ubuntu修改MySQL的tmpdir参数失败的解决方法

问题 在查询大表时&#xff0c;MySQL提示ERROR 3 (HY000): Error writing file /tmp/MYfd268 (OS errno 28 - No space&#xff0c;应该是临时文件夹/tmp没有足够的空间&#xff1b;想修改文件夹/etc/mysql/my.cnf中的参数tmpdir命令改变临时文件夹&#xff1b; sudo nano /e…

《青少年编程与数学》课程方案:3、课程形式

《青少年编程与数学》课程方案&#xff1a;3、课程形式 一、这门课程是一门学习课程&#xff0c;不是教学课程二、这门课程是一门独立的课程&#xff0c;不是多门课程三、这门课程有一条主要的线索是计算四、这门课程需要重视输出、强调可见性五、这门课程需要灵活运用&#xf…

C/C++函数指针、C#委托是什么?

函数指针 #include<stdio.h>//声明函数指针 typedef int(*Calc)(int a, int b); int Add(int a, int b) {return a b; } int Sub(int a, int b) {return a - b; }int main() {Calc funcPoint1 &Add;Calc funcPoint2 &Sub;int x 120;int y 140;int z 0;z …

Docker 部署 RocketMQ

0. 拉取镜像 docker pull apache/rocketmq:5.2.0 docker pull styletang/rocketmq-console-ng1. 创建容器共享网络 docker network create rocketmq2. 启动NameServer # 启动 NameServer docker run -d --name rmqnamesrv -p 9876:9876 --network rocketmq apache/rocketmq:…