NIO 非阻塞式IO

news2024/11/25 22:54:08

NIO

Java NIO 基本介绍

  1. Java NIO 全称 Java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 NewIO),是同步非阻塞的。
  2. NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
  3. NIO 有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)
  4. NIO面向缓冲区,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
  5. Java NIO 的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
  6. 通俗理解:NIO 是可以做到用一个线程来处理多个操作的。假设有 10000 个请求过来,根据实际情况,可以分配 50 或者 100 个线程来处理。不像之前的阻塞 IO 那样,非得分配 10000 个。
  7. HTTP 2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP 1.1 大了好几个数量级。

NIO 三大核心原理示意图

一张图描述 NIOSelectorChannelBuffer 的关系。

  1. 每个 Channel 都会对应一个 Buffer
  2. Selector 对应一个线程,一个线程对应多个 Channel(连接)。
  3. 该图反应了有三个 Channel 注册到该 Selector //程序
  4. 程序切换到哪个 Channel 是由事件决定的,Event 就是一个重要的概念。
  5. Selector 会根据不同的事件,在各个通道上切换。
  6. Buffer 就是一个内存块,底层是有一个数组。
  7. 数据的读取写入是通过 Buffer,这个和 BIO是不同的,BIO 中要么是输入流,或者是输出流,不能双向,但是 NIO 的 Buffer 是可以读也可以写,需要 flip 方法切换 Channel 是双向的,可以返回底层操作系统的情况,比如 Linux,底层的操作系统通道就是双向的。在这里插入图片描述

NIO 和 BIO 的比较

  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多。

  2. BIO 是阻塞的,NIO 则是非阻塞的。

  3. BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

  4. Buffer和Channel之间的数据流向是双向的

缓冲区(Buffer)

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个**容器对象(含数组)**该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer

通道(Channel)

NIO 的通道类似于流,但有些区别如下:

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲读数据,也可以写数据到缓冲:
  1. BIO 中的 Stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)是双向的,可以读操作,也可以写操作。
  2. ChannelNIO 中是一个接口 public interface Channel extends Closeable{}
  3. 常用的 Channel 类有:FileChannelDatagramChannelServerSocketChannelSocketChannel。【ServerSocketChanne 类似 ServerSocketSocketChannel 类似 Socket
  4. FileChannel 用于文件的数据读写,DatagramChannel 用于 UDP 的数据读写,ServerSocketChannelSocketChannel 用于 TCP 的数据读写。

NIO 还支持通过多个 Buffer(即 Buffer数组)完成读写操作,即 ScatteringGathering【举例说明】

/**
 * @Author:jiangdw7
 * @date: 2023/8/9 10:04
 */
public class ScatteringAndGatheringTest {

    public static void main(String[] args) throws Exception {

        //使用 ServerSocketChannel 和 SocketChannel 网络
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        //绑定端口到 socket,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);

        //创建 buffer 数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        //等客户端连接 (telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();

        int messageLength = 8; //假定从客户端接收 8 个字节

        //循环的读取
        while (true) {
            int byteRead = 0;

            while (byteRead < messageLength) {
                long l = socketChannel.read(byteBuffers);
                byteRead += l; //累计读取的字节数
                System.out.println("byteRead = " + byteRead);
                //使用流打印,看看当前的这个 buffer 的 position 和 limit
                Arrays.asList(byteBuffers).stream().map(buffer -> "position = " + buffer.position() + ", limit = " + buffer.limit()).forEach(System.out::println);
            }

            //将所有的 buffer 进行 flip
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
            //将数据读出显示到客户端
            long byteWirte = 0;
            while (byteWirte < messageLength) {
                long l = socketChannel.write(byteBuffers);
                byteWirte += l;
            }
            //将所有的buffer进行clear
            Arrays.asList(byteBuffers).forEach(buffer -> {
                buffer.clear();
            });
            System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWirte + ", messagelength = " + messageLength);
        }
    }
}

Selector(选择器)

  1. JavaNIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)。
  2. Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个 Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
  3. 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程。
  4. 避免了多线程之间的上下文切换导致的开销。

注意事项

  1. NIO 中的 ServerSocketChannel 功能类似 ServerSocketSocketChannel 功能类似 Socket
  2. Selector 相关方法说明
    • selector.select(); //阻塞
    • selector.select(1000); //阻塞 1000 毫秒,在 1000 毫秒后返回
    • selector.wakeup(); //唤醒 selector
    • selector.selectNow(); //不阻塞,立马返还
public class NIOClient {
   private static Selector selector;

   public static void main(String[] args) throws Exception {
       selector = Selector.open();
       SocketChannel sc = SocketChannel.open();
       sc.configureBlocking(false);
       sc.connect(new InetSocketAddress("127.0.0.1", 8081));
       sc.register(selector, SelectionKey.OP_READ);

       ByteBuffer bf = ByteBuffer.allocate(1024);
       bf.put("Hi,server,i'm client".getBytes());


       if (sc.finishConnect()) {
           bf.flip();
           while (bf.hasRemaining()) {
               sc.write(bf);
           }

           while (sc.isConnected()) {
               selector.select();
               Iterator<SelectionKey> it = selector.selectedKeys().iterator();
               while (it.hasNext()) {
                   SelectionKey key = it.next();


                   if (key.isReadable()) {
                       ByteArrayOutputStream bos = new ByteArrayOutputStream();
                       bf.clear();
                       SocketChannel othersc = (SocketChannel) key.channel();
                       while (othersc.read(bf) > 0) {
                           bf.flip();
                           while (bf.hasRemaining()) {
                               bos.write(bf.get());
                           }
                           bf.clear();
                       }
                       System.out.println("服务端返回的数据:" + bos.toString());
                       Thread.sleep(5000);
                       sc.close();
                       System.out.println("客户端关闭...");
                   }
               }
               selector.selectedKeys().clear();
           }
       }
   }
}
public class NIOServer {
    private static Selector selector;
    private static ServerSocketChannel serverSocketChannel;
    private static ByteBuffer bf = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws Exception {
        init();
        while (true) {
            int select = selector.select(10000);
            if (select == 0) {
                System.out.println("等待连接10秒...");
                continue;
            }
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                if (key.isAcceptable()) {
                    System.out.println("连接准备就绪");
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    System.out.println("等待客户端连接中........................");
                    SocketChannel channel = server.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    System.out.println("读准备就绪,开始读.......................");
                    SocketChannel channel = (SocketChannel) key.channel();
                    System.out.println("客户端的数据如下:");

                    int readLen = 0;
                    bf.clear();
                    StringBuffer sb = new StringBuffer();
                    while ((readLen = channel.read(bf)) > 0) {
                        bf.flip();
                        byte[] temp = new byte[readLen];
                        bf.get(temp, 0, readLen);
                        sb.append(new String(temp));
                        bf.clear();
                    }
                    if (-1 == readLen) {
                        System.out.println(channel.hashCode()+"号客户端关闭。");
                        channel.close();
                    }else {
                        channel.write(ByteBuffer.wrap(("客户端,你传过来的数据是:" + sb.toString()).getBytes()));
                        System.out.println(sb.toString()+"132123");
                    }
                }
                it.remove();
            }
        }
    }

    private static void init() throws Exception {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8081));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }
}

参考连接

https://www.cnblogs.com/xdouby/p/8942083.html

https://www.zhihu.com/question/22524908

https://blog.csdn.net/ArtAndLife/article/details/121001656

AIO

  1. JDK7 引入了 AsynchronousI/O,即 AIO。在进行 I/O 编程中,常用到两种模式:ReactorProactorJavaNIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理

  2. AIONIO2.0,叫做异步不阻塞的 IOAIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用

  3. 目前 AIO 还没有广泛应用,Netty 也是基于 NIO,而不是 AIO,因此我们就不详解 AIO 了,有兴趣的同学可以参考《Java新一代网络编程模型AIO原理及Linux系统AIO介绍》

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

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

相关文章

AIGC 浪潮下,鹅厂新一代前端人的真实工作感受

点击链接了解详情 原创作者&#xff1a;张波 腾小云导读 AIGC 这一时代潮流已然不可阻挡&#xff0c;我们要做的不是慌乱&#xff0c;而是把握住这个时代的机会。本文就和大家一起来探索在 AIGC 下&#xff0c;前端工程师即将面临的挑战和机遇。聊聊从以前到现在&#xff0c;A…

诸神之战:数字时代的低代码服务商与代理商究竟谁更强?

随着数字化转型浪潮的推进&#xff0c;企业对数字化应用开发的需求迅速增长。低代码作为一种新的软件开发范式&#xff0c;以其可视化和快速构建应用的能力&#xff0c;被广泛应用于成千上万家企业中。当低代码行业的逐渐发展成熟&#xff0c;越来越多的人看到了低代码的商业价…

使用乐观锁解决超卖问题

目录 什么是超卖&#xff1f; 乐观锁和悲观锁的定义 悲观锁&#xff1a; 乐观锁&#xff1a; 乐观锁的实现方式 1.版本号 2.CAS法 什么是超卖&#xff1f; 举个例子&#xff1a;订单系统中&#xff0c;用户在执行下单操作时&#xff0c;可能同一时间有无数个用户同时下单&…

平替版Airtag

Airtag是什么&#xff1f; AirTag是苹果公司设计的一款定位神奇&#xff0c;它通过一款纽扣电池进行供电&#xff0c;即可实现长达1-2年的关键物品的定位、查找的功能。 按照苹果公司自己的话说—— 您“丢三落四这门绝技&#xff0c;要‍失‍传‍了”。 AirTag 可帮你轻松追…

USB(二):Type-C

一、引脚定义 Type-C口有 4对TX/RX差分线&#xff0c;2对USB D/D-&#xff0c;1对SBU&#xff0c;2个CC&#xff0c;4个VBUS和4个地线Type-C母座视图&#xff1a; Type-C公头视图&#xff1a; 二、关键名词 DFP(Downstream Facing Port)&#xff1a; 下行端口&#xff0c…

【云原生】Pod的进阶

目录 一、资源限制二、重启策略三、健康检查 &#xff0c;又称为探针&#xff08;Probe&#xff09;3.1示例1&#xff1a;exec方式3.2示例2&#xff1a;httpGet方式3.3示例3&#xff1a;tcpSocket方式3.4示例4&#xff1a;就绪检测3.5示例5&#xff1a;就绪检测2 四、启动、退出…

设置VsCode 将打开的多个文件分行(栏)排列,实现全部显示

目录 1. 前言 2. 设置VsCode 多文件分行(栏)排列显示 1. 前言 主流编程IDE几乎都有排列切换选择所要查看的文件功能&#xff0c;如下为Visual Studio 2022的该功能界面&#xff1a; 图 1 图 2 当在Visual Studio 2022打开很多文件时&#xff0c;可以按照图1、图2所示找到自…

价格监测与数据分析的关系

所谓的价格监测&#xff0c;其实可以理解为是低价数据的监测&#xff0c;当监测价格时&#xff0c;其他页面上的商品数据也会被同时采集监测&#xff0c;如标题、库存、销量、评价等内容&#xff0c;所以品牌在做电商价格监测时&#xff0c;其实也可以对数据进行分析。 力维网络…

【React学习】—jsx语法规则(三)

【React学习】—jsx语法规则&#xff08;三&#xff09; 一、jsx语法规则&#xff1a; 1、定义虚拟DOM&#xff0c;不要写引号&#xff0c; 2、标签中混入JS表达式要用{} 3、样式的类名指定不要用class&#xff0c;要用className 4、内联样式&#xff0c;要用style{{key:value}…

linux环形缓冲区kfifo实践2:配合等待队列使用

基础 struct __wait_queue_head {spinlock_t lock;struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t; 初始化等待队列&#xff1a;init_waitqueue_head 深挖init_waitqueue_head宏的定义可知&#xff0c;传递给它的参数q是一个wait_queu…

pytest 编写规范

一、pytest 编写规范 1、介绍 pytest是一个非常成熟的全功能的Python测试框架&#xff0c;主要特点有以下几点&#xff1a; 1、简单灵活&#xff0c;容易上手&#xff0c;文档丰富&#xff1b;2、支持参数化&#xff0c;可以细粒度地控制要测试的测试用例&#xff1b;3、能够…

分享之python 协程

线程和进程的操作是由程序触发系统接口&#xff0c;最后的执行者是系统&#xff1b;协程的操作则是程序员。 协程存在的意义&#xff1a;对于多线程应用&#xff0c;CPU通过切片的方式来切换线程间的执行&#xff0c;线程切换时需要耗时&#xff08;保存状态&#xff0c;下次继…

Redux中reducer 中为什么每次都要返回新的state!!!

Redux中reducer 中为什么每次都要返回新的state&#xff01;&#xff01;&#xff01; 最近在学习react相关的知识&#xff0c;学习redux的时候遇到看到一个面试题&#xff1a; 如果Redux没返回新的数据会怎样&#xff1f; 这就是要去纠结为什么编写reducer得时候为什么不允许直…

LT8711HE 是一款高性能的Type-C/DP1.2到HDMI2.0转换器

LT8711HE 1.描述 LT8711HE是一种高性能的Type-C/DP1.2到HDMI2.0转换器&#xff0c;设计用于连接USB Type-C源或DP1.2源到HDMI2.0接收器。LT8711HE集成了一个DP1.2兼容的接收器&#xff0c;和一个HDMI2.0兼容的发射机。此外&#xff0c;还包括两个CC控制器&#xff0c;用于CC通…

Linux Maven 安装与配置

目录 Maven 下载 解压缩下载的文件 移动Maven文件夹 配置环境变量 验证安装 注意 Maven 下载 官方地址 Maven – Download Apache Maven&#xff0c;下载完成后&#xff0c;解压到合适的位置即可&#xff1b; 解压缩下载的文件 解压缩下载的文件&#xff1a; 使用以下命…

Malloc动态内存分配

在C语言中我们会使用malloc来动态地分配内存&#xff0c;这样做的一个主要理由是有些数据结构的大小只有在运行时才能确定。例如&#xff0c;如果你正在编写一个程序&#xff0c;需要用户输入一些数据&#xff0c;但你不知道用户会输入多少数据&#xff0c;那么你就需要使用动态…

VGPU理解与实践包含虚拟机显卡直通,k8s安装,GPU-manager使用与实践测试

提示&#xff1a;文章分为三部分&#xff1a;物理GPU绑定虚拟机、k8s安装、gpu-manager虚拟化实现与测试 文章目录 前言一、什么是VGPU&#xff1f;二、此文件会拆分成三部分&#xff1a;1.物理机显卡直通虚拟机2.安装K8S3.安装GPU-manager、测试全流程 总结 前言 用户角度GPU…

【Linux】HTTPS协议——应用层

1 HTTPS是什么&#xff1f; HTTPS也是⼀个应⽤层协议.是在 HTTP 协议的基础上引⼊了⼀个加密层. HTTP 协议内容都是按照⽂本的⽅式明⽂传输的. 这就导致在传输过程中出现⼀些被篡改的情况. HTTP VS HTTPS 早期很多公司刚起步的时候&#xff0c;使用的应用层协议都是HTTP&am…

7.7 通俗易懂详解稠密连接网络DenseNet 手撕稠密连接网络DenseNet

一.思想 与ResNet的区别 DenseNet这样拼接有什么好处&#xff1f;DenseNet优点 对于每一层&#xff0c;使用前面所有层的特征映射作为输入&#xff0c;并且其自身的特征映射作为所有后续层的输入。 DenseNet的优点: 缓解了消失梯度问题&#xff0c;加强了特征传播&#xff0c…

在java集合HashMap中如何替换某一个键值

replace() 方法替换 hashMap 中是指定的 key 对应的 value。 replace() 方法的语法为&#xff1a; hashmap.replace(K key, V newValue) 或 hashmap.replace(K key, V oldValue, V newValue)示例代码如下&#xff08;把hashmap集合中的值为USA 的记录替换 成“US”&#xff0…