Unix中的I/O模型和Java NIO
Unix网络编程中的五种I/O模型
阻塞式I/0、非阻塞I/O、I/O复用、信号驱动式I/O、异步I/O
以一个网络输入为例,一个输入操作通常包括两个不同的阶段:
等待数据准备好,将数据从内核拷贝到进程中。具体过程参考下面的图。
阻塞式I/O
默认情况下的I/O模型都是阻塞式I/O,应用进程从调用recvfrom开始到它返回的整个时间段内都是被阻塞的。此也就是说,应用程序进程投入睡眠,内核在检查到有数据准备好之后,将数据从内核复制到进程后才返回。
非阻塞I/O
将套接字设置为非阻塞会通知内核:当所请求的I/O操作要把本进程投入睡眠才能完成时,不要投入睡眠,而是返回一个错误。如图,当用户进程读取数据时而内核没有可读数据时,用户进程并没有阻塞,而是立刻返回EWOULDBLOCK,这样通过轮询返回值,当没有数据准备好,就继续调用recvfrom,当内核准备好了数据,并且再次收到请求数据的系统调用时,就会将数据复制到进程。
I/O复用
阻塞在select或者poll这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用之上。
从图中可以看出,用户进程阻塞在select之上,等到有可读的套接字,就可以调用recvfrom将数据复制到进程缓冲区。
和阻塞式比较起来,I/O复用并没有什么优势,相反还优劣势,因为涉及到两次系统调用,然而使用select的优势在于可以单线程管理多个套接字(描述符)连接。
传统的监控多个socket的解决方案是为每个socket创建一个线程,并使得线程可以再read调用中阻塞,直到数据可用。这事实上将每个被阻塞的线程当作了socket监控器,并将Java 虚拟机的线程调度当作了通知机制。这两者本来都不是为了这种目的而设计的。程序员和Java 虚拟机都为管理所有这些线程的复杂性和性能损耗付出了代价,这在线程数量的增长失控时表现得更为突出。由于开启多个会造成资源的大量浪费。由于网络延迟的原因,同时在处理socket的用户线程往往比实际的socket数量要少很多,然而由于线程数量太多,cpu来回切换却要花费过多的资源。
Java 1.4中首次加入NIO,就是一种I/O复用,使用的selector选择器。
信号驱动式I/O
使用信号,让内核在socket就绪时发送SIGIO信号通知我们。开启套接字的信号驱动式I/O功能。并通过sigaction系统调用安装一个信号处理函数(回调函数),该系统调用立刻返回。随后就可以在信号处理函数中调用recvfrom读取到数据。
异步I/O
当用户进程调用read内核即使没有数据准备好也会立刻返回,并不会阻塞用户进程,然后内核在整个操作完成后再通知进程,通过回调函数通知。这里的整个操作包括等待数据准备好和将数据从内核复制到用户进程的缓冲区。
和信号驱动式I/O的区别是:号驱动式I/O告诉我们什么时候可以启动一个I/O操作,而异步I/O中内核通知我们I/O操作何时完成,就是说等待数据准备好和将数据复制到进程都是内核做的,直到这两步完成后直接通知用户进程拿数据。
Java在jdk1.7中加入了异步I/O叫做AIO
五种I/O模型的区别
阻塞和非阻塞
阻塞和非阻塞的区别在有没有I/O调用所导致的由于数据未准备好造成的进程睡眠。如果一个I/O操作的系统调用发出后,需要让当前进程睡眠,就是阻塞式的,否则就是非阻塞式的。
同步和异步
同步I/O:导致请求进程阻塞直到I/O操作完成,也就是说在数据从内核复制到进程完成之前,进程一直阻塞。
异步I/O:不导致请求进程阻塞。
这样看来,前四种都属于同步I/O,异步就是异步,没有阻塞和非阻塞的概念。
Java中的I/O模型
Bio:阻塞式I/O
Nio:非阻塞I/O(jdk 1.4)
Aio:异步I/O(jdk 1.7)
NIO是什么?
Java在1.4中首次增加了NIO(New I/O,Non-blockingI/O),是一种同步非阻塞的I/O模型。引入NIO的目的在于提高速度,在此基础上,以前版本中旧的I/O操作的函数全部改写为使用NIO时实现,因此即使不显式的使用NIO编写代码,也能从中受益,速度的提高包括文件I/O和网络I/O,网络I/O中最大的收益在于可以使用单线程来管理多个Socket连接,是一种I/O复用。
为什么?
JVM自身在I/O 方面效率欠佳。操作系统与Java基于流的I/O模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而JVM的I/O 类喜欢操作小块数据——单个字节、几行文本。结果,操作系统送来整缓冲区的数据,java.io的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io类则喜欢一铲子一铲子地加工数据。有了NIO,就可以轻松地把一卡车数据备份到您能直接使用的地方(ByteBuffer对象)---- Java Nio O'Reilly Media
传统流I/O是基于字节的,所有I/O都被视为单个字节的移动;而NIO是基于块的,大家可能猜到了,NIO的性能肯定优于流I/O。没错!其性能的提高 要得益于其使用的结构更接近操作系统执行I/O的方式:通道和缓冲器。我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车载满煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互;我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据----Thinking in java
注:这是一篇笔记,包括自己的思考总结,所有内容均来自
《Unix网络编程》
《Java Nio》
《Thinking injava》