I/O 类型

  • 阻塞 I/O:用户线程发起 I/O 操作后,内核在检查数据过程中用户线程一直阻塞,直到内核将数据复制到用户线程;

    1. data = socket.read();
  • 非阻塞 I/O:用户线程发起 I/O 操作后,可以马上得到内核返回,若返回值说明数据未到则稍后用户线程再发起 I/O 操作,用户线程需要轮询内核;

    1. while(true) {
    2. data = socket.read();
    3. if(data == true) {
    4. // 内核数据已来,处理数据
    5. break;
    6. } else {
    7. // 内核数据未来,用户线程可以进行其他工作
    8. }
    9. }
  • 多路复用 I/O:使用一个 Selector 线程在内核中不断轮询,在有 I/O 数据来的时候才通知用户线程进行 I/O 操作,由于只管理 I/O 的轮询,因此业务操作转发给其他线程处理;

  • 信号驱动 I/O:用户线程发起 I/O 操作后,为该 socket 注册一个信号,内核准备好数据后发送信号到用户线程来调用 I/O 中的数据;
  • 异步 I/O:用户线程发起 I/O 操作后,内核直接返回一个状态说明是否请求成功,之后是信号驱动 I/O 的流程;由于发起请求和数据读取都在内核中完成,期间用户线程不需要等待。

    Java NIO

    NIO 与 I/O 区别

  • I/O 面向 Stream,NIO 面向 Buffer:Stream 中无法前后移动数据; NIO 是从 Channel 读到 Buffer 或者从 Buffer 写入 Channel 的过程,可以前后移动数据,避免粘包、拆包等情况,在网络不可靠情况下好用;

  • I/O 阻塞式,NIO 非阻塞式:I/O 调用 write()read() 时需要阻塞;NIO 通过 Selector 监听 Channel 上的变化,有数据时使用 Buffer 读,没数据时执行其他操作,写数据时异步将 Buffer 的数据写到 Channel 上,用户线程无需等待。

    NIO 核心

    Selector

    核心是多路复用 I/O 中的 Selector 线程,监听的是 Channel 上的事件(连接打开或数据到达)。
    image.png
    Selector 工作模式

    Channel 与 Buffer

    Buffer 是个容器,用于存储 Channel 来的数据和将数据发到双向流 Channel,C/S 架构中的流程是:Client -> ClientBuffer -> ClientChannel -> ServerChannel -> ServerBuffer -> Server。

  • Channel:

    • UDP:DatagramChannel
    • TCP:SocketChannel(客户端)和 ServerSocketChannel(服务端);
    • 文件:FileChannel
  • Buffer:ByteBufferIntBufferCharBufferLongBufferDoubleBufferFloatBufferShortBuffer(基本数据类型的 Buffer)。

image.png
Channel 与 Buffer 工作模式

Reactor

单线程模型

image.png
Reactor 单线程模型

  • 模块
    • Client:NIO 客户端,向服务器发起 TCP 连接并发送数据;
    • Acceptor:与 Client 建立连接;
    • Dispatcher:接收 Client 数据并以 ByteBuffer 的形式发送到 DecodeHandler;
    • DecodeHandler:解码器,读取 Client 的数据,对消息进行解码、处理、应答;
    • EncodeHandler:编码器,将返回给 Client 的数据编码并写入 Channel。
  • 流程
    1. Acceptor 接收 Client 的 TCP 请求并建立连接;
    2. Dispatcher 将收到的消息写入 ByteBuffer 并分配到 DecodeHandler 进行处理;
    3. 消息处理后使用 EncodeHandler 将响应消息返回。
  • 总结
    • Dispatcher 与 Handler 之间的交互就是 Channel 与 Buffer 的交互流程。

      多线程模型

      image.png
      Reactor 多线程模型
      将单线程模型的 Dispatcher 由原来的单线程执行,变为使用线程池实现 Dispatcher 的任务,可以处理更多 Client 的请求。

      主从线程模型

      image.png
      Reactor 主从线程模型
      将多线程模型的 Acceptor 也使用线程池实现,并且只负责建立连接,连接建立成功后在 SubReactor 的线程池的某个线程中注册一个 SocketChannel,后续该线程负责 I/O 操作,这样连接建立和消息响应都是异步的。