IO模型

本文解析了同步和异步IO模型的概念及工作原理,介绍了BIO、NIO和AIO三种常见IO模型的区别,通过Java示例代码展示了不同模型的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IO模型

IO模型可以根据是否是同步的分为同步和异步两种,POSIX的定义如下:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.

An asynchronous I/O operation does not cause the requesting process to be blocked.

简单解释下,即同步IO将线程阻塞至IO完成,异步IO不阻塞线程。

但这里又有个问题,既然同步IO是阻塞线程至IO完成,为什么还分BIO和NIO。

这里所描述的IO不是IO的全过程,以socket为例,IO可分为两个阶段:准备阶段和就绪阶段。准备阶段是网卡还没有接收到数据,就绪阶段是网卡已经接收到数据了,但诗句还在内核空间中,用户线程还不能访问数据。

当数据在准备阶段时,NIO的线程并不会阻塞,只是不断轮询数据是否准备完毕;当数据在就绪阶段时,kernel需要将内核中的数据复制到用户空间中,此阶段是会阻塞线程的。

用《大型网站系统与Java中间件实践》中的话来区分下NIO和AIO

AIO和NIO的一个最大的区别是,NIO在有通知时可以进行相关操作,例如读或者写,而AIO在有通知时表示相关操作已经完成。

可以理解为,NIO是通知你某个socket已经准备好了,你自己去把要读或者写的数据处理一下,比如Java中的打开inputstream或者outputstream。而AIO是通知你,需要的数据已经处理好放在某个位置,想进行什么操作可以进行了。

同步阻塞IO BIO(Blocking IO)

阻塞IO

同步阻塞模型是最形象的一种模型,也是初学者编程使用最多的模型。

利用上图对该模型加以说明,用户线程调用recvfrom()方法,线程阻塞,等待内核返回数据。内核需要经历两个阶段,准备数据和将数据拷贝到用户空间中。等两个阶段都结束了,用户线程才能解除阻塞状态。

使用Java简单实现BIO

public class SocketServer {

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(52991);
        System.out.println("正在监听本地端口:" + serverSocket.getLocalPort());
        while (true) {
            Socket client = serverSocket.accept();
            System.out.println("新的连接" + client.getLocalSocketAddress());
            InputStream inputStream = client.getInputStream();
            byte[] buffer = new byte[1024];
            inputStream.read(buffer);
            OutputStream outputStream = client.getOutputStream();
            outputStream.write(buffer);

        }
    }
}
public class SocketClient {

    public static void main(String[] args) throws Exception {
        try (Socket socket = new Socket()) {
            socket.bind(new InetSocketAddress(52998));
            socket.connect(new InetSocketAddress("localhost", 52991));
            OutputStream outputStream = socket.getOutputStream();
            byte[] buffer = "hello world".getBytes();
            outputStream.write(buffer);
            outputStream.flush();
            InputStream inputStream = socket.getInputStream();
            byte[] inputBuffer = new byte[1024];
            inputStream.read(inputBuffer);
            System.out.println(new String(inputBuffer));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

同步非阻塞IO NIO(Non-Blocking IO)

基于事件驱动思想,采用Reactor模式,是Java服务端系统采用比较多的方式,基于NIO实现了IO多路复用。

Reactor:将I/O事件分派给对应的Handler
Acceptor:处理客户端新连接,并分派请求到处理器链中
Handlers:执行非阻塞读/写任务

avatar

单reactor多线程
基于

非阻塞IO

对于最基本的NIO模型, 在数据的准备阶段,用户线程是不阻塞的,循环使用recvfrom询问内核数据是否准备完毕。如果准备完毕,陷入阻塞,等待内核将数据复制进用户空间。

NIO的通知发生在动作之前,是在可写、可读的时候,Selector发现这些事件后调用Handler处理。

使用Java实现简单的NIO。

但是这段代码是有问题的,当client线程增加时,会产生ConcurrentModificationException,即还在遍历selector的keyset时,它就产生了修改。且client获得的数据不能正确显示。

public class SocketServer {

    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();

        ssc.bind(new InetSocketAddress(52991));
        Selector selector = Selector.open();

        ssc.configureBlocking(false);
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            int readyNum = selector.select();
            System.out.println("已准备好的连接数:" + readyNum);

            if (readyNum > 0) {
                Set<SelectionKey> selectionKeys = selector.keys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();

                    if (selectionKey.isAcceptable()) {
                        ServerSocketChannel tmpSSC = (ServerSocketChannel) selectionKey.channel();
                        SocketChannel tmpSC = tmpSSC.accept();

                        tmpSC.configureBlocking(false);
                        tmpSC.register(selector, SelectionKey.OP_READ);
                    }

                    if (selectionKey.isConnectable()) {
                        System.out.println("建立连接");
                    }

                    ByteBuffer byteBuffer = ByteBuffer.allocate(15);
                    if (selectionKey.isReadable()) {
                        SocketChannel tmpSC = (SocketChannel) selectionKey.channel();
                        tmpSC.read(byteBuffer);

                        byte[] recvBytes = byteBuffer.array();
                        System.out.println("接收数据:" + new String(recvBytes));
                        byteBuffer.clear();
                    }

                    if (selectionKey.isWritable()) {
                        byte[] sendBytes = "data accepted".getBytes();
                        byteBuffer.put(sendBytes);
                        SocketChannel tmpSC = (SocketChannel) selectionKey.channel();
                        tmpSC.write(byteBuffer);

                        System.out.println("发送数据");
                        byteBuffer.clear();
                    }

                    // iterator.remove();
                }
            }
        }
    }
}
public class SocketClient {

    public static void main(String[] args) {
        for (int i = 0; i < 2; i++) {
            int localPort = 52998;
            new Thread(() -> {
                try (Socket socket = new Socket()) {
                    int random = (int)(Math.random() * 1000);
                    socket.bind(new InetSocketAddress(localPort + random));
                    socket.connect(new InetSocketAddress("localhost", 52991));
                    OutputStream outputStream = socket.getOutputStream();
                    byte[] buffer = "hello world".getBytes();
                    outputStream.write(buffer);
                    outputStream.flush();
                    InputStream inputStream = socket.getInputStream();
                    byte[] inputBuffer = new byte[20];

                    inputStream.read(inputBuffer);
                    System.out.println(new String(inputBuffer));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }).start();
        }
    }
}

异步IO AIO(Asynchronous IO)

异步IO模型

采用Proactor模式,该模式与Reactor模式大同小异主要区别在于,事件处理者发起一个异步读写请求,指定缓存地址、大小、回调函数之后,交给系统内核处理。

内核处理完成后通知用户线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值