BIO&NIO&AIO

简介: BIO&NIO&AIO

BIO

同步阻塞模型, 一个客户端连接对应一个处理线程

缺点

  • IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
  • 如果线程很多,会导致服务器线程太多,压力太大

应用场景

BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解

图解

BIO.png

代码

  • Server
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        for (;;){
            System.out.println("等待客户端连接....");
            //此处阻塞
            Socket socket = serverSocket.accept();
            System.out.println("有客户端连接了");
            byte[] bytes = new byte[1024];
            //此处阻塞
            int len = socket.getInputStream().read(bytes);
            if (len != -1)
                System.out.println("接收到客户端的信息:" + new String(bytes, 0, len));
            System.out.println("向客户端发送一条信息...");
            socket.getOutputStream().write("Hello Client".getBytes());
            socket.getOutputStream().flush();
            System.out.println("信息已发送");
            socket.close();
        }
    }
}
  • Client
public class BIOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8080);
        System.out.println("准备向服务端发送信息....");
        socket.getOutputStream().write("Hello Server".getBytes());
        socket.getOutputStream().flush();
        System.out.println("信息发送完毕");
        byte[] bytes = new byte[1024];
        //此处阻塞
        int len = socket.getInputStream().read(bytes);
        if (len != -1)
            System.out.println("接收到服务端返回的信息:" + new String(bytes, 0, len));
        socket.close();
        System.out.println("连接结束");
    }
}

NIO

同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理。

I/O多路复用底层一般用的Linux API(select,poll,epoll)来实现,他们的区别如下表:

select poll epoll(JDK 1.5及以上)
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO效率 线性遍历 O(n) 线性遍历 O(n) 事件通知方式O(1)
最大连接 有上限 无上限 无上限

IO效率

  • select: 每次调用都进行线性遍历,时间复杂度为O(n)
  • poll: 每次调用都进行线性遍历,时间复杂度为O(n)
  • epoll:事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1)

应用场景:

NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂, JDK1.4 开始支持

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

图解

NIO.png


1、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组

2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理

3、selector 可以对应一个或多个线程

4、NIO 的 Buffer 和 channel 都是既可以读也可以写

代码

  • Server
public class NIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //配置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        //创建一个多路复用器selector
        Selector selector = Selector.open();
        //将ServerSocketChannel注册到selector上,并且设置selector对客户端连接感兴趣
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        for(;;){
            System.out.println("等待事件发生....");
            //轮询selector的key,此处阻塞
            selector.select();
            System.out.println("有事件发生了");
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //删除本次已处理的key,防止下次select重复处理
                iterator.remove();
                handle(selectionKey);
            }
        }
    }
    public static void handle(SelectionKey selectionKey) throws IOException {
        if(selectionKey.isAcceptable()){
            System.out.println("有客户端连接事件发生了");
            //由于此处为对accept感兴趣,只有ServerSocketChannel注册到selector上时是对accept感兴趣,
            //所以这里的channel是ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            //NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
            SocketChannel socketChannel = serverSocketChannel.accept();//同BIO返回一个连接客户端的channel(socket)
            //将SocketChannel配置为非阻塞,并且对read事件感兴趣
            socketChannel.configureBlocking(false);
            socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
        }else if(selectionKey.isReadable()){
            System.out.println("有客户端数据可读事件发生了");
            //由于此处为对read感兴趣,只有SocketChannel注册到selector上时是对read感兴趣
            //所以这里的channel是SocketChannel
            SocketChannel channel = (SocketChannel) selectionKey.channel();
            //以下为读取客户端发送的数据与向客户端发送数据操作
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            //NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
            int len = channel.read(byteBuffer);
            if(len != -1)
                System.out.println("接收到客户端的数据:" + new String(byteBuffer.array(), 0, len));
            ByteBuffer bufferToWrite = ByteBuffer.wrap("Hello Client".getBytes());
            channel.write(bufferToWrite);
        }
    }
}
  • Client
public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        //此时客户端注册到selector,并且是对connect感兴趣
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 客户端连接服务器,其实方法执行并没有实现连接,需要调用channel.finishConnect()才能完成连接
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
        while (true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                if(selectionKey.isConnectable()){
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    ByteBuffer bufferToWrite = ByteBuffer.wrap("Hello Server".getBytes());
                    channel.write(bufferToWrite);
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int len = channel.read(byteBuffer);
                    if(len != -1)
                        System.out.println("接收到服务端的数据:" + new String(byteBuffer.array(), 0, len));
                }
            }
        }
    }
}

AIO

异步非阻塞, 由操作系统完成后回调通知服务端程序启动线程去处理。

应用场景:

AIO方式适用于连接数目多且连接比较长(重操作) 的架构,JDK7 开始支持

代码

  • Server
public class AIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        final AsynchronousServerSocketChannel serverChannel =
                AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                try {
                    // 再此接收客户端连接,如果不写这行代码后面的客户端连接不上服务端
                    serverChannel.accept(attachment, this);
                    System.out.println(socketChannel.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, result));
                            socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }
                        @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();
            }
        });
        Thread.sleep(Integer.MAX_VALUE);
    }
}
  • Client
public class AIOClient {
    public static void main(String... args) throws Exception {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

BIO&NIO&AIO对比

BIO NIO AIO
IO模型 同步阻塞 同步非阻塞 异步非阻塞
编程难度 简单 复杂 复杂
可靠性
吞吐量

目录
相关文章
|
1月前
|
设计模式
Lettuce的特性和内部实现问题之Netty NIO的性能优于BIO的问题如何解决
Lettuce的特性和内部实现问题之Netty NIO的性能优于BIO的问题如何解决
|
1月前
|
缓存 Java UED
BIO、NIO、AIO有什么区别
【8月更文挑战第16天】BIO、NIO、AIO有什么区别
25 4
|
29天前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
27 2
|
2月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
115 1
|
2月前
|
监控 网络协议 Java
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
44 0
|
3月前
|
Java 视频直播 数据库连接
Java I/O 模型详解:BIO、NIO 与 AIO 的特性与应用
Java I/O 模型详解:BIO、NIO 与 AIO 的特性与应用
52 2
|
3月前
|
存储 监控 Java
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
深入探索Java BIO与NIO输入输出模型:基于文件复制和socket通信
|
2月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
4月前
|
存储 监控 Java
深入探索Java语言的NIO(New I/O)技术
深入探索Java语言的NIO(New I/O)技术
|
2月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解