博客 常见IO模型总结 BIO NIO AIO
Post
Cancel

常见IO模型总结 BIO NIO AIO

前言

我们的经常能听到BIO, NIO, AIO之类的关键词,是时候在到这个的知识点进行归类总结下

为什么是需要IO

就是主存到外设之间的数据传输,像读取一个磁盘文件或者写数据到磁盘, 网络通讯就是数据从网卡外设到主存之间的传递

Linux 下的常见网络IO模型

阻塞型IO(BIO)

这个是主打一个简单,就是调一个的系统函数时的,死等结果返回。

非阻塞型IO(NIO)

因为前面的BIO是一个缺点,就是死等,让线程干不了别的会事情, 所以在数据准备好的之前做了别的事情,所以可能通过调用非阻塞的函数,然后轮询去系统函数去获取结果,我们在实现异步的常用的模式,调用请求接口时,这个接口会的立马返回请求的ID,后面就用这个ID来查询结果,十分像我们的去网上申请业务,得到了一个业务受理号,后面的通过这个受理号是不停得去查询业务结果

多路复用型IO(Multiplexing)

上面的模型的缺点是一个的while循环,不停在循环去查询状态,而且多个请求处理时,需要起多个线程来的循环,这样导致很大的资源浪费。这就发明了多路复用模型,先把文件描述符登记,接着当数据可处理里,就会得到事件通知就去做read 或write, 这个需要用到的相关操作系统的实现有select, poll, epoll, kqueue

  • select 源自 1983 年 BSD UNIX 的 select(2) 系统调用,是 POSIX 标准的一部分, 就是像用一堆描述符中找到有信号的,然后它是有长度控制的1024
  • poll, 它的名字是轮询的意思,系统逐个询问每个 fd 的状态,就像挨家挨户 polling,1997 年随 Single UNIX Specification 引入,取代 select 的 fd 数量限制
  • epoll(2002) ,首字母e 代表的是event的意思,前面的两个前辈是表收到通知,但不是知哪个fd可被处理,就要挨个轮询,复杂占为O(n), 这个epoll 是linux 特有的,基于事件驱动的 poll,内核主动通知”哪个 fd 发生了什么事件”, 2002 年 Linux 2.5.44 内核引入,Linux 特有(非 POSIX)
  • kqueue(2000年) 是BSD系像MacOS,它做为另一个的实现都是解决select, poll遍历的问题,epoll 专注于网络 IO 的高效监控,而 kqueue 是一个通用的事件通知框架——不仅能做 epoll 的事,还能把操作系统各种机制(信号、文件、进程、定时器)和应用程序自己的事件全部统一到一个接口里处理。

名字反映了 I/O 多路复用技术的三次迭代——

  1. select:告诉你”有人好了”,你自己去找是谁
  2. poll:同上,但打破了人数限制
  3. epoll,kqueue:直接告诉你”张三好了、李四好了”,精准高效

ps: 为什么Linux 不直接引用kqueue, 而是另造了个轮子呢?

信号通知IO

就是通过信号通知进程,数据准备就绪,可以进行读取, 但缺点就是它无法通知哪个FD,要遍历,所以每次都要遍历所有fd, 信号驱动 I/O 是 “过渡方案” ——在历史上首次实现了”内核通知代替进程轮询”,但设计粗糙;epoll 吸收了其思想,用事件队列替代信号机制,成为更优雅的工程实现。

异步IO(AIO)

多路复用目前是主流的IO但它也还是同步阻塞IO,收到事件后,还是要通过Read 或Write 方法进行操作,这个两个操作是的同步的, 所以就有了个AIO,对比epoll, 操作系统直接把数据写到了用户态空间,直接就可以用了,不用进行调用Read把数据从内核态到用户态,看起来这样来说是最高效的,因为系统内核提前都把事情都做了,那为什么现在不把它切换为主流方案?

常见Java的网络IO

Java 网络编程 I/O 模型的演进历程,核心要点如下:

  • 在java 1.4 之前只有BIO, 就是我们ServerSocket进行read write这样的代码
>import java.io.*; import java.net.*; /** * BIO 服务端:同步阻塞模式 * 缺点:并发高时线程数爆炸,每个连接独占一个线程 */ public class BioServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("BIO Server started on 8080"); while (true) { // 阻塞等待客户端连接 Socket socket = serverSocket.accept(); System.out.println("New client connected: " + socket.getInetAddress()); // 每个连接创建新线程处理 new Thread(new BioHandler(socket)).start(); } } static class BioHandler implements Runnable { private Socket socket; public BioHandler(Socket socket) { this.socket = socket; } @Override public void run() { try (BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) { String line; // 阻塞读取数据 while ((line = reader.readLine()) != null) { System.out.println("Received: " + line); writer.println("Echo: " + line); } } catch (IOException e) { e.printStackTrace(); } } } }
  • 在java 1.4 就引入 NIO 可以支持select, poll, 用到的类就是Selector, ServerSocketChannel
  • 在java 1.5 支持 epoll(事件驱动,O(1) 复杂度,Linux 下性能大幅提升)
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * NIO 服务端:同步非阻塞 + 多路复用(Selector)
 * 核心:一个 Selector 线程管理多个 Channel
 */
public class NioServer {
    
    public static void main(String[] args) throws IOException {
        // 1. 创建 Selector(多路复用器)
        Selector selector = Selector.open();
        
        // 2. 创建 ServerSocketChannel,绑定端口
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false);  // 关键:设置为非阻塞模式
        
        // 3. 注册到 Selector,监听 ACCEPT 事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO Server started on 8080");
        
        while (true) {
            // 4. 阻塞等待就绪事件(有事件才唤醒,否则休眠)
            selector.select();
            
            // 5. 获取就绪的 SelectionKey 集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();
            
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();  // 必须移除,否则重复处理
                
                if (key.isAcceptable()) {
                    // 有新连接接入
                    handleAccept(key, selector);
                } else if (key.isReadable()) {
                    // 有数据可读
                    handleRead(key);
                } else if (key.isWritable()) {
                    // 可以写数据
                    handleWrite(key);
                }
            }
        }
    }
    
    // 处理连接请求
    private static void handleAccept(SelectionKey key, Selector selector) 
            throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();  // 非阻塞,立即返回
        clientChannel.configureBlocking(false);
        
        // 注册读事件,并附带一个 Buffer
        clientChannel.register(selector, SelectionKey.OP_READ, 
            ByteBuffer.allocate(1024));
        
        System.out.println("Client connected: " + clientChannel.getRemoteAddress());
    }
    
    // 处理读事件
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        
        int read = channel.read(buffer);  // 非阻塞读取
        if (read == -1) {
            // 客户端断开
            key.cancel();
            channel.close();
            return;
        }
        
        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        String message = new String(data);
        System.out.println("Received: " + message);
        
        // 准备写回响应
        buffer.clear();
        buffer.put(("Echo: " + message).getBytes());
        buffer.flip();
        
        // 切换为写模式
        key.interestOps(SelectionKey.OP_WRITE);
    }
    
    // 处理写事件
    private static void handleWrite(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        
        channel.write(buffer);  // 非阻塞写入
        
        if (!buffer.hasRemaining()) {
            // 写完了,切回读模式
            buffer.clear();
            key.interestOps(SelectionKey.OP_READ);
        }
    }
}
  • java 1.7 引用了包NIO2 它是包含了真正的异步非阻塞的实现, 用到的类是 AsynchronousSocketChannel然后的大量的回调函数
>import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.CountDownLatch; /** * AIO 服务端:真正的异步非阻塞 * 核心:所有 I/O 操作都带 CompletionHandler 回调,由操作系统完成 */ public class AioServer { public static void main(String[] args) throws IOException, InterruptedException { AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open(); serverChannel.bind(new InetSocketAddress(8080)); System.out.println("AIO Server started on 8080"); // 异步接受连接,传入 CompletionHandler 回调 serverChannel.accept(null, new AcceptHandler(serverChannel)); // 防止主线程退出 new CountDownLatch(1).await(); } // 连接回调处理器 static class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Void> { private AsynchronousServerSocketChannel serverChannel; public AcceptHandler(AsynchronousServerSocketChannel serverChannel) { this.serverChannel = serverChannel; } @Override public void completed(AsynchronousSocketChannel clientChannel, Void attachment) { // 继续接受下一个连接(递归) serverChannel.accept(null, this); System.out.println("Client connected: " + clientChannel); ByteBuffer buffer = ByteBuffer.allocate(1024); // 异步读取数据,传入 ReadHandler 回调 clientChannel.read(buffer, buffer, new ReadHandler(clientChannel)); } @Override public void failed(Throwable exc, Void attachment) { exc.printStackTrace(); } } // 读取回调处理器 static class ReadHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel clientChannel; public ReadHandler(AsynchronousSocketChannel clientChannel) { this.clientChannel = clientChannel; } @Override public void completed(Integer result, ByteBuffer buffer) { if (result == -1) { // 连接关闭 try { clientChannel.close(); } catch (IOException e) { e.printStackTrace(); } return; } buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); String message = new String(data); System.out.println("Received: " + message); // 准备响应 ByteBuffer writeBuffer = ByteBuffer.wrap( ("Echo: " + message).getBytes()); // 异步写入数据,传入 WriteHandler 回调 clientChannel.write(writeBuffer, writeBuffer, new WriteHandler(clientChannel)); } @Override public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); } } // 写入回调处理器 static class WriteHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel clientChannel; public WriteHandler(AsynchronousSocketChannel clientChannel) { this.clientChannel = clientChannel; } @Override public void completed(Integer result, ByteBuffer buffer) { if (buffer.hasRemaining()) { // 没写完,继续写 clientChannel.write(buffer, buffer, this); } else { // 写完了,继续读取下一个请求 ByteBuffer readBuffer = ByteBuffer.allocate(1024); clientChannel.read(readBuffer, readBuffer, new ReadHandler(clientChannel)); } } @Override public void failed(Throwable exc, ByteBuffer buffer) { exc.printStackTrace(); } } }

Netty 的网络IO模型

它是利用Java 多路复用(Selector) + 非阻塞IO 

>// 1. 创建非阻塞通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); serverChannel.configureBlocking(false); // ← 关键:设置为非阻塞模式 // 2. 创建 Selector Selector selector = Selector.open(); // 3. 注册通道到 Selector,关注 ACCEPT 事件 serverChannel.register(selector, SelectionKey.OP_ACCEPT); // 4. 轮询就绪事件 while (true) { selector.select(); // 阻塞等待有事件就绪(底层用 epoll/kqueue/select) Set<SelectionKey> keys = selector.selectedKeys(); for (SelectionKey key : keys) { if (key.isAcceptable()) { /* ... */ } if (key.isReadable()) { /* ... */ } // ... } }
This post is licensed under CC BY 4.0 by the author.

bootstrap-table group-by-v2 的使用

-

Comments powered by Disqus.