BIO, NIO, AIO

同步,异步,阻塞,非阻塞

  • 同步:a 事件必须等到 b 事件完成才可以继续执行/返回
  • 异步:a 事件可以先执行/返回,不需要等待 b 事件的完成,而是通过回调处理 b 事件的返回结果
  • 阻塞:当发起一次请求时,调用者一直等待结果的返回,只有当条件满足时,才继续处理后续的工作
  • 非阻塞:当发起一次请求时,调用者不用一直等待结果的返回,可以先去做其他的事情

1、BIO(Blocking IO)

同步阻塞 io 模型,一个连接一个线程,即当客户端有连接请求时就会创建一个线程去处理连接请求,会造成不必要的开销,可以通过线程池来改善

1.1、代码示例
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class BioServer {

    public static void main(String[] args) throws IOException {
        // 创建 server
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务器启动");
        while (true){
            // 主线程等待客户端的连接
            System.out.println(Thread.currentThread().getName() + " 等待客户端连接");
            // 等待客户端连接
            Socket socket = serverSocket.accept();
            // 创建一个线程处理当前请求
            ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
            newCachedThreadPool.execute(() -> {
                // 处理连接
                try {
                    System.out.println("客户端 " + Thread.currentThread().getName() + " 已连接");
                    handler(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

        }
    }

    private static void handler(Socket socket) throws IOException {
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        System.out.println("等待客户端" + Thread.currentThread().getName() + "发送数据");
        int read = inputStream.read(bytes);
        System.out.println("客户端 " + Thread.currentThread().getName() + " 发送数据");
        while (read != -1){
            System.out.println(new String(bytes));
            read = inputStream.read(bytes);
        }
    }
}

1.2、问题
  • 当有大量客户端连接时,就需要创建大量的线程,资源占用较大
  • 连接建立后如果没有当前线程暂时没有可读内容,就会阻塞,accept,read,write 都会阻塞线程

2、NIO(non-blocking IO)

同步非阻塞 io 模型,面向缓冲区

三大核心部分:

2.1、selector(多路复用器)

能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来, 只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程, 不用去维护
多个线程,并且避免了多线程之间的上下文切换导致的开销。

2.2、buffer(缓冲区)

一个可以读写的内存块,底层是数组实现的,在 BIO 中是采用 stream 流的方式进行读写,在 NIO 中读取时直接从 buffer 中读取,发送时直接写入 buffer 中

2.3、channel(通道)

NIO 是通过 channel 进行读写,读写可以在一个通道进行,而 BIO 中的流只能读或者写,常用的 channel 有 FileChannel,ServerSocketChannel 相当于 BIO 中的 serverSocket,SocketChannel 相当于 BIO 中的 socket

2.4、代码

服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * 服务器端
 */
public class NioServer {

    public static void main(String[] args) throws IOException {
        // 获取服务器通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(6666));
        System.out.println("服务器已经启动");
        // 获取选择器
        Selector selector = Selector.open();
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        // 将服务器通道注册到选择器中
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("将服务器通道注册到选择器中");
        while (true){
            // 没有事件发生
            if (selector.select(1000) == 0) {
                continue;
            }
            // 获取所有的已选择的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 拿到 selectionKeys 的迭代器
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
//            keyIterator.forEachRemaining();
            while (keyIterator.hasNext()){
                SelectionKey selectionKey = keyIterator.next();
                // 是连接事件
                if (selectionKey.isAcceptable()){
                    // 获取客户端通道
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    // 将客户端通道注册到选择器上
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接成功");
                }
                // 读取事件
                if (selectionKey.isReadable()){
                    // 获取当前事件的缓冲区对象
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    // 获取当前事件对应的通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    System.out.println("读取之前缓冲区的数据:" + new String(byteBuffer.array()));
                    // 将通道中的数据读取到缓冲区中
                    channel.read(byteBuffer);
                    System.out.println("读取之后缓冲区的数据:" + new String(byteBuffer.array()));
                    byteBuffer.clear();
                }

                keyIterator.remove();
            }
        }
    }
}

客户端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioClient {

    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 6666));
        socketChannel.configureBlocking(false);
        String str = "hello word";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
    }
}

3、AIO(Asynchronous IO)

在 io 编程中,常用有俩种模式,reactor 和 proactor,NIO 就是 reactor,当有事件触发时,服务端得到通知,进行相应的处理,AIO 是异步非阻塞,采用 Proactor 模式,有效的请求才启动线程

4、不同 io 模型的对比

下图来源于 <<netty 权威指南>>
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值