[java 和linux ]: NIO ,AIO ,BIO

126 阅读6分钟

java: NIO ,AIO ,BIO

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

nio_select_model.png

Channel通道

简介

  1. NIO channel 通道类似于流,但是有区别:

(1) 通道可以同时读或者写,而流只能读或者只能写

(2)通道可以实现异步读写数据

(3)通道可以从缓冲区读数据,也可以写数据到缓冲区

Channel 在NIO中是一个接口,下面有很多子接口和主要实现。主要实现有:

  • FileChannel 注意用于文件的数据读写,
  • DatagramChannel 用于UDP的数据读写,
  • ServerSocketChannel SocketChannel 用于TCP的数据读写。

ByteBuffer 主要函数

  • allocate(int capacity):静态方法,分配一个指定容量的新字节缓冲区。
  • clear():清空缓冲区,将位置设置为 0,限制设置为容量,用于准备写入数据。
  • flip():将缓冲区从写模式切换为读模式,将限制设置为当前位置,将位置重置为 0,用于准备读取数据。
  • rewind():将位置设置为 0,保持限制不变,用于重读缓冲区中的数据。
  • compact():将缓冲区中未读取的数据复制到缓冲区的开始位置,将位置设置为复制后的位置,限制设置为容量,用于继续写入数据。
  • put(byte b):将一个字节写入缓冲区的当前位置,位置递增。
  • put(byte[] src):将字节数组写入缓冲区的当前位置,位置递增。
  • put(ByteBuffer src):将另一个字节缓冲区的内容写入当前缓冲区,位置递增。
  • get():从缓冲区的当前位置读取一个字节,位置递增。
  • get(byte[] dst):从缓冲区的当前位置读取字节到指定的字节数组,位置递增。
  • get(ByteBuffer dst):从缓冲区的当前位置读取字节到另一个字节缓冲区,位置递增。
  • remaining():返回当前位置与限制之间的字节数量。
  • hasRemaining():检查是否还有未读取的字节。
  • mark():在当前位置设置一个标记。
  • reset():将位置重置为之前设置的标记位置。
  • markSupported():检查缓冲区是否支持标记和重置操作。

小结

  • BIO(Blocking I/O):采用阻塞式 I/O 模型,线程在执行 I/O 操作时被阻塞,无法处理其他任务,适用于连接数较少且稳定的场景。

  • NIO(New I/O 或 Non-blocking I/O):使用非阻塞 I/O 模型,线程在等待 I/O 时可执行其他任务,通过 Selector 监控多个 Channel 上的事件,提高性能和可伸缩性,适用于高并发场景。

  • AIO(Asynchronous I/O):采用异步 I/O 模型,线程发起 I/O 请求后立即返回,当 I/O 操作完成时通过回调函数通知线程,进一步提高了并发处理能力,适用于高吞吐量场景。

    • 用CompletionHandler 回调接收事件完成;

java中异步IO 如何获取线程处理的结果呢?

Asynchronous IO : 异步IO;

异步IO的一个常见模型是使用回调(Callback)来处理操作完成的通知和结果。

如:CompletionHandler




在 Linux 下,有三种主要的 I/O 模型:Blocking I/O (BIO),Non-blocking I/O (NIO),和 Asynchronous I/O (AIO)。这些模型代表了不同的方式来处理输入输出操作。

  1. Blocking I/O (BIO) :

    • 在 Blocking I/O 模型中,当应用程序发起一个 I/O 操作时,操作系统会阻塞应用程序直到操作完成。这意味着应用程序会一直等待数据准备好或者操作完成,无法做其他事情。
    • 在 Linux 下,传统的文件 I/O 操作 (read() 和 write()) 就是阻塞的。
  2. Non-blocking I/O (NIO) :

    • Non-blocking I/O 模型允许应用程序在发起 I/O 操作后继续执行其他任务,而不必等待操作完成。
    • 在 Linux 下,可以通过设置文件描述符为非阻塞模式,使用 fcntl() 函数或 ioctl() 函数来实现非阻塞 I/O。应用程序可以通过轮询或者使用 select()poll()epoll() 等函数来检查文件描述符的状态,以确定何时可以进行读写操作。
  3. Asynchronous I/O (AIO) :

    • Asynchronous I/O 模型更进一步,允许应用程序发起 I/O 操作后继续执行其他任务,并在操作完成后得到通知。
    • Linux 提供了 AIO 支持,应用程序可以使用 aio_read() 和 aio_write() 等函数来发起异步 I/O 操作,然后可以通过轮询或者回调函数等机制来处理 I/O 完成的通知。

epoll 实现 NIO

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

int main() {
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1) {
        perror("epoll_create1");
        return 1;
    }

    // 添加文件描述符到 epoll 实例中
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = STDIN_FILENO;  // 监视标准输入
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &event) == -1) {
        perror("epoll_ctl");
        return 1;
    }

    struct epoll_event events[MAX_EVENTS];

    while (1) {
        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); // 阻塞等待事件发生
        if (num_events == -1) {
            perror("epoll_wait");
            return 1;
        }

        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == STDIN_FILENO) {
                char buf[256];
                int bytes_read = read(STDIN_FILENO, buf, sizeof(buf));
                if (bytes_read == -1) {
                    perror("read");
                    return 1;
                }
                printf("Read from stdin: %.*s\n", bytes_read, buf);
            }
        }
    }

    close(epoll_fd);

    return 0;
}

aio_read() :的异步回调

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <aio.h>
#include <unistd.h>
#include <errno.h>

void aio_completion_handler(sigval_t sigval) {
    struct aiocb *cb = (struct aiocb *)sigval.sival_ptr;

    if (aio_error(cb) == 0) {
        // 读取操作完成,处理数据
        printf("Async read completed successfully. Data read: %s\n", (char *)cb->aio_buf);
    } else {
        // 读取操作出错
        printf("Async read failed with error: %d, %s\n", aio_error(cb), strerror(aio_error(cb)));
    }

    free(cb->aio_buf);
    free(cb);
}

int main() {
    int fd = open("example.txt", O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    struct aiocb *cb = (struct aiocb *)malloc(sizeof(struct aiocb));
    memset(cb, 0, sizeof(struct aiocb));

    cb->aio_fildes = fd;
    cb->aio_buf = malloc(1024); // 分配缓冲区
    cb->aio_nbytes = 1024;
    cb->aio_sigevent.sigev_notify = SIGEV_THREAD;
    cb->aio_sigevent.sigev_notify_function = aio_completion_handler;
    cb->aio_sigevent.sigev_notify_attributes = NULL;
    cb->aio_sigevent.sigev_value.sival_ptr = cb;

    // 发起异步读取操作
    if (aio_read(cb) == -1) {
        perror("aio_read");
        return 1;
    }

    // 在这里可以继续执行其他操作

    // 等待异步操作完成
    while (aio_error(cb) == EINPROGRESS) {
        // 可以做一些其他工作
    }

    close(fd);

    return 0;
}

参考