当前位置:   article > 正文

快速入门java网络编程基础------Nio

快速入门java网络编程基础------Nio

一. NIO 基础

哔哩哔哩黑马程序员 netty实战视频

0.什么是nio?

NIO(New I/O)是Java中提供的一种基于通道和缓冲区的I/O(Input/Output)模型。它是相对于传统的IO(InputStream和OutputStream)模型而言的新型I/O模型。NIO的主要特点包括:

1.通道与缓冲区:

2.NIO引入了通道(Channel)和缓冲区(Buffer)的概念。通道是对传统IO中的流的抽象,它可以支持双向数据传输。而缓冲区则是存储数据的地方,数据在通道和缓冲区之间传递。

3.非阻塞IO:

4.NIO提供了非阻塞I/O操作的能力。在传统IO模型中,当一个线程在读取或写入数据时会被阻塞,而在NIO中,线程可以继续处理其他任务,而不必等待数据的读取或写入完成。

5.选择器(Selector):

6.NIO引入了选择器的概念,使得一个线程可以同时监控多个通道的IO事件。这样,一个线程可以有效地管理多个通道,从而提高系统的性能。

7.面向缓冲区的操作:

8.NIO中的数据读取和写入都是面向缓冲区的。数据首先被读取到缓冲区,然后再从缓冲区写入到通道,或者从通道读取到缓冲区。这种方式相对于直接流式IO更加灵活,可以更好地处理不同大小的数据块。

9.异步IO:

10.Java NIO提供了异步I/O操作的支持。通过使用Future、Callback等机制,可以实现异步的IO操作,使得程序可以在数据读取或写入的同时执行其他任务。

11.多路复用:

12.多路复用是NIO的一个重要特性,通过选择器可以实现同时管理多个通道的IO操作。这在高并发的网络应用中特别有用。

1. 三大组件

1.1 Channel & Buffer

channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层

channel
buffer

常见的 Channel 有

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

buffer 则用来缓冲读写数据,常见的 buffer 有

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

1.2 Selector

selector 单从字面意思不好理解,需要结合服务器的设计演化来理解它的用途

多线程版设计
多线程版
socket1
thread
socket2
thread
socket3
thread
⚠️ 多线程版缺点
  • 内存占用高
  • 线程上下文切换成本高 #cpu的线程是固定的,如果无限制增加线程处理请求会导致线程堵塞
  • 只适合连接数少的场景
线程池版设计
线程池版
socket1
thread
socket2
thread
socket3
socket4
⚠️ 线程池版缺点
  • 阻塞模式下,线程仅能处理一个 socket 连接 //线程池中线程固定
  • 仅适合短连接场景 //http
selector 版设计

selector 的作用就是配合一个线程来管理多个 channel,获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下(不会因为单个线程的堵塞而失去效果),不会让线程吊死在一个 channel 上。适合连接数特别多,但流量低的场景(low traffic)

selector 版
selector
thread
channel
channel
channel

调用 selector 的 select() 会阻塞直到 channel 发生了读写就绪事件,这些事件发生,select 方法就会返回这些事件交给 thread 来处理

2. ByteBuffer

有一普通文本文件 data.txt,内容为

1234567890abcd
  • 1

使用 FileChannel 来读取文件内容

@Slf4j
public class ChannelDemo1 {
    public static void main(String[] args) {
        try (RandomAccessFile file = new RandomAccessFile("helloword/data.txt", "rw")) {
            FileChannel channel = file.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(10);
            do {
                // 向 buffer 写入
                int len = channel.read(buffer);
                log.debug("读到字节数:{}", len);
                if (len == -1) {
                    break;
                }
                // 切换 buffer 读模式
                buffer.flip();
                while(buffer.hasRemaining()) {
                    log.debug("{}", (char)buffer.get());
                }
                // 切换 buffer 写模式
                buffer.clear();
            } while (true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

输出

10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:10
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 1
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 2
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 3
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 4
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 5
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 6
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 7
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 8
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 9
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 0
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:4
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - a
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - b
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - c
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - d
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:-1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2.1 ByteBuffer 正确使用姿势

  1. 向 buffer 写入数据,例如调用 channel.read(buffer)

  2. 调用 flip() 切换至读模式

  3. 从 buffer 读取数据,例如调用 buffer.get()

  4. 调用 clear() 或 compact() 切换至写模式

  5. 重复 1~4 步骤

    flip()java.nio.ByteBuffer 类中的一个方法,它用于将缓冲区的当前读取位置设置为缓冲区的开始位置,并将缓冲区的大小调整为等于缓冲区剩余的字节数。

    在将缓冲区中的数据写入网络或文件之前,通常需要调用 flip() 方法,以便将缓冲区中的数据移动到读取位置。这样,下一次从缓冲区中读取数据时,就可以从缓冲区的开始位置开始读取

channelbuffer 分别是 java.nio.channels.Channeljava.nio.ByteBuffer 两个类。

Channel 类是 NIO 中的核心接口,它表示一个通信通道,如套接字、文件描述符等。它提供了一种将字节数据从一个地方读取到另一个地方的方法,可以用于网络编程、文件读写等场景。

ByteBuffer 类是 NIO 中的缓冲区类,它提供了一种高效的字节读写操作。它允许将字节数据从一个地方读取到另一个地方,同时提供了一些方法来改变当前读取和写入的位置,以及调整缓冲区的大小等。

channel.read(buffer) 语句表示从指定的 channel 中读取数据到 buffer 中。channel.read(buffer) 会尝试读取数据到 buffer 中,直到 buffer 中的字节数达到或超过 buffer.capacity() 或者读取到 EOF(表示读取到了流的末尾)。如果读取到了 EOF,则返回 -1,否则返回读取到的字节数。

这个语句的作用是将 channel 中的数据读取到 buffer 中,从而可以对读取到的数据进行处理。如果读取到了 EOF,说明已经读取到了流的末尾,可以关闭 channelbuffer,或者重新设置 buffer 的位置,以便读取下一部分数据。

2.2 ByteBuffer 结构

ByteBuffer 有以下重要属性

  • capacity
  • position
  • limit

一开始

在这里插入图片描述

写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态
在这里插入图片描述

flip 动作发生后,position 切换为读取位置,limit 切换为读取限制

在这里插入图片描述

读取 4 个字节后,状态
在这里插入图片描述

clear 动作发生后,状态
在这里插入图片描述

compact 方法,是把未读完的部分向前压缩,然后切换至写模式

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/636044
推荐阅读
相关标签