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月前
|
网络协议 Dubbo Java
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
本文详细解析了NIO、AIO、BIO的核心区别,NIO的三个核心概念,以及NIO在Java框架中的应用等。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
|
29天前
|
Java
BIO、NIO、AIO 有什么区别
BIO(阻塞I/O)模型中,服务器实现模式为一个连接一个线程;NIO(非阻塞I/O)使用单线程或少量线程处理多个请求;AIO(异步I/O)则是在NIO基础上进一步优化,采用事件通知机制,提高并发处理能力。
52 5
|
29天前
|
消息中间件 监控 Java
BIO、NIO、AIO在不同场景下的应用对比
BIO(阻塞I/O)、NIO(非阻塞I/O)和AIO(异步I/O)是Java中处理I/O操作的三种模式。BIO适用于连接数少且稳定的场景;NIO通过非阻塞模式提高并发处理能力,适合高并发场景;AIO则完全异步,适合需要高效、低延迟的I/O操作场景。
98 4
|
3月前
|
Java
Netty BIO/NIO/AIO介绍
Netty BIO/NIO/AIO介绍
|
2月前
|
Java Linux 应用服务中间件
【编程进阶知识】高并发场景下Bio与Nio的比较及原理示意图
本文介绍了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO采用传统的线程模型,每个连接请求都会创建一个新线程进行处理,导致在高并发场景下存在严重的性能瓶颈,如阻塞等待和线程创建开销大等问题。而NIO则通过事件驱动机制,利用事件注册、事件轮询器和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,显著提升了系统的并发处理能力。
78 0
|
4月前
|
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操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
99 2
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
5月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解
|
5月前
|
Java 大数据
如何在Java中进行网络编程:Socket与NIO
如何在Java中进行网络编程:Socket与NIO
|
5月前
|
Java
Java中的NIO编程详解
Java中的NIO编程详解