NIO 非阻塞式IO

这篇具有很好参考价值的文章主要介绍了NIO 非阻塞式IO。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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 非阻塞式IO,nio

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介绍》文章来源地址https://www.toymoban.com/news/detail-639070.html

到了这里,关于NIO 非阻塞式IO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JAVA基础】- 同步非阻塞模式NIO详解

    NIO(Non-Blocking IO)是同步非阻塞方式来处理IO数据。服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到选择器上,选择器轮询到连接有IO请求时才启动一个线程进行处理。 同步(synchronous) :调用方式指应用(Application),调用方发起有一个功能调用时,在

    2024年02月13日
    浏览(30)
  • 深入理解网络非阻塞 I/O:NIO

    🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者 📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代 🌲文章所在专栏:网络 I/O 🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识 💬 向我询问任何您想

    2024年02月05日
    浏览(26)
  • IO、NIO、IO多路复用

    IO是什么? 网络IO是如何连接的? 下面是一次网络读取内容的I/O示意图,数据先从外设(网卡)到内核空间,再到用户空间(JVM),最后到应用程序的一个过程。 上述一次I/O读取,所谓的阻塞和非阻塞体现在哪里呢? Java最早期的版本的I/O就是这样实现的。当程序调用到读取

    2024年01月20日
    浏览(32)
  • 为什么Tomcat的NIO在读取body时要模拟阻塞?

    文章首发地址 Tomcat的NIO完全可以以非阻塞方式处理IO,为什么在读取body部分时要模拟阻塞呢?在Tomcat的NIO读取HTTP请求时,为了保证请求的正确性和可靠性,需要模拟阻塞模式,这是因为servlet规范里定义了ServletInputStream在读数据时是阻塞模式。 ServletInputStream在读取数据时是阻

    2024年02月13日
    浏览(45)
  • BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程

    上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题

    2024年02月14日
    浏览(35)
  • java-IO&NIO

    1.1. 阻塞 IO 模型   最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象。当用户线程发出 IO 请求之后,内 核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程

    2024年02月12日
    浏览(30)
  • Java 中的 IO 和 NIO

    Java IO(Input/Output)流是用于处理输入和输出数据的机制。它提供了一种标准化的方式来读取和写入数据,可以与文件、网络、标准输入输出等进行交互。 Java IO流主要分为两个流模型:字节流(Byte Stream)和字符流(Character Stream)。 字节流(Byte Stream) InputStream:字节输入流

    2024年02月10日
    浏览(30)
  • IO/NIO交互模拟及渐进式实现

    2024年02月03日
    浏览(45)
  • 什么是Java中的NIO(New IO)?

    首先,NIO是什么?NIO全称是Java NIO(New IO),它是一种新的I/O模型,相对于传统的I/O模型,NIO具有更高的性能和更低的延迟。在NIO中,我们使用通道(Channels)和缓冲区(Buffers)来代替传统的输入输出流(InputStream/OutputStream)。 接下来,我们来举个例子。假设我们要读取一个

    2024年02月14日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包