在Unix Socket的输入操作中,可以将其分为以下几个阶段:
-  等待数据就绪(内核空间): 
 在这个阶段,应用程序通过调用阻塞式的读取函数(如recv)或非阻塞式的读取函数(如recv、recvfrom)等待数据的到达。如果没有数据到达,阻塞式的读取函数会一直等待,而非阻塞式的读取函数会立即返回一个错误码或标识表示数据未准备好。
-  数据拷贝到内核缓冲区(内核空间): 
 当数据就绪后,操作系统会将数据从网络中拷贝到内核缓冲区中。这个阶段是在内核空间中进行的,应用程序无法直接访问或操作内核缓冲区。
-  数据拷贝到用户缓冲区(用户空间): 
 在这个阶段,从内核缓冲区将数据拷贝到应用程序提供的用户缓冲区中。这个步骤涉及了将数据从内核空间切换到用户空间的操作。
-  数据处理和应用程序操作(用户空间): 
 一旦数据被拷贝到用户缓冲区,应用程序就可以对数据进行处理和操作。这可能包括解析数据、执行特定的业务逻辑或对数据进行进一步的处理
在整个输入操作过程中,数据从网络到达应用程序的用户空间,经过了多次拷贝和处理。故而产生零拷贝的方案,以减少用户空间与CPU内核空间的拷贝过程,减少用户上下文与CPU内核上下文间的切换,提高系统效率
I/O 模型

阻塞 I/O
在阻塞IO模型中,应用程序发起一个IO操作后,会一直阻塞等待,直到IO操作完成
- 当应用程序执行IO操作时,如果数据没有准备好或无法立即写入目标,应用程序会一直等待,直到数据就绪。

非阻塞 I/O
在非阻塞IO模型中,应用程序发起一个IO操作后,会立即返回,而不会等待IO操作的完成
- 当应用程序执行非阻塞IO操作时,如果数据没有准备好或无法立即写入目标,应用程序会立即返回一个错误码或标识
- 应用程序可以通过轮询或使用select、poll、epoll等函数来检查IO操作的状态,以确定数据是否已经就绪

多路复用 I/O
多路复用IO模型通过使用select、poll、epoll等函数,将多个IO操作集中在一起进行管理。应用程序将多个IO操作注册到多路复用机制(Selector)中,并在调用多路复用函数时等待就绪的IO操作

信号驱动 I/O
信号驱动IO模型中,应用程序通过注册信号处理函数,并将文件描述符设置为非阻塞模式。
- 当IO操作就绪时,操作系统会发送一个信号给应用程序,应用程序在信号处理函数中进行IO操作的处理

异步 I/O
异步IO模型中,应用程序发起IO操作后,可以继续执行其他任务,而不需要等待IO操作的完成
- 当IO操作完成后,操作系统会通知应用程序,应用程序可以异步地获取或处理已完成的IO结果

I/O 模型对比

同步 I/O 与异步 I/O
- 阻塞等待 vs 非阻塞操作:同步I/O需要应用程序阻塞等待操作完成,而异步I/O可以让应用程序在操作进行时继续执行其他任务。
- 应用程序控制 vs 操作系统控制:同步I/O模型需要应用程序主动发起和等待I/O操作的完成,而异步I/O模型中,应用程序发起I/O操作后,操作系统负责管理和执行操作,并在完成时通知应用程序。
- 阻塞模式 vs 异步通知:同步I/O模型中,应用程序需要等待数据就绪或写入目标后才能继续执行,而异步I/O模型中,应用程序可以在操作发起后立即返回,并在操作完成后得到通知
Java I/O 模型
| BIO | NIO | AIO | |
|---|---|---|---|
| 阻塞 vs 非阻塞 | 阻塞 | 非阻塞 | 非阻塞 | 
| 同步 vs 异步 | 同步 | 同步 | 异步 | 
| 线程资源消耗 | 需要为每个连接分配独立的线程 | 通过单线程处理多个连接 | 利用操作系统的异步通知机制,不需要额外的线程资源 | 
| 数据处理能力 | 阻塞等待 | 使用缓冲区(Buffer)进行数据处理,提供了更高效的数据读写能力 | 通过异步I/O操作提供高性能的异步操作机制 | 
BIO
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善
public class BioExample {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("Server started, waiting for connections...");
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("Client connected: " + socket.getInetAddress());
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            socket.close();
            System.out.println("Client disconnected.");
        }
    }
}
NIO
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。当进行读写操作时,只须直接调用API的read或write方法
public class NioExample {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        serverSocketChannel.configureBlocking(false);
        System.out.println("Server started, waiting for connections...");
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel != null) {
                System.out.println("Client connected: " + socketChannel.getRemoteAddress());
                executorService.submit(() -> {
                    try {
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        while (socketChannel.read(buffer) != -1) {
                            buffer.flip();
                            socketChannel.write(buffer);
                            buffer.clear();
                        }
                        socketChannel.close();
                        System.out.println("Client disconnected: " + socketChannel.getRemoteAddress());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
}
AIO
异步IO模型中,应用程序发起IO操作后,可以继续执行其他任务,而不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序,应用程序可以异步地获取或处理已完成的IO结果
public class AioExample {
    public static void main(String[] args) throws IOException {
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        System.out.println("Server started, waiting for connections...");
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                serverSocketChannel.accept(null, this);
                try {
                    System.out.println("Client connected: " + socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            if (result == -1) {
                                try {
                                    socketChannel.close();
                                    System.out.println("Client disconnected: " + socketChannel.getRemoteAddress());
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                return;
                            }
                            buffer.flip();
                            socketChannel.write(buffer, buffer, this);
                            buffer.clear();
                        }
                        @Override
                        public void failed(Throwable exc, ByteBuffer buffer) {
                            exc.printStackTrace();
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
参考资料:
- 《Unix网络编程第1卷》
- Redis之IO线程、IO多路复用,BIO、NIO和AIO区别










