zoukankan      html  css  js  c++  java
  • JAVA NIO概述(一):I/O模型

    NIO是jdk1.4加入的新功能,我们一般成为非阻塞IO,在1.4之前,JAVA中的都是BIO(堵塞IO),BIO有以下几个缺点:

    1. 没有数据缓冲区,I/O性能存在问题
    2. 没有C/C++中channel(通道)的概念,只有输入和输出流
    3. 同步阻塞式I/O(BIO),通常会导致线程被长时间阻塞
    4. 支持的字符集有限,硬件的可一致性不好
    我们会经常听到 同步(synchronous) IO和异步(asynchronous) IO,那么阻塞(blocking) IO和非阻塞(non-blocking)IO,,同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?
     
    我们先对UNIX常用的I/O模型做一个简单的介绍.Linux会把所有外部设备都当做一个文件来操作,对文件的读写会返回一个file descriptor(fd,文件描述符)。对socket读写也会返回相应的描述符,称作socketfd(socket 描述符),描述符是一个数组,指向内核中的一个结构体(文件路径,数据区等一些属性)
    UNIX提供了5中I/O模型:堵塞I/O模型,非堵塞I/O模型,I/O复用模型,信号驱动I/O模型,异步I/O。
    对于一个network IO,以read操作来举例,它会涉及到两个系统对象,1.调用这个IO的process or thread,2.系统内核(kernel),当一个read操作发生时,它会经过两个步骤:
    1. 等待数据准备
    2. 将数据从内核拷进用户进程中
    上面5种IO模型就是在这两个阶段上各自有不同的情况。
     
     
     

    blocking IO 

     在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

    当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整 个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。
    所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

    non-blocking IO

    linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

    从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
    所以,用户进程其实是需要不断的主动询问kernel数据好了没有。

     

    IO multiplexing

    IO复用模型,也就是linux中常说的select、epoll,有一些地方也称为事件驱动模型。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

    当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个 socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
    这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用 select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
    在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被 block的。只不过process是被select这个函数block,而不是被socket IO给block。

    linux中一般选用epoll作为轮询和网络事件通知,因为它相比select有以下改进:
    1. 支持一个进程打开的socket fd(描述符)不受限制,仅受限于操作系统的最大文件句柄数(select打开的默认是1024)
    2. IO效率不会随着FD数目的增加而线性下降:在一个大socket集合中,由于网络空闲或者延迟,某一时段只有一部分是活跃的,但是select/poll会扫描全部的socket,导致效率下降。而epoll只会扫描活跃的集合,这是因为epoll是根据每个fd的callback函数实现的,只有活跃的fd才会调用callback,其他idle不会调用。
    3. epoll的api更加简单

    Asynchronous I/O

    先看一下它的流程:

    用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都 完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

    到目前为止,已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题:blocking和non-blocking的区别在哪,synchronous IO和asynchronous IO的区别在哪。
    先回答最简单的这个:blocking vs non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。

    在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。Stevens给出的定义(其实是POSIX的定义)是这样子的:

    • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
    • An asynchronous I/O operation does not cause the requesting process to be blocked;
    两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从 kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。
     

    各个IO Model的比较如图所示:

    经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用 recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的 状态,也不需要主动的去拷贝数据。

    举一个抢票的例子来说明这四个IO Model:
    阻塞IO: 火车站排队买票,得一直排队,到你了才能买
    非堵塞:电脑上登录网站买票,每次可以查询是否有票,然后可以做其他事,隔会在看看有没有票,有的话就买;
    IO复用:和非堵塞差不多,差别就是开多个抢票软件买票,然后守在旁边,隔会在看看有没有票,有的话就买;
    异步IO:直接找黄牛买,有票了,黄牛就通知你,付钱

     
    总结:
    区分是同步IO还是异步IO:同步IO在数据准备就绪后,需要自己负责读写,把数据用内核空间复制到用户空间中,也就是说这个读写过程是堵塞的,而异步IO不需要自己负责读写,异步IO的实现会把数据从内核空间复制到用户空间。
    区分是堵塞IO还是非堵塞IO: 就是在数据准备过程中,堵塞IO会一直堵塞直到数据准备好,而非堵塞IO在数据未准备好的情况下,会立即返回一个error状态,可以继续做其他事情,过一段时间再次轮询这个状态,直到成功即可。
     

  • 相关阅读:
    HAProxy、Keepalived 在 Ocatvia 的应用实现与分析
    Octavia 的 HTTPS 与自建、签发 CA 证书
    Octavia 创建 loadbalancer 的实现与分析
    OpenStack Rally 质量评估与自动化测试利器
    自建 CA 中心并签发 CA 证书
    Failed building wheel for netifaces
    通过 vSphere WS API 获取 vCenter Datastore Provisioned Space 置备空间
    OpenStack Placement Project
    我们建了一个 Golang 硬核技术交流群(内含视频福利)
    没有图形界面的软件有什么用?
  • 原文地址:https://www.cnblogs.com/vhua/p/nio_1.html
Copyright © 2011-2022 走看看