🌺The Begin🌺点点关注,收藏不迷路🌺

1. 开篇:I/O模型是什么?为什么重要?

一次简单的数据读取,从应用程序发起请求到数据真正就绪,中间要经过操作系统内核、硬件中断、DMA等多层协作。不同的处理方式,就形成了不同的I/O模型。

I/O模型的核心区别在于两个阶段:

两个等待阶段

阶段1
等待数据准备
如等待网络数据包到达

阶段2
将数据从内核拷贝到用户空间

五大I/O模型

  1. 阻塞I/O(Blocking I/O)- BIO
  2. 非阻塞I/O(Non-blocking I/O)- NIO
  3. I/O多路复用(I/O Multiplexing)- epoll/select
  4. 信号驱动I/O(Signal-driven I/O)
  5. 异步I/O(Asynchronous I/O)- AIO

2. 快速概览:五大I/O模型对比

I/O模型 第一阶段(等待数据) 第二阶段(拷贝到用户空间) 整体是否阻塞 代表作
阻塞I/O 阻塞 阻塞 全程阻塞 传统Socket
非阻塞I/O 轮询(不阻塞) 阻塞 部分阻塞 设置NONBLOCK
I/O多路复用 阻塞(select/poll) 阻塞 部分阻塞 Nginx/Redis/Netty
信号驱动I/O 不阻塞(信号通知) 阻塞 部分阻塞 SIGIO信号
异步I/O 不阻塞 不阻塞 全程不阻塞 Windows IOCP/Linux io_uring

阻塞程度对比

BIO 全程阻塞🥵

NIO 轮询+阻塞😐

多路复用 并发处理😊

AIO 全程非阻塞🚀


3. 模型一:阻塞I/O(BIO)

3.1 核心流程

硬件设备 内核 应用程序 硬件设备 内核 应用程序 第一阶段:等待数据就绪 第二阶段:拷贝数据 整个过程中,应用程序完全阻塞 recvfrom() 系统调用 等待数据 数据准备完成 将数据从内核拷贝到用户空间 返回成功

3.2 特点

阻塞I/O BIO

优点

使用简单

开发调试方便

内核自动调度

缺点

一个线程处理一个连接

线程上下文切换开销大

无法支撑高并发

典型场景

JDBC数据库连接

低并发业务

简单Demo

3.3 Java代码示例

// 传统BIO服务端 - 每个连接一个线程
public class BioServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        
        while (true) {
            // 此处阻塞,等待客户端连接
            Socket socket = serverSocket.accept();
            
            // 为每个连接创建新线程
            new Thread(() -> {
                try {
                    BufferedReader reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream())
                    );
                    String line;
                    // 此处阻塞,等待数据读取
                    while ((line = reader.readLine()) != null) {
                        System.out.println(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

4. 模型二:非阻塞I/O(NIO)

4.1 核心流程

内核 应用程序 内核 应用程序 数据未就绪 loop [轮询循环] 数据准备就绪 轮询过程中不阻塞, 但拷贝数据时仍阻塞 recvfrom() 系统调用 返回EWOULDBLOCK错误 继续其他任务或等待 recvfrom() 系统调用 拷贝数据到用户空间 返回成功

4.2 非阻塞I/O流程图

应用程序发起recvfrom

数据是否就绪?

立即返回EWOULDBLOCK

短暂延时/做其他事

内核拷贝数据到用户空间

返回成功

4.3 特点

非阻塞I/O NIO

优点

调用不会阻塞线程

一个线程可管理多个连接

提高CPU利用率

缺点

轮询占用CPU

频繁系统调用

实现复杂

轮询问题

空轮询造成CPU浪费

需要设置合理延时


5. 模型三:I/O多路复用

5.1 核心流程

内核 应用程序 内核 应用程序 使用select/poll/epoll 监听多个文件描述符 select() 系统调用 返回有数据就绪的fd recvfrom() 系统调用 拷贝数据 返回成功

5.2 多路复用核心思想

I/O多路复用

监听

监听

监听

监听

单个线程/进程
Selector

Socket1

Socket2

Socket3

...

传统BIO

线程1

Socket1

线程2

Socket2

线程3

Socket3

5.3 select/poll/epoll 演进

epoll

事件驱动

只返回就绪fd

O1复杂度

红黑树+就绪链表

poll

链表结构
无上限

仍需拷贝全部fd

O遍历查找

select

描述符集合
FD_SETSIZE限制1024

每次拷贝全部fd

O遍历查找就绪fd

5.4 多路复用工作流程

渲染错误: Mermaid 渲染失败: Parse error on line 3: ...LOOP{selector.select()} LOOP -->|有就绪 -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

5.5 Java NIO代码示例

// NIO多路复用服务端
public class NioServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false);
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        while (true) {
            // 阻塞,等待就绪事件
            selector.select();
            
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                
                if (key.isAcceptable()) {
                    SocketChannel client = serverChannel.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer);  // 数据已就绪,不阻塞
                    // 处理数据...
                }
            }
        }
    }
}

6. 模型四:信号驱动I/O

6.1 核心流程

内核 应用程序 内核 应用程序 立即返回,数据未就绪 数据准备就绪 sigaction() 注册信号处理函数 recvfrom() 系统调用 等待数据准备 发送SIGIO信号 recvfrom() 系统调用 拷贝数据到用户空间 返回成功

6.2 特点

信号驱动I/O

流程

注册信号处理函数

发起非阻塞调用

信号通知数据就绪

再次调用读取数据

优点

等待数据时不阻塞

无需轮询

缺点

信号队列可能溢出

信号处理上下文受限

实际使用较少


7. 模型五:异步I/O(AIO)

7.1 核心流程

内核 应用程序 内核 应用程序 立即返回 数据已在用户空间 可直接使用 aio_read() 系统调用 等待数据准备 拷贝数据到用户空间 (应用程序指定的缓冲区) 发送信号或回调

7.2 异步I/O vs 其他模型

异步I/O

操作系统主动通知

数据拷贝由内核完成

AIO/io_uring

同步I/O

应用程序主动等待

数据拷贝由应用程序发起

BIO/NIO/多路复用/信号驱动

7.3 Java AIO示例

// Java AIO 异步I/O
public class AioServer {
    public static void main(String[] args) throws IOException {
        AsynchronousServerSocketChannel serverChannel = 
            AsynchronousServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        
        serverChannel.accept(null, new CompletionHandler<>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                // 继续接受下一个连接
                serverChannel.accept(null, this);
                
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                // 异步读取,不阻塞
                client.read(buffer, buffer, new CompletionHandler<>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        attachment.flip();
                        // 数据已就绪,可直接使用
                        System.out.println("收到数据: " + 
                            new String(attachment.array()));
                    }
                    
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        exc.printStackTrace();
                    }
                });
            }
            
            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });
        
        // 主线程不阻塞,可以继续其他工作
        Thread.sleep(Integer.MAX_VALUE);
    }
}

8. 五大模型对比总结

模型5: AIO

aio_read

回调

APP

KERNEL

模型4: 信号驱动

sigaction

SIGIO

recvfrom

APP

KERNEL

模型3: 多路复用

select

就绪列表

recvfrom

APP

KERNEL

模型2: NIO

轮询

就绪后拷贝

APP

KERNEL

模型1: BIO

recvfrom阻塞

拷贝数据

APP

KERNEL

内核空间

内核

用户空间

应用程序

总结表

I/O模型 等待数据阶段 拷贝数据阶段 系统调用次数 适用场景
阻塞I/O 阻塞 阻塞 1次 低并发,简单场景
非阻塞I/O 不阻塞(轮询) 阻塞 N次+1次 CPU充足,连接数中等
多路复用 阻塞(select) 阻塞 2次 高并发,如Nginx/Netty
信号驱动 不阻塞 阻塞 2次 实际使用较少
异步I/O 不阻塞 不阻塞 1次 高并发+高性能+高复杂度

9. 实战选型:我该用哪个?

简单Demo/内部工具
连接数<100

Web服务
连接数1000~10000
如Tomcat/Jetty

超高并发
海量长连接
即时通讯/游戏

CPU密集型
数据处理

你是什么场景?

✅ BIO
开发效率最高

✅ I/O多路复用
Netty/Redis/Nginx

✅ AIO
或io_uring
Java AIO慎用

CPU密集型
I/O模型影响小
专注计算优化

业界实践

中间件/框架 使用的I/O模型 原因
Nginx 多路复用(epoll) 高并发、可扩展性强
Redis 多路复用(epoll) 单线程+事件驱动
Netty 多路复用 Java生态最强NIO框架
Tomcat 7+ 多路复用(NIO) 传统BIO → NIO演进
Node.js 多路复用(libuv) 事件循环+异步
Apache(Prefork) BIO 每请求一进程

10. 进阶:io_uring——Linux的新一代异步I/O

渲染错误: Mermaid 渲染失败: Lexical error on line 2. Unrecognized text. ...R subgraph 传统AIO(POSIX) P1[接 ----------------------^

io_uring 工作流程

写入SQ

内核消费

写入CQ

读取

应用程序

提交队列 Submission Queue

内核

完成队列 Completion Queue


11. 面试高频追问

Q1:Java的NIO是NIO(非阻塞)还是多路复用?

:Java NIO全称是New I/O,核心是多路复用模型,内部封装了select/poll/epoll,提供了非阻塞能力,本质是同步非阻塞 + 多路复用

Q2:为什么说Netty高性能?

  1. 使用I/O多路复用(epoll)
  2. 零拷贝技术
  3. 无锁化设计(Pipeline)
  4. 内存池(PooledByteBufAllocator)

Q3:select的1024限制是什么?

:select使用fd_set结构,默认最大文件描述符数量为FD_SETSIZE=1024,是编译时常量。epoll没有此限制。

Q4:阻塞和非阻塞的区别是什么?

  • 阻塞:调用结果返回前,当前线程被挂起
  • 非阻塞:立即返回,不等待结果

12. 总结脑图

I/O模型

阻塞I/O BIO

同步阻塞

简单但低并发

每个连接一个线程

非阻塞I/O

同步非阻塞

轮询机制

CPU占用问题

多路复用

同步阻塞

select/poll/epoll

一个线程管多个连接

Nginx/Netty/Redis

信号驱动

第一阶段异步

第二阶段同步

使用较少

异步I/O AIO

全程异步

回调机制

Windows IOCP

Linux io_uring


13. 一句话总结

五种I/O模型的本质区别在于“数据就绪的等待阶段”和“数据拷贝阶段”是否阻塞:BIO全程阻塞,NIO和多路复用拷贝阶段阻塞,信号驱动等待阶段不阻塞,只有AIO全程非阻塞。高并发场景下,多路复用(epoll)是目前最成熟的主流方案,AIO(io_uring)是未来的方向。

记忆口诀
BIO傻等数据来,NIO轮询问内核,多路复用管家看,信号驱动等通知,AIO搞定全自动。


📌 如果你觉得这篇文章讲清楚了,欢迎点赞、收藏、评论交流!

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐