你知道从BIO到NIO的进化史吗?

IO是什么

IO可以简单地理解为是计算机与硬盘之间进行通讯一种方式,通过输入输出流来进行数据的交互。在java中存在了几种IO的接口,如常见的字节流和字符流。

  • InputStream :字节输入流
  • OutputStream :字节输出流
  • Reader :字符输入流
  • Writer :字符输出流

在java应用程序层面通过调用native方法调用操作系统,从硬盘中或者internet中进行对数据的存取操作。

IO的几种类型

在IO的发展史中大致存在了以下几种类型的IO操作。
在java发展之初性能并不需要维持很大的并发量,因此原始的IO是一种同步并阻塞的IO。在程序=>操作系统=>硬盘层面只能保证一个线程的操作,并不允许多个线程同时进行连接。
随着业务的发展与性能要求的提升,逐渐演变出了一种通过线程池创建的多线程连接,即同步非阻塞的IO,此时会有一个守护线程持续不断的访问硬件层面是否返回成功,返回成功则告诉应用程序。
后来由于同步仍然存在部分的阻塞,又演变为一种异步非阻塞的IO,即NIO,此时会将所有连接都阻塞在一个单线程的队列中,开启一个线程来判断是否需要通信,如果需要则修改连接状态进行通讯,并异步返回。

同步并阻塞的BIO

首先我们来看看初始版本的BIO,只通过一个线程进行同步并阻塞的BIO。

public class BioServer {
    public static void main(String[] args) {
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            System.out.println("BioServer has started。Listener on port:"+serverSocket.getLocalSocketAddress());
            while (true){
                Socket clientSocket = serverSocket.accept();
                System.out.println("Connection from "+clientSocket.getRemoteSocketAddress());
                try (Scanner input = new Scanner(clientSocket.getInputStream())){
                    while (true){
                        String request = input.nextLine();
                        if("quit".equals(request)){
                            break;
                        }
                        System.out.println(String.format("From %s : %s",clientSocket.getRemoteSocketAddress(),request));
                        String response = "From BioServer Hello "+request+". \n";
                        clientSocket.getOutputStream().write(response.getBytes());
                    }
                }
            }
        }catch (Exception e){

        }
    }
}

此时当一个连接占据时其他连接都无法获取socket连接,造成的极大地不便,当多个用户进行操作时效率会变得很低。

通过线程池实现同步非阻塞的BIO

此时为了解决多用户的连接请求,演化出线程池实现多个socket连接。

//处理客户端请求数据
public class RequestHandler {
    public String handle(String request){
        return  "From BioServer Hello "+request+". \n";
    }
}

public class BioServerThreadPool {
    public static void main(String[] args) {
        ExecutorService executor= Executors.newFixedThreadPool(3);
        RequestHandler requestHandler = new RequestHandler();
        try(ServerSocket serverSocket = new ServerSocket(9999)){
            System.out.println("BioServer has started。Listener on port:"+serverSocket.getLocalSocketAddress());
            while (true) {
                Socket clientSocket = serverSocket.accept();
                executor.submit(new ClientHandler(clientSocket,requestHandler));
                System.out.println("Connection from " + clientSocket.getRemoteSocketAddress());
            }
        } catch (Exception e) {

        }
    }
}

public class ClientHandler implements  Runnable {
    private final Socket clientSocket;
    private final RequestHandler requestHandler;
    public ClientHandler(Socket clientSocket, RequestHandler requestHandler) {
        this.clientSocket = clientSocket;
        this.requestHandler = requestHandler;
    }

    @Override
    public void run() {
        try (Scanner input = new Scanner(clientSocket.getInputStream())){
            while (true){
                String request = input.nextLine();
                if("quit".equals(request)){
                    break;
                }
                System.out.println(String.format("From %s : %s",clientSocket.getRemoteSocketAddress(),request));
                String response = requestHandler.handle(request);
                clientSocket.getOutputStream().write(response.getBytes());
            }
        }catch (Exception e){
            throw new RuntimeException();
        }
    }
}

此时虽然使用了多线程,但是线程池的数量仍然是一个不好处理的东西,多了可能系统硬件承受不住,少了可能会造成溢出,或者无法合理运用系统资源,而选择一个合适的线程池数量则是一个头疼的问题,与此同时线程持续的进行频繁交互,并且在读取数据时会造成部分阻塞,也会造成性能问题,怎么样解决这些问题呢?

异步非阻塞的NIO的实现

基于以上讨论,出现了一种异步非阻塞的NIO模式。
这种方式会将所有连接阻塞在一个叫Channel的队列中,并将状态设置为accept,此时会用一个守护线程selector持续轮询,如果某个连接需要进行IO操作,则会将此连接状态修改为read或者write进行读写,同时从队列中弹出符合条件的连接进行IO操作,以此达到异步非阻塞的效果。

public class NioServer {
    public static void main(String[] args) throws Exception {
        //01、创建一个服务端Channel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        //设置为非阻塞
        serverChannel.configureBlocking(false);
        //serverchannel需要绑定一个端口
        serverChannel.bind(new InetSocketAddress(6666));
        System.out.println("BioServer has started。Listener on port:"+serverChannel.getLocalAddress());
        //02、Selector:专门用来进行轮询,判断socket的状态
        Selector selector = Selector.open();
        //将一个个channel注册到Selector,channel初始状态
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        //ByteBuffer进行数据临时存储
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        RequestHandler requestHandler = new RequestHandler();

        //对selector里面的channel进行轮询,判断谁需要后续的IO操作
        while(true){
            int select = selector.select();
            if (select==0){
                continue;
            }
            //selector中有channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                //selectionKey中保存了channel的各种信息
                SelectionKey key = iterator.next();
                //假如channel的状态是readable
                if(key.isAcceptable()){
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = channel.accept();
                    System.out.println("Connection from"+clientChannel.getRemoteAddress());
                    clientChannel.configureBlocking(false);
                    //将channel改变状态:read、write
                    clientChannel.register(selector,SelectionKey.OP_READ);
                }
                if (key.isReadable()){
                    //数据交互
                    SocketChannel channel = (SocketChannel) key.channel();
                    //将客户端的数据读取到buffer中
                    channel.read(buffer);
                    String request = new String(buffer.array()).trim();
                    buffer.clear();
                    System.out.println(String.format("From %s : %s",channel.getRemoteAddress(),request));
                    String response = requestHandler.handle(request);
                    channel.write(ByteBuffer.wrap(response.getBytes()));
                }
                iterator.remove();
            }
        }
    }
}

NIO的使用场景

因为NIO的性能比较高效,一些框架会基于NIO进行api的封装,典型的有Netty,而Dubbo的rpc协议又是基于Netty进行封装的。因此NIO在一些频繁IO操作高性能框架中使用的还是挺多的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值