聊聊分布式架构05——[NIO基础]BIO到NIO的演进

news2025/1/15 20:50:29

目录

I/O

I/O模型

BIO示例

BIO与NIO比较

NIO的三大核心

NIO核心之缓冲区

Buffer常用子类:

Buffer常用API

Buffer中的重要概念

NIO核心之通道

FileChannel 类

FileChannel常用方法

NIO核心之选择器

概述

应用

NIO非阻塞原理分析

服务端流程

客户端流程

简单NIO示例

AIO简介

主要特点

主要组件和类

总结



I/O

I/O(Input/Output)是计算机科学和编程中的一个重要概念,指的是计算机程序与外部世界(通常是硬件设备、文件系统、网络、用户等)进行数据交换和通信的过程。I/O 可以包括从外部设备读取数据(输入)和将数据写入外部设备(输出)两个方面。以下是关于I/O的一些重要概念和说明:

  1. 输入(Input): 输入是指程序从外部获取数据的过程。这可以是从键盘、鼠标、传感器、文件、网络连接等获取数据。例如,用户通过键盘输入文本,程序从网络接收传感器数据,都属于输入操作。

  2. 输出(Output): 输出是指程序向外部设备发送数据的过程。这可以是将数据写入文件、向屏幕显示信息、通过网络传输数据等。例如,程序将计算结果写入文件,向用户显示图形界面,都属于输出操作。

  3. 流(Stream): 流是一种抽象概念,用于表示数据在程序和外部设备之间的有序、连续的传输。I/O流通常分为输入流和输出流。输入流用于从外部设备读取数据,输出流用于将数据发送到外部设备。

  4. 阻塞和非阻塞(Blocking and Non-blocking): 阻塞I/O是指当程序进行I/O操作时,如果没有数据可用或没有完成写入,程序将被阻塞,无法执行其他任务。非阻塞I/O允许程序在等待数据就绪时继续执行其他任务。

  5. 同步和异步(Synchronous and Asynchronous): 同步I/O是指程序在发起I/O操作后等待操作完成,然后继续执行后续任务。异步I/O允许程序发起I/O操作后继续执行其他任务,当操作完成时,程序会收到通知。

  6. 文件I/O: 文件I/O是指程序与文件系统进行交互的操作,包括读取文件内容、写入文件、创建文件、删除文件等。文件I/O通常用于数据的持久化和存储。

  7. 网络I/O: 网络I/O是指程序与其他计算机通过网络进行通信的操作,包括发送和接收数据、建立连接、断开连接等。网络I/O通常用于构建分布式应用程序。

I/O模型

不同的I/O模型使用不同的方法来处理输入和输出操作,具有不同的特性和适用场景。以下是常见的I/O模型:

  1. 阻塞I/O模型(Blocking I/O): 在阻塞I/O模型中,当应用程序发起一个I/O请求时,程序会被阻塞,直到操作完成或出错。这意味着程序需要等待,不能执行其他任务,直到I/O操作完成。阻塞I/O模型通常简单易用,但可能导致程序性能下降,特别是在高并发环境下。

  2. 非阻塞I/O模型(Non-blocking I/O): 非阻塞I/O模型允许应用程序在等待I/O操作完成时继续执行其他任务,而不会被阻塞。程序可以通过轮询或回调等方式来检查是否有数据可用或操作已完成。非阻塞I/O模型适用于需要高并发处理的场景,但编程复杂度较高。

  3. 多路复用I/O模型(Multiplexing I/O): 多路复用I/O模型使用了选择器(Selector)来监听多个I/O通道的状态,一旦某个通道准备好进行I/O操作,就会触发相应的事件,应用程序可以响应这些事件。这允许一个线程同时管理多个I/O通道,减少线程开销,提高并发性能。常见的多路复用I/O模型包括select、poll和epoll。

  4. 信号驱动I/O模型(Signal-driven I/O): 在信号驱动I/O模型中,应用程序通过注册信号处理程序来处理I/O事件。当I/O操作完成时,操作系统会向应用程序发送信号,应用程序可以捕获信号并处理事件。这种模型通常用于Unix系统。

  5. 异步I/O模型(Asynchronous I/O): 异步I/O模型允许应用程序发起I/O操作后继续执行其他任务,同时注册回调函数来处理I/O操作的完成。操作系统在I/O操作完成后调用回调函数通知应用程序。这种模型适用于需要高度并发和异步操作的场景。

BIO示例

BIO的简单流程

创建BIOServer

public class BIOServer {
    ServerSocket socketServer; // 服务端网络IO的封装对象
    // 构造服务器
    public BIOServer(int port) {
        try {
            socketServer = new ServerSocket(port);
            System.out.println("BIOServer start,Port :" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
​
    // 端口有了,需要监听连接
    public void listen() {
        try {
            // 调用accept()阻塞等待客户端连接
            Socket client = socketServer.accept();
            System.out.println("communication port:" + client.getPort());
​
            InputStream inputStream = client.getInputStream(); // 客户端连接传递的信息流
            BufferedReader clientIn = new BufferedReader(new InputStreamReader(inputStream)); // 通过流读取客户端输入
            PrintWriter serverOut = new PrintWriter(client.getOutputStream()); // 基于socket构造服务端输出对象用于和client发送消息
​
            // 服务端通过控制台输入向客户端通信发送消息,模拟TCP的全双工通信
            BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in)); // 通过流读取控制台输入
            String line = systemIn.readLine();
            while (!line.equals("bye")) { // 持续连接
                serverOut.println(line); // 向客户端输出消息
                serverOut.flush(); // 手动将缓冲区中的数据强制刷新到输出流中,以确保数据被立即写入底层的输出流
                System.out.println("receive client msg:" + clientIn.readLine());
                System.out.println("server send msg:" + line);
                line = systemIn.readLine();
            }
            inputStream.close();
            clientIn.close();
            serverOut.close();
            client.close();
            socketServer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
​
    public static void main(String[] args) throws IOException {
        // 启动服务端,开启监听
        new BIOServer(8080).listen();
    }
}

启动运行Server,创建Client连接Server

public class BIOClient {
    public static void main(String[] args){
        try {
            // 客户端网络IO的封装对象
            Socket socket = new Socket("localhost", 8080);
​
            // 控制台输入客户端输出信息
            BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
            // 在当前socket连接上写入数据
            PrintWriter clientOut = new PrintWriter(socket.getOutputStream(), true);
            // 获取服务端通过socket发送的输入流
            BufferedReader serverIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 模拟Tcp全双工通信
            String line = systemIn.readLine();
            while (!line.equals("bye")) { // 持续连接
                clientOut.println(line); // 向服务端输出消息
                clientOut.flush(); // 手动将缓冲区中的数据强制刷新到输出流中,以确保数据被立即写入底层的输出流
                System.out.println("receive server msg:" + serverIn.readLine());
                System.out.println("client send msg:" + line);
                line = systemIn.readLine();
            }
            systemIn.close();
            serverIn.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BIO中的阻塞是指读写数据都是单向的,因此是同步阻塞。

BIO与NIO比较

流和通道最大的区别是流总是一个方向的,而通道是双向的,这也可以理解为阻塞和非阻塞的差异

BIONIO
阻塞非阻塞
面向流面向缓冲区
每个客户端连接都需要一个独立线程处理选择器,允许一个线程管理多个通道
连接少负载低,适合传统的同步阻塞式服务高并发性和响应性,适合聊天应用,实时游戏等
编程模型简单,高并发需要管理大量线程编程模型复杂,具备更好的性能和并发处理能力

NIO的三大核心

NIO有三大核心部分: Channel(通道),Buffer(缓冲区),Selector(选择器)

  1. 通道(Channel): 通道是NIO中的基本概念,代表着数据源和数据目标,可以是文件、网络连接、管道等。通道提供了一个可供程序读写数据的抽象接口,不同类型的通道可以用于不同的I/O操作。常见的通道类型包括:

    • FileChannel:用于文件I/O操作。

    • SocketChannel:用于套接字网络连接。

    • ServerSocketChannel:用于服务器套接字。

    • DatagramChannel:用于UDP协议数据报的通道。

  2. 缓冲区(Buffer): 缓冲区是NIO中用于存储数据的容器,通常是一个数组。缓冲区用于在通道和应用程序之间传输数据,可以读取数据到缓冲区或将数据从缓冲区写入通道。缓冲区提供了对数据的有序访问,常见的缓冲区类型包括:

    • ByteBuffer:用于存储字节数据。

    • CharBuffer:用于存储字符数据。

    • ShortBufferIntBufferLongBuffer:用于存储整数数据。

    • FloatBufferDoubleBuffer:用于存储浮点数数据。

  3. 选择器(Selector): 选择器是NIO中用于多路复用的关键组件,它允许一个线程管理多个通道。通过选择器,可以监视多个通道上的事件,如可读、可写等,并在事件就绪时唤醒相关的线程来处理。选择器的使用使得可以高效地处理多个并发连接,提高了程序的性能和响应性。

NIO核心之缓冲区

缓冲区(Buffer)一个用于特定基本数据类型的容器,所有缓冲区都是 Buffer 抽象类的子类。

Buffer就像一个数组,可以保存多个相同类型的数据。

Buffer常用子类:
  • ByteBuffer

  • CharBuffer

  • ShortBuffer

  • IntBuffer

  • LongBuffer

  • FloatBuffer

  • DoubleBuffer

创建一个容量为capacity 的 XxxBuffer 对象:

static XxxBuffer allocate(int capacity)
Buffer常用API
  • Buffer clear():清空缓冲区,重置位置和限制。

  • Buffer flip():切换缓冲区的读写模式,将位置设置为0,并将限制设置为当前位置。

  • Buffer rewind():将位置设置为0,保留限制的值,用于重读缓冲区中的数据。

  • Buffer mark():在当前位置设置标记。

  • Buffer reset():将位置重置为先前设置的标记位置。

  • boolean hasRemaining():检查是否还有剩余可读数据。

  • int remaining():返回剩余可读数据的数量。

  • int position():获取当前位置。

  • Buffer position(int n):将设置缓冲区的当前位置为 n.

  • int limit():获取限制值的位置。

  • Buffer limit(int newLimit):设置限制值的位置为n,并返回修改后的Buffer对象。

  • int capacity():返回 Buffer 的 capacity 大小

Buffer中的重要概念
  • 容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小, 也称为"容量",缓冲区容量不能为负,并且创建后不能更改。

  • 限制 (limit):表示缓冲区中可以操作数据的大小 (limit 后数据不能进行读写)。缓冲区的限制不能 为负,并且不能大于其容量。 写入模式,限制等于 buffer的容量。读取模式下,limit等于写入的数据量。

  • 位置 (position):下一个要读取或写入的数据的索引。 缓冲区的位置不能为 负,并且不能大于其限制

位置、限制、容量遵守以下规律: 0 <= position <= limit <= capacity

1.init初始化容量为10的缓冲区

2.FileChannel.read(buffer)将一个大小为4byte的文件读入缓冲区(put操作)

3.flip()切换读写模式

4.buffer.get()读取缓冲区数据

5.clear()清空缓冲区,方便下次put操作

使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer

  2. 调用flip()方法,转换为读取模式

  3. 从Buffer中读取数据

  4. 调用buffer.clear()方法或者buffer.compact()方 法清除缓冲区

NIO核心之通道

Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

FileChannel 类

获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下

  • FileInputStream

  • FileOutputStream

  • RandomAccessFile

  • DatagramSocket

  • Socket

  • ServerSocket

  • 获取通道的其他方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。或者通过通道的静态方法 open() 打开并返回指定通道

FileChannel常用方法
  • int read(ByteBuffer dst) :从Channel 到 中读取数据到 ByteBuffer

  • long read(ByteBuffer[] dsts) : 将Channel中的数据“分散”到 ByteBuffer[]

  • int write(ByteBuffer src) :将 ByteBuffer中的数据写入到 Channel

  • long write(ByteBuffer[] srcs) :将 ByteBuffer[] 到 中的数据“聚集”到 Channel

  • long position() :返回此通道的文件位置

  • FileChannel position(long p) :设置此通道的文件位置

  • long size() :返回此通道的文件的当前大小

  • FileChannel truncate(long s) :将此通道的文件截取为给定大小

  • void force(boolean metaData) :强制将所有对此通道的文件更新写入到存储设备中

NIO核心之选择器
概述

选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。

  • Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)

  • Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个(Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管

  • 理多个通道,也就是管理多个连接和请求。

  • 只有在连接/通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程

  • 避免了多线程之间的上下文切换导致的开销

应用

创建 Selector :通过调用 Selector.open() 方法创建一个 Selector。

Selector selector = Selector.open();

向选择器注册通道:SelectableChannel.register(Selector sel, int ops)

//1. 获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2. 切换非阻塞模式
ssChannel.configureBlocking(false);
//3. 绑定连接
ssChannel.bind(new InetSocketAddress(port));
//4. 获取选择器
Selector selector = Selector.open();
//5. 将通道注册到选择器上, 并且指定“监听接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):

  • 读 : SelectionKey.OP_READ (1)

  • 写 : SelectionKey.OP_WRITE (4)

  • 连接 : SelectionKey.OP_CONNECT (8)

  • 接收 : SelectionKey.OP_ACCEPT (16)

NIO非阻塞原理分析

Selector可以实现一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

服务端流程

1.三大核心配置

// 创建轮询器
Selector selector = Selector.open(); 
// 创建serverSocket通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
// 设置缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024); 

2.阻塞模式设置,绑定端口

// 绑定端口地址
serverSocketChannel.bind(new InetSocketAddress(8088)); 
// NIO为了兼容BIO,默认是阻塞式
serverSocketChannel.configureBlocking(false); 

3.注册通道并且指定“监听接收事件”

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

4.轮询的方式获取选择器上已经“准备就绪”的事件

public void listen() {
        try {
            while (selector.select() > 0) {
                Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 获取已经准备好的事件
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); // 迭代器轮询
                while (keyIterator.hasNext()) { // 同步体现在这里,因为每次只能拿一个key,每次只能处理一种状态
                    SelectionKey key = keyIterator.next();
                    process(key); // 处理key代表的业务:就绪、读、写等等
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
public void process(SelectionKey key) throws IOException {
        if(key.isAcceptable()) {
            // 接受客户端连接请求
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
​
            // 将客户端socketChannel注册到selector,并监听读事件
            socketChannel.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // key.channel 从多路复用器中拿到客户端的引用
            SocketChannel socketChannel = (SocketChannel) key.channel();
            int len = socketChannel.read(buffer);
            if (len > 0) {
                buffer.flip(); // 读写切换,这里是切换为读模式
                String content = new String(buffer.array(), 0, len);
                System.out.println("get receive content:" + content);
                buffer.clear();
            }
        }
    }
客户端流程

1.获取通道

SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost",8088));

2.切换为非阻塞模式

socketChannel.configureBlocking(false);

3.分配缓冲区大小

ByteBuffer buffer = ByteBuffer.allocate(1024);

4.发送数据给绑定的服务端

            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.print("请输入:");
                String msg = scanner.nextLine();
                buffer.put(msg.getBytes());
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
            }
简单NIO示例

服务端

public class Server {
    private int port;
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;

    public Server(int port) {
        this.port = port;
        try {
            // 创建serverSocketChannel,初始化服务端口
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(this.port));
            // 创建selector选择器
            selector = Selector.open();
            // 设置非阻塞模式,NIO兼容BIO默认值为true
            serverSocketChannel.configureBlocking(false);
            // 注册serverSocketChannel到选择器上,并监听客户端连接
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("server start on port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listen(){
        try {
            while (selector.select() > 0) { // 有IO事件就绪,迭代处理
                // 获取SelectedKeys迭代器
                Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey currKey = keyIterator.next();
                    // 根据IO事件状态判断处理
                    process(currKey);
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
    deal with selectionKey
    读   : SelectionKey.OP_READ (1)
    写   : SelectionKey.OP_WRITE (4)
    连接 : SelectionKey.OP_CONNECT (8)
    接收 : SelectionKey.OP_ACCEPT (16)
     */
    public void process(SelectionKey key) throws IOException {
        if (key.isValid()) {
            if (key.isAcceptable()) { // 处理新接入的请求
                System.out.println("done SelectionKey start:" + SelectionKey.OP_ACCEPT);
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                // 客户端SocketChannel连接到selector
                sc.register(selector, SelectionKey.OP_READ);
                System.out.println("done SelectionKey end:" + SelectionKey.OP_ACCEPT);
            } else if (key.isReadable()) { // 读取数据
                System.out.println("done SelectionKey start:" + SelectionKey.OP_READ);
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024); // 分配缓冲区
                int len = sc.read(buffer);
                if (len > 0) {
                    buffer.flip(); // 缓冲区读取切换
                    String content = new String(buffer.array(), 0, len);
                    System.out.println("server receive content :" + content);
                    String currTime = content.equalsIgnoreCase("Query time") ? new Date(System.currentTimeMillis()).toString() : "error order";
                    System.out.println("server send :" + currTime);
                    doWrite(sc, currTime);
                } else if (len < 0) {
                    // 对端链路关闭
                    key.cancel();
                    sc.close();
                } else {
                    ; // 读到0个字节,忽略
                }
                System.out.println("done SelectionKey end:" + SelectionKey.OP_READ);
                System.out.println("==============================================");
            }
        }
    }

    private void doWrite(SocketChannel sc, String currTime) throws IOException {
        if (currTime != null && currTime.trim().length() > 0) {
            byte[] bytes = currTime.getBytes();
            ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
            buffer.put(bytes);
            buffer.flip();
            sc.write(buffer);
        }
    }

    public static void main(String[] args) {
        new Server(8088).listen();  // 启动服务端开启监听客户端连接
    }
}

客户端

public class Client {
    private int port;
    private SocketChannel socketChannel;
    private Selector selector;

    public Client(int port) {
        this.port = port;
    }

    public static void main(String[] args) {
        new Client(8088).run();
    }
     public void run() {
         try {
             // 创建socketChannel
             socketChannel = SocketChannel.open(new InetSocketAddress("localhost", port));
             // 创建selector
             selector = Selector.open();
             // 设置SocketChannel为非阻塞模式
             socketChannel.configureBlocking(false);

             //如果直连接连接成功,则注册到多路复用器上,并注册SelectionKey.OP_READ操作
             if (socketChannel.isConnected()) {
                 socketChannel.register(selector, SelectionKey.OP_READ);
             } else {//如果直连接连接未成功,则注册到多路复用器上,并注册SelectionKey.OP_CONNECT操作
                 socketChannel.register(selector, SelectionKey.OP_CONNECT);
             }
             while (socketChannel.isConnected()) {
                 Scanner scanner = new Scanner(System.in);
                 System.out.print("请输入:");
                 String msg = scanner.nextLine();
                 ByteBuffer buffer = ByteBuffer.allocate(msg.length());
                 buffer.put(msg.getBytes());
                 buffer.flip();
                 socketChannel.write(buffer); // 客户端输入写入缓冲区
                 System.out.println("client send :" + msg);

                 //休眠1秒  无论是否有读写事件发生 selector每隔1秒被唤醒
                 selector.select(1000);

                 // 获取SelectedKeys迭代器
                 Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
                 while (keyIterator.hasNext()) {
                     SelectionKey currKey = keyIterator.next();
                     // 根据IO事件状态判断处理
                     if (currKey.isValid()) {
                         process(currKey);
                     }
                     keyIterator.remove();
                 }
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
     }


    /**
     * 管道状态
     * SelectionKey.OP_CONNECT 是否连接
     * SelectionKey.OP_ACCEPT  是否阻塞
     * SelectionKey.OP_READ    是否可读
     * SelectionKey.OP_WRITE  是否可写
     */
    private static void process(SelectionKey key) throws IOException {
        SocketChannel sc = (SocketChannel) key.channel();
        if (key.isReadable()) {
            ByteBuffer bf = ByteBuffer.allocate(1024);
            int bytes = sc.read(bf);
            if (bytes > 0) {
                bf.flip();
                byte[] byteArray = new byte[bf.remaining()];
                bf.get(byteArray);
                String resopnseMessage = new String(byteArray, "UTF-8");
                System.out.println("接收到服务端数据:" + resopnseMessage);
            } else if (bytes < 0) {
                key.cancel();
                sc.close();
            }
        }
    }
}

运行结果:

AIO简介

AIO(Asynchronous I/O)是Java NIO(New I/O)库的一部分,用于实现异步非阻塞I/O操作。与传统的BIO(Blocking I/O)和NIO(Non-blocking I/O)不同,AIO通过回调机制在I/O操作完成时通知应用程序,而不需要应用程序一直轮询等待数据准备或操作完成。

以下是Java AIO的简介和主要特点:

主要特点
  1. 异步操作: AIO提供了异步I/O操作,允许应用程序在发起I/O请求后继续执行其他任务,而不必等待I/O操作完成。

  2. 回调机制: AIO使用回调机制通知应用程序有关I/O操作的完成情况。应用程序可以注册回调函数,当I/O操作完成时,操作系统会调用回调函数来处理数据。

  3. 适用于高并发: AIO适用于高并发环境,可以处理大量的并发连接和I/O操作,而无需为每个连接创建一个线程。

  4. 文件I/O和网络I/O: AIO不仅适用于网络编程,还适用于文件I/O。它可以用于处理文件读写、网络套接字通信等多种I/O操作。

主要组件和类
  1. AsynchronousChannel: AIO的通道类,包括AsynchronousFileChannel(文件I/O)和AsynchronousSocketChannel(套接字网络通信)等。这些通道支持异步I/O操作。

  2. CompletionHandler: 用于注册回调函数的接口,包括CompletedHandler<V, A>,其中V表示I/O操作的结果类型,A表示附加的上下文对象。应用程序通过实现这个接口来处理I/O操作完成的通知。

  3. AsynchronousServerSocketChannel: 用于实现异步的服务器套接字通信,可以监听并接受连接请求。

总结

BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)各自适用于不同的场景,以下是它们的主要适用场景:

  1. BIO(Blocking I/O):

    • 适用场景: BIO适用于连接数量较少且每个连接的负载较低的场景。它在每个连接上都创建一个独立的线程来处理I/O操作,因此不适合高并发的环境。

    • 示例应用: 传统的同步阻塞式服务器,如Web服务器的早期版本,FTP服务器等。

  2. NIO(Non-blocking I/O):

    • 适用场景: NIO适用于高并发和高吞吐量的网络应用,其中需要处理大量并发连接。它通过复用少量线程来管理多个通道,减少了线程开销,提高了性能和资源利用率。

    • 示例应用: 高性能的网络服务器、聊天应用、实时游戏服务器、代理服务器等。

  3. AIO(Asynchronous I/O):

    • 适用场景: AIO适用于需要高并发、异步处理的应用,尤其是在处理文件I/O或需要等待时间较长的I/O操作时。它通过回调机制在I/O操作完成时通知应用程序,允许应用程序继续执行其他任务,不必等待I/O操作完成。

    • 示例应用: 文件上传/下载服务、邮件服务器、高吞吐量的文件传输应用等。

综合考虑,选择适当的I/O模型取决于应用程序的需求和性能要求。以下是一些指导原则:

  • 如果应用程序的并发连接数量较低,且可以接受阻塞模式,BIO可能是一个简单的选择。

  • 如果应用程序需要高并发处理,能够轻松处理数千个并发连接,NIO是更好的选择。

  • 如果应用程序需要异步处理且需要高并发,AIO可能是更好的选择,尤其在处理文件I/O或等待时间较长的I/O操作时。

另外,不同的应用程序可能会使用混合模型,根据具体需求在不同的部分使用BIO、NIO或AIO,以充分发挥它们的优势。

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

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

相关文章

前端—— 分层模型和应用协议

1 分层模型 MAC地址 可以认为计算机专属&#xff0c;可以认为每台计算机的 MAC地址 固定不变&#xff1b; IP地址 可以认为是计算机当前的【家庭地址】&#xff0c;动态唯一&#xff0c;家庭地址变化&#xff0c;IP地址 也跟着变化&#xff1b; 举个例子&#xff0c;A 给 B 发…

SpringBoot——》更改Redis序列化方式

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

【多线程案例】设计模式-单例模式

1.单例模式 什么是单例模式&#xff1f; 所谓单例&#xff0c;即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象&#xff0c;并且提前在类里面创建好一个实例对象&#xff0c;把构造方法私有化&#xff0c;再对外提供获取这个实例对象的方法&#xff0c;&#xff0…

乐得瑞LDR6023/LDR6028完美兼容iphone15系列OTG充电功能

OTG&#xff08;On-The-Go&#xff09;功能是一项便捷的数据交换功能&#xff0c;广泛应用于移动设备间的数据传输。虽然最早由USB标准化组织在2001年发布&#xff0c;但在市场上的普及主要集中在2014年左右&#xff0c;尤其是在大多数安卓手机上。 在TYPE-C PD协议中&#xf…

HTML5+CSSDAY4综合案例一--热词

样式展示图&#xff1a; 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>热词…

Nginx的安全控制

安全控制 关于web服务器的安全是比较大的一个话题&#xff0c;里面所涉及的内容很多&#xff0c;Nginx反向代理是安全隔离来提升web服务器的安全&#xff0c;通过代理分开了客户端到应用程序服务器端的连接&#xff0c;实现了安全措施。在反向代理之前设置防火墙&#xff0c;…

深入浅出的介绍一下虚拟机VMware Workstation——part1

目录 VMware Workstation说明VMware Workstation的主要特点VMware Workstation安装VMware Workstation卸载 VMware Workstation说明 1、 虚拟机的软件在市面上有很多&#xff0c;目前功能强大且比较主流的虚拟机软件首选VMware 2、VMware是一个虚拟PC的软件&#xff0c;可以在…

A (1087) : DS单链表--类实现

Description 用C语言和类实现单链表&#xff0c;含头结点 属性包括&#xff1a;data数据域、next指针域 操作包括&#xff1a;插入、删除、查找 注意&#xff1a;单链表不是数组&#xff0c;所以位置从1开始对应首结点&#xff0c;头结点不放数据 类定义参考 #include<…

边坡安全监测系统的功能优势

随着科技的进步&#xff0c;边坡安全监测系统在各种工程项目中发挥着越来越重要的作用。这款系统通过实时监测垂直、水平位移数据&#xff0c;以折线图的方式显示在监控平台中&#xff0c;为工程人员提供了直观、便捷的监控工具&#xff0c;从而能够及时掌握边坡稳定状况&#…

Quarto 入门教程 (1):简单介绍和资料汇总

本推文是 “手把手教你使用 Quarto 构建文档” 教程的第一部分&#xff0c;本文先介绍 Quarto 构建文档的原理&#xff1b;可创建的文档类型&#xff1b;对应的参考资源分享。 下一部分&#xff0c;会手把手介绍如何使用它&#xff08;下次推文吧&#xff5e;&#xff09;。 …

行车记录仪检测不到内存卡的原因

最近修了两个行车记录仪&#xff0c;相同的问题&#xff0c;都是提示插入内存卡&#xff08;TF卡&#xff09;。网上搜索资料&#xff0c;并没有明确的指出问题原因&#xff0c;有的是直接更换卡槽。 于是自己分析&#xff0c;首先内存卡电路属于小电流&#xff0c;而且电压并不…

好物分享|项目经理真的离不开这16个表格

今天给大家分享一些项目经理必备工具&#xff5e; SWOT分析法&#xff0c;可以对研究对象所处的情景进行全面、系统、准确的研究&#xff0c;从而根据研究结果制定相应的发展战略、计划以及对策等。 优点在于&#xff0c;考虑问题全面&#xff0c;是一种系统思维。而且可以把问…

OLED透明拼接屏,探索石家庄城市之美,在该城市中有哪些应用表现

引言&#xff1a;石家庄&#xff0c;作为河北省首府&#xff0c;拥有悠久的历史与独特的风貌。近年来&#xff0c;随着科技的迅猛发展&#xff0c;石家庄城市景观也焕然一新。 其中&#xff0c;OLED透明拼接屏作为一项创新科技&#xff0c;在城市中的应用给石家庄增添了一抹璀…

C# 把多个dll合成一个dll

Nuget 下载ILMerge两个工程 dog为测试工程 TestIlmerge为准备合并的类库 如下图所示&#xff0c; 由于我们引用下面4个库 正常生成后&#xff0c;会有TestIlmerge.dll和下面的这4个dll 只生成TestIlmerge.dll 打开工程文件 在最下方加入以下两段 <Target Name"ILMerge…

解决WordPress升级后提示:无需升级,您的WordPress数据库已经是最新的了

问题描述 当升级了 WordPress 6.3 后&#xff0c;登录后台出现了提示&#xff1a;无需升级&#xff0c;您的WordPress 数据库已经是最新的了。并且无法进入后台了。 出现这个问题的原因可能是你网站开启了 Memcached 缓存。 如何验证是否开启了 Memcached 缓存&#xff1f;检…

C++基础——基础语法

1 注释 C支持单行注释和多行注释。 单行注释 // 注释内容单行注释直到改行末尾&#xff0c;可以与代码放在同一行&#xff0c;在代码后面注释 多行注释 /* 注释内容 */包含在其中的都会被注释 2 变量 变量的作用是给指定的内存空间起名&#xff0c;方便操作这段内存。变…

用Python抓取京东商品评论

分析可能存在的失效时&#xff0c;我们强调要基于功能分析失效&#xff0c;每种功能可以思考七种类别失效模式&#xff0c;从而考虑比较全面。 这背后其实是对可靠性概念的理解&#xff0c;可靠性的定义&#xff1a;产品在规定的条件下和规定的时间内&#xff0c;完成规定功能…

架构师-软件工程习题选择题

架构师-软件工程习题选择题

今年较火的扩散模型Diffusion Model

&#x1f680; 作者 &#xff1a;“码上有钱” &#x1f680; 文章简介 &#xff1a;AI-扩散算法 &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac;项目 扩散模型加噪去噪过程 原理 高斯噪声 在深度学习中&#xff0c;高斯噪声通常指的是一种服从…

库存管理与策略模式

库存管理是个难题&#xff0c;涉及到采购、销售、仓储、门店、网站订单各个环节&#xff0c;实物库存和系统库存很难保证完全一致&#xff0c;记得系统刚上线第一天&#xff0c;因为实物库存与导入系统的库存有差异&#xff0c;系统又做了限制系统库存必须大于0才允许销售普通订…