三十八

  • ByteBuffer:NIO的数据传输是基于缓冲区的, ByteBuffer正是NIO数据传输中所使用的缓冲区抽象. ByteBuffer支持在堆外分配内存. 一般的I/O操作, 都需要进行系统调用,这样会先切换到内核态,内核态要先从文件读取数据到它的缓冲区,只有等数据准备完毕后,才会从内核态把数据写到用户态,所谓的阻塞IO其实就是说的在等待数据准备好的这段时间内进行阻塞.

  • Channel:它类似于文件描述符,简单地来说它代表了一个实体(如一个硬件设备、文件、Socket或者一个能够执行一个或多个不同的I/O操作的程序组件)。你可以从一个Channel中读取数据到缓冲区,也可以将一个缓冲区中的数据写入到Channel。

  • Selector:选择器是NIO实现的关键,NIO采用的是I/O多路复用的方式来实现非阻塞,Selector通过在一个线程中监听每个Channel的IO事件来确定有哪些已经准备好进行IO操作的Channel,因此可以在任何时间检查任意的读操作或写操作的完成状态。这种方式避免了等待IO操作准备数据时的阻塞,使用较少的线程便可以处理许多连接,减少了线程切换与维护的开销。

  1. Unix中五种I/O模型
  • 阻塞I/O模型 是最常见的I/O模型,通常我们使用的InputStream/OutputStream都是基于阻塞I/O模型。在上图中,我们使用UDP作为例子,recvfrom()函数是UDP协议用于接收数据的函数,它需要使用系统调用并一直阻塞到内核将数据准备好,之后再由内核缓冲区复制数据到用户态(即是recvfrom()接收到数据),所谓阻塞就是在等待内核准备数据的这段时间内什么也不干。 就像去餐厅吃饭等饭做好的时间段中,什么都做不了只能干等.

  • 非阻塞I/O模型 内核在数据尚未准备好的情况下回返回一个错误码EWOULDBLOCK,而recvfrom并没有在失败的情况下选择阻塞休眠,而是不断地向内核询问是否已经准备完毕,在上图中,前三次内核都返回了EWOULDBLOCK,直到第四次询问时,内核数据准备完毕,然后开始将内核中缓存的数据复制到用户态。这种不断询问内核以查看某种状态是否完成的方式被称为polling(轮询)。 即点了饭后不干等,而是不断去问好了没.

  • I/O多路复用 其思想跟非阻塞I/O是一样的,只不过在非阻塞I/O中,是在recvfrom的用户态(或一个线程)中去轮询内核,这种方式会消耗大量的CPU时间。而I/O多路复用则是通过select()或poll()系统调用来负责进行轮询,以实现监听I/O读写事件的状态。如上图中,select监听到一个datagram可读时,就交由recvfrom去发送系统调用将内核中的数据复制到用户态。这种方式的优点很明显,通过I/O多路复用可以监听多个文件描述符,且在内核中完成监控的任务。但缺点是至少需要两个系统调用(select()与recvfrom())。

Unix中提供了两种I/O多路复用函数,select()和poll()。 后来推出了更高效的实现叫epoll(),其采用了事件驱动的方式而不是轮询

  • 信号驱动I/O模型 使用到了信号,内核在数据准备就绪时会通过信号来进行通知

  • 异步I/O模型 同样依赖于信号处理程序来进行通知,但与以上I/O模型都不相同的是,异步I/O模型通知的是I/O操作已经完成,而不是数据准备完成。

我们之前讨论的各种I/O模型无论是阻塞还是非阻塞,它们所说的阻塞都是指的数据准备阶段. 可以说异步I/O模型才是真正的非阻塞,主进程只管做自己的事情,然后在I/O操作完成时调用回调函数来完成一些对数据的处理操作即可.

ByteBuf 在Java NIO中提供了ByteBuffer作为字节缓冲区容器,但该类的API使用起来不太方便,所以Netty实现了ByteBuf作为更方便的容器

你可能感兴趣的:(三十八)