java IO多种模式讲解

1.Java IO读写原理

   无论是Socket的读写还是文件的读写,在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。在原理上和处理流程上,都是一致的。区别在于参数的不同。

  用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。

 先强调一个基础知识:read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。

 read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的

2.同步阻塞IO(Blocking IO)

        当用户线程调用了recvfrom这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),

   这个时候内核就要等待足够的数据到来。而在用户线程这边,整个线程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户线程才解除阻塞的状态,重新运行起来。

   这种缺点: 会为每个连接配套一条独立的线程,并且等待数据和数据拷贝过程中是一直阻塞的,不适合高并发场景

 

3.同步非阻塞NIO(None Blocking IO)

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

这种缺点: 需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。

4.同步IO多路复用模型(select/epoll)

用户首先将需要进行IO添加到select中,然后整个线程会被阻塞,而同时,内核会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户线程再调用read操作,将数据从内核拷贝到用户线程。


这个图和BIO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。

但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

然而,使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

IO多路复用模型使用了Reactor设计模式实现了这一机制

 

5.异步IO模型(asynchronous IO)

         AIO的基本流程是:用户线程通过系统调用,告知kernel内核启动某个IO操作,用户线程返回。kernel内核在整个IO操作(包括数据准备、数据复制)完成后,通知用户程序,用户执行后续的业务操作。

     kernel的数据准备是将数据从网络物理设备(网卡)读取到内核缓冲区;kernel的数据复制是将数据从内核缓冲区拷贝到用户程序空间的缓冲区。

在内核kernel的等待数据和复制数据的两个阶段,用户线程都不是block(阻塞)的。用户线程需要接受kernel的IO操作完成的事件,或者说注册IO操作完成的回调函数,到操作系统的内核。所以说,异步IO有的时候,也叫做信号驱动 IO 。

异步IO模型缺点:

需要完成事件的注册与传递,这里边需要底层操作系统提供大量的支持,去做大量的工作。

目前来说, Windows 系统下通过 IOCP 实现了真正的异步 I/O。但是,就目前的业界形式来说,Windows 系统,很少作为百万级以上或者说高并发应用的服务器操作系统来使用。

而在 Linux 系统下,异步IO模型在2.6版本才引入,目前并不完善。所以,这也是在 Linux 下,实现高并发网络编程时都是以 IO 复用模型模式为主。

 

Proactor设计模式:

6.select、poll、epoll的原理与区别

select

时间复杂度:O(n)

fd_set(监听的端口个数):32位机默认是1024个,64位机默认是2048。

缺点

        (1)单进程可以打开fd有限制;

        (2)对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低;

        (2)用户空间和内核空间的复制非常消耗资源;

②poll

      同步多路IO复用

      调用过程和select类似

      时间复杂度:O(n)

      其和select不同的地方:采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制

③epoll

时间复杂度:O(1)

epoll的工作方式

与select,poll一样,但是增加了对I/O多路复用的技术
只关心“活跃”的链接,无需遍历全部描述符集合
能够处理大量的链接请求(系统可以打开的文件数目)

设想一个场景:有100万用户同时与一个进程保持着TCP连接,而每一时刻只有几十个或几百个TCP连接是活跃的(接收TCP包),也就是说在每一时刻进程只需要处理这100万连接中的一小部分连接。那么,如何才能高效的处理这种场景呢?进程是否在每次询问操作系统收集有事件发生的TCP连接时,把这100万个连接告诉操作系统,然后由操作系统找出其中有事件发生的几百个连接呢?实际上,在Linux2.4版本以前,那时的select或者poll事件驱动方式是这样做的。

这里有个非常明显的问题,即在某一时刻,进程收集有事件的连接时,其实这100万连接中的大部分都是没有事件发生的。因此如果每次收集事件时,都把100万连接的套接字传给操作系统(这首先是用户态内存到内核态内存的大量复制),而由操作系统内核寻找这些连接上有没有未处理的事件,将会是巨大的资源浪费,然后select和poll就是这样做的,因此它们最多只能处理几千个并发连接。而epoll不这样做,它在Linux内核中申请了一个简易的文件系统,把原先的一个select或poll调用分成了3部分:

1)调用epoll_create建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);

2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字;

3)调用epoll_wait收集发生事件的连接;监听发生事件连接是ep_poll_callback

这样只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。

转载:https://www.cnblogs.com/crazymakercircle/p/10225159.html   https://www.cnblogs.com/yanguhung/p/10145755.html  https://blog.youkuaiyun.com/xxxxxx91116/article/details/12083613

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值