美文网首页
Java基础-IO

Java基础-IO

作者: 16325 | 来源:发表于2020-02-17 10:49 被阅读0次

概念

  • IO指的是输入输出,主要是指文件的IO和网络的IO。
  • 对于java程序来说,分成BIO,NIO,AIO。
  • 各种IO的操作流程:
    1.向操作系统请求数据。
    2.操作系统将外部数据复制到内核缓冲区。
    3.操作系统将缓冲区数据复制到进程缓冲区。
    4.进程获取数据,完成IO操作。

BIO

  • BIO是最早的阻塞IO,是同步阻塞IO,对应操作系统调用socket的函数,这些函数都是阻塞的,所以在BIO中,读取文件,等待网络连接,等到socket数据,这些操作统统都是阻塞的。

NIO

  • 因为BIO的阻塞特性,所以每次网络操作,包括等待连接,获取数据,发送数据,都是阻塞的,给变成带来很大麻烦,只能是针对每一个网络客户端创建新的线程来处理,带来了严重的系统负担。
  • NIO是基于操作的系统的select函数,linux的poll函数进行调用,针对于IO操作流程的第二点和第三点,这两部操作对于java进程来说,都是非阻塞的。所以可以实现为:向 系统注册了感兴趣的事件后,只要检查这些事件是否完成,即可得到所需的数据。
  • 这时候可能读者要问:在我们写NIO的时候,也需要while(true)啊,如下代码:
            Selector selector = Selector.open();
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(8090));
            serverSocketChannel.configureBlocking(false);
            //向通道注册选择器,并且注册接受事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                //获取已经准备好的通道数量
                int readyChannels = selector.selectNow();
                //如果没准备好,重试
                if (readyChannels == 0) continue;
                //获取准备好的通道中的事件集合
                Set selectedKeys = selector.selectedKeys();
                Iterator keyIterator = selectedKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = (SelectionKey) keyIterator.next();
                    if (key.isAcceptable()) {
                         这里注册的是accept事件,
                    } else if (key.isConnectable()) {
                    } else if (key.isReadable()) {
                    } else if (key.isWritable()) {
                    }
                    //注意每次迭代末尾的keyIterator.remove()调用。
                    //Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。
                    //下次该通道变成就绪时,Selector会再次将其放入已选择键集中
                    keyIterator.remove();
                }
            }
 

这里第一个while(true)难道不会把进程阻塞起来?
其实这里的阻塞和操作系统调用时候的阻塞并不是同一个含义,NIO的非阻塞指的是客户端链接,客户端数据的读写这些操作是非阻塞的,也就是说我们的java进程不需要在等待客户端链接的时候阻塞,而是有客户端链接之后,我们的进程就会可以拿到这个链接,注意这里是可以拿到,而并不是已经拿到,并没有回调的功能,如果注册了回调,客户端链接上来我们的回调函数执行,这就是AIO的范畴了。
所以,我们可以拿到链接,可以拿到客户端发来的数据,可以写数据。但是这只是可以,具体的拿这个操作,还是需要自己完成的。
这里可以想象一个极端的例子,客户端链接成功能后,五分钟内肯定会发来一条消息,那么我们真的可以不要代码中的while(true),而是等五分钟后,直接从selector中获取数据。

  • 如果像想使用NIO一样使用BIO呢? 等待连接是阻塞的,这里没办法了。链接上来之后保存这些链接,每个链接一个线程,然后阻塞的read,哪个线程read完成了,告诉另一个控制线程说我读完了。然后控制线程while(true)轮询,拿到各个客户端的数据。这里的每个客户端线程的阻塞read,read之后的挂载数据,其实就是NIO中操作系统代替我们完成的工作

AIO

  • 根据NIO的while(true), 如果不需要我们自己拿已完成的事件和数据,而是提供回调函数,等客户端链接完成或者数据收到,自动执行回调函数,这就是AIO了。这时候就真的是异步操作了。

Reactor模型

  • 经过之前的基础概念的说明,可以看出NIO使用一个线程就可以完成所有感兴趣事件的注册和轮询了,那么NIO该如果设计和优化我们的服务端代码呢?
  • 自POSA2 中的关于Reactor Pattern 介绍中,我们了解了Reactor 的处理方式:

1.同步的等待多个事件源到达(采用select()实现)
2.将事件多路分解以及分配相应的事件服务进行处理,这个分派采用server集中处理(dispatch)
3.分解的事件以及对应的事件服务应用从分派服务中分离出去(handler)

  • 为何要是用reactor模型?
    先来看一段NIO的代码:
public NIOServer(int port) throws Exception {
      selector = Selector.open();
      serverSocket = ServerSocketChannel.open();
      serverSocket.socket().bind(new InetSocketAddress(port));
      serverSocket.configureBlocking(false);
      serverSocket.register(selector, SelectionKey.OP_ACCEPT);
  }
​
  @Override
  public void run() {
      while (!Thread.interrupted()) {
          try {
              //阻塞等待事件
              selector.select();
              // 事件列表
              Set selected = selector.selectedKeys();
              Iterator it = selected.iterator();
              while (it.hasNext()) {
                  it.remove();
                  //分发事件
                  dispatch((SelectionKey) (it.next()));
              }
          } catch (Exception e) {
​
          }
      }
  }
​
  private void dispatch(SelectionKey key) throws Exception {
      if (key.isAcceptable()) {
          register(key);//新链接建立,注册
      } else if (key.isReadable()) {
          read(key);//读事件处理
      } else if (key.isWritable()) {
          wirete(key);//写事件处理
      }
  }
​
  private void register(SelectionKey key) throws Exception {
      ServerSocketChannel server = (ServerSocketChannel) key
              .channel();
      // 获得和客户端连接的通道
      SocketChannel channel = server.accept();
      channel.configureBlocking(false);
      //客户端通道注册到selector 上
      channel.register(this.selector, SelectionKey.OP_READ);
  }

我们可以看到上述的NIO例子已经差不多拥有reactor的影子了:

1.基于事件驱动-> selector(支持对多个socketChannel的监听)
2.统一的事件分派中心-> dispatch
3.事件处理服务-> read & write

  • 事实上NIO已经解决了上述BIO暴露的一些问题了,服务器的并发客户端有了量的提升,不再受限于一个客户端一个线程来处理,而是一个线程可以维护多个客户端(selector 支持对多个socketChannel 监听)。但这依然不是一个完善的Reactor Pattern ,首先Reactor 是一种设计模式,好的模式应该是支持更好的扩展性,显然以上的并不支持,另外好的Reactor Pattern 必须有以下特点:

1.更少的资源利用,通常不需要一个客户端一个线程
2.更少的开销,更少的上下文切换以及locking
3.能够跟踪服务器状态
4.能够管理handler 对event的绑定

那么好的Reactor Pattern应该是怎样的?
首先我们基于Reactor Pattern 处理模式中,定义以下三种角色:

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

单Reactor单线程模型

image.png
/**
    * 等待事件到来,分发事件处理
    */
  class Reactor implements Runnable {
​
      private Reactor() throws Exception {
​
          SelectionKey sk =
                  serverSocket.register(selector,
                          SelectionKey.OP_ACCEPT);
          // attach Acceptor 处理新连接
          sk.attach(new Acceptor());
      }
​
      public void run() {
          try {
              while (!Thread.interrupted()) {
                  selector.select();
                  Set selected = selector.selectedKeys();
                  Iterator it = selected.iterator();
                  while (it.hasNext()) {
                      it.remove();
                      //分发事件处理
                      dispatch((SelectionKey) (it.next()));
                  }
              }
          } catch (IOException ex) {
              //do something
          }
      }
​
      void dispatch(SelectionKey k) {
          // 若是连接事件获取是acceptor
          // 若是IO读写事件获取是handler
          Runnable runnable = (Runnable) (k.attachment());
          if (runnable != null) {
              runnable.run();
          }
      }
​
  }
  /**
    * 连接事件就绪,处理连接事件
    */
  class Acceptor implements Runnable {
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注册读写
                  new Handler(c, selector);
              }
          } catch (Exception e) {
​
          }
      }
  }
  /**
    * 处理读写业务逻辑
    */
  class Handler implements Runnable {
      public static final int READING = 0, WRITING = 1;
      int state;
      final SocketChannel socket;
      final SelectionKey sk;
​
      public Handler(SocketChannel socket, Selector sl) throws Exception {
          this.state = READING;
          this.socket = socket;
          sk = socket.register(selector, SelectionKey.OP_READ);
          sk.attach(this);
          socket.configureBlocking(false);
      }
​
      @Override
      public void run() {
          if (state == READING) {
              read();
          } else if (state == WRITING) {
              write();
          }
      }
​
      private void read() {
          process();
          //下一步处理写事件
          sk.interestOps(SelectionKey.OP_WRITE);
          this.state = WRITING;
      }
​
      private void write() {
          process();
          //下一步处理读事件
          sk.interestOps(SelectionKey.OP_READ);
          this.state = READING;
      }
​
      /**
        * task 业务处理
        */
      public void process() {
          //do something
      }
  }

这是最基本的单Reactor单线程模型。其中Reactor线程,负责多路分离套接字,有新连接到来触发connect 事件之后,交由Acceptor进行处理,有IO读写事件之后交给hanlder 处理。Acceptor主要任务就是构建handler ,在获取到和client相关的SocketChannel之后 ,绑定到相应的hanlder上,对应的SocketChannel有读写事件之后,基于reactor 分发,hanlder就可以处理了(所有的IO事件都绑定到selector上,有Reactor分发)。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。

单Reactor多线程模型

image.png
/**
    * 多线程处理读写业务逻辑
    */
  class MultiThreadHandler implements Runnable {
      public static final int READING = 0, WRITING = 1;
      int state;
      final SocketChannel socket;
      final SelectionKey sk;
​
      //多线程处理业务逻辑
      ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
​
​
      public MultiThreadHandler(SocketChannel socket, Selector sl) throws Exception {
          this.state = READING;
          this.socket = socket;
          sk = socket.register(selector, SelectionKey.OP_READ);
          sk.attach(this);
          socket.configureBlocking(false);
      }
​
      @Override
      public void run() {
          if (state == READING) {
              read();
          } else if (state == WRITING) {
              write();
          }
      }
​
      private void read() {
          //任务异步处理
          executorService.submit(() -> process());
​
          //下一步处理写事件
          sk.interestOps(SelectionKey.OP_WRITE);
          this.state = WRITING;
      }
​
      private void write() {
          //任务异步处理
          executorService.submit(() -> process());
​
          //下一步处理读事件
          sk.interestOps(SelectionKey.OP_READ);
          this.state = READING;
      }
​
      /**
        * task 业务处理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }

相对于第一种单线程的模式来说,在处理业务逻辑,也就是获取到IO的读写事件之后,交由线程池来处理,这样可以减小主reactor的性能开销,从而更专注的做事件分发工作了,从而提升整个应用的吞吐。

多Reactor多线程模型

image.png
/**
    * 多work 连接事件Acceptor,处理连接事件
    */
  class MultiWorkThreadAcceptor implements Runnable {
​
      // cpu线程数相同多work线程
      int workCount =Runtime.getRuntime().availableProcessors();
      SubReactor[] workThreadHandlers = new SubReactor[workCount];
      volatile int nextHandler = 0;
​
      public MultiWorkThreadAcceptor() {
          this.init();
      }
​
      public void init() {
          nextHandler = 0;
          for (int i = 0; i < workThreadHandlers.length; i++) {
              try {
                  workThreadHandlers[i] = new SubReactor();
              } catch (Exception e) {
              }
​
          }
      }
​
      @Override
      public void run() {
          try {
              SocketChannel c = serverSocket.accept();
              if (c != null) {// 注册读写
                  synchronized (c) {
                      // 顺序获取SubReactor,然后注册channel 
                      SubReactor work = workThreadHandlers[nextHandler];
                      work.registerChannel(c);
                      nextHandler++;
                      if (nextHandler >= workThreadHandlers.length) {
                          nextHandler = 0;
                      }
                  }
              }
          } catch (Exception e) {
          }
      }
  }
  /**
    * 多work线程处理读写业务逻辑
    */
  class SubReactor implements Runnable {
      final Selector mySelector;
​
      //多线程处理业务逻辑
      int workCount =Runtime.getRuntime().availableProcessors();
      ExecutorService executorService = Executors.newFixedThreadPool(workCount);
​
​
      public SubReactor() throws Exception {
          // 每个SubReactor 一个selector 
          this.mySelector = SelectorProvider.provider().openSelector();
      }
​
      /**
        * 注册chanel
        *
        * @param sc
        * @throws Exception
        */
      public void registerChannel(SocketChannel sc) throws Exception {
          sc.register(mySelector, SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
      }
​
      @Override
      public void run() {
          while (true) {
              try {
              //每个SubReactor 自己做事件分派处理读写事件
                  selector.select();
                  Set<SelectionKey> keys = selector.selectedKeys();
                  Iterator<SelectionKey> iterator = keys.iterator();
                  while (iterator.hasNext()) {
                      SelectionKey key = iterator.next();
                      iterator.remove();
                      if (key.isReadable()) {
                          read();
                      } else if (key.isWritable()) {
                          write();
                      }
                  }
​
              } catch (Exception e) {
​
              }
          }
      }
​
      private void read() {
          //任务异步处理
          executorService.submit(() -> process());
      }
​
      private void write() {
          //任务异步处理
          executorService.submit(() -> process());
      }
​
      /**
        * task 业务处理
        */
      public void process() {
          //do IO ,task,queue something
      }
  }
​

第三种模型比起第二种模型,是将Reactor分成两部分:

  1. mainReactor负责监听server socket,用来处理新连接的建立,将建立的socketChannel指定注册给subReactor。
  2. subReactor维护自己的selector, 基于mainReactor 注册的socketChannel多路分离IO读写事件,读写网 络数据,对业务处理的功能,另其扔给worker线程池来完成。

第三种模型中,我们可以看到,mainReactor 主要是用来处理网络IO 连接建立操作,通常一个线程就可以处理,而subReactor主要做和建立起来的socket做数据交互和事件业务处理操作,它的个数上一般是和CPU个数等同,每个subReactor一个县城来处理。此种模型中,每个模块的工作更加专一,耦合度更低,性能和稳定性也大量的提升,支持的可并发客户端数量可达到上百万级别。关于此种模型的应用,目前有很多优秀的矿建已经在应用了,比如mina 和netty 等。上述中去掉线程池的第三种形式的变种,也 是Netty NIO的默认模式。下一节我们将着重讲解netty的架构模式。

相关文章

  • Java NIO

    一、基础概念 Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java ...

  • 一文带你精通Java I/O流,分分钟吊打面试官!

    Java IO概述 IO就是输入/输出。Java IO类库基于抽象基础类InputStream和OutputStr...

  • java IO入门笔记

    1.java IO流的概念,分类,类图 1.1. java IO 流的概念 java的io是实现输入和输出的基础,...

  • Java IO详解

    1 Java IO流的概念,分类 1.1 Java IO流的概念 java的IO是实现输入和输出的基础,可以方便的...

  • 初步接触 Java Net 网络编程

    本文目的是大概了解 Java 网络编程体系,需要一点点 Java IO 基础,推荐教程 系统学习 Java IO。...

  • 系统学习 Java IO (三)----File

    目录:系统学习 Java IO---- 目录,概览 Java IO API 中的 File 类可以访问基础文件系统...

  • java基础——IO

    I/O作为作为人机交互的核心问题,很多web应用系统的瓶颈都是I/O瓶颈。本文主要总结java的I/O类库基本架构...

  • Java IO 基础

    一、File 类 java.io.File 类:文件和目录路径名的抽象表示形式。通过File对象可以访问文件的属性...

  • java IO基础

    1.IO即BIO Input Output 以内存角度定义向内存中写数据Input对应方法read()从内存中读取...

  • Java基础-IO

    概念 IO指的是输入输出,主要是指文件的IO和网络的IO。 对于java程序来说,分成BIO,NIO,AIO。 各...

网友评论

      本文标题:Java基础-IO

      本文链接:https://www.haomeiwen.com/subject/wwfbfhtx.html