NIO知识概括

四种IO模型

Java IO读写原理:

  • 无论是Socket的读写还是文件的读写,在Java层面的应用开发或者是linux系统底层开发,都属于输入input和输出output的处理,简称为IO读写。在原理上和处理流程上,都是一致的。区别在于参数的不同。
  • 用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。
  • 先强调一个基础知识:read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。
  • read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。

内核缓冲与进程缓冲区:

  • 缓冲区的目的,是为了减少频繁的系统IO调用。大家都知道,系统调用需要保存之前的进程数据和状态等信息,而结束调用之后回来还需要恢复之前的信息,为了减少这种损耗时间、也损耗性能的系统调用,于是出现了缓冲区。
  • 有了缓冲区,操作系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区中。等待缓冲区达到一定数量的时候,再进行IO的调用,提升性能。至于什么时候读取和存储则由内核来决定,用户程序不需要关心。
  • 在linux系统中,系统内核也有个缓冲区叫做内核缓冲区。每个进程有自己独立的缓冲区,叫做进程缓冲区。
  • 所以,用户程序的IO读写程序,大多数情况下,并没有进行实际的IO操作,而是在读写自己的进程缓冲区。

java IO读写的底层流程:

  • 用户程序进行IO的读写,基本上会用到系统调用read&write,read把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区,它们不等价于数据在内核缓冲区和磁盘之间的交换。在这里插入图片描述
  • 首先看看一个典型Java 服务端处理网络请求的典型过程:
    (1)客户端请求
    Linux通过网卡,读取客户断的请求数据,将数据读取到内核缓冲区。
    (2)获取请求数据
    服务器从内核缓冲区读取数据到Java进程缓冲区。
    (1)服务器端业务处理
    Java服务端在自己的用户空间中,处理客户端的请求。
    (2)服务器端返回数据
    Java服务端已构建好的响应,从用户缓冲区写入系统缓冲区。
    (3)发送给客户端
    Linux内核通过网络 I/O ,将内核缓冲区中的数据,写入网卡,网卡通过底层的通讯协议,会将数据发送给目标客户端。

阻塞与非阻塞:

  • 阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间,执行用户的操作。
  • 阻塞指的是用户空间程序的执行状态,用户空间程序需等到IO操作彻底完成。
  • 传统的IO模型都是同步阻塞IO。在java中,默认创建的socket都是阻塞的。
  • 非阻塞IO,指的是用户程序不需要等待内核IO操作完成后,内核立即返回给用户一个状态值,用户空间无需等到内核的IO操作彻底完成,可以立即返回用户空间,执行用户的操作,处于非阻塞的状态。
  • 简单的说:阻塞是指用户空间(调用线程)一直在等待,而且别的事情什么都不做;非阻塞是指用户空间(调用线程)拿到状态就返回,IO操作可以干就干,不可以干,就去干的事情。
  • 非阻塞IO要求socket被设置为NONBLOCK。
  • 强调一下,这里所说的NIO(同步非阻塞IO)模型,并非Java的NIO(New IO)库。

同步与异步:

  • 同步IO,是一种用户空间与内核空间的调用发起方式。
  • 同步IO是指用户空间线程是主动发起IO请求的一方,内核空间是被动接受方。
  • 异步IO则反过来,是指内核kernel是主动发起IO请求的一方,用户线程是被动接受方。

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种::

  • (1)同步阻塞IO(Blocking IO)
  • (2)同步非阻塞IO(Non-blocking IO)
  • (3)IO多路复用(IO Multiplexing)
    即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
  • (4)异步IO(Asynchronous IO)
    异步IO,指的是用户空间与内核空间的调用方式反过来。用户空间线程是变成被动接受的,内核空间是主动调用者。这一点,有点类似于Java中比较典型的模式是回调模式,用户空间线程向内核空间注册各种IO事件的回调函数,由内核去主动调用。

同步阻塞IO(Blocking IO):

  • 在linux中的Java进程中,默认情况下所有的socket都是blocking IO。在阻塞式 I/O模型中,应用程序在从IO系统调用开始,一直到到系统调用返回,这段时间是阻塞的。返回成功后,应用进程开始处理用户空间的缓存数据。在这里插入图片描述
  • 举个例子,发起一个blocking socket的read读操作系统调用,流程大概是这样:
    (1)当用户线程调用了read系统调用,内核(kernel)就开始了IO的第一个阶段:准备数据。很多时候,数据在一开始还没有到达(比如,还没有收到一个完整的Socket数据包),这个时候kernel就要等待足够的数据到来。
    (2)当kernel一直等到数据准备好了,它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。
    (3)从开始IO读的read系统调用开始,用户线程就进入阻塞状态。一直到kernel返回结果后,用户线程才解除block的状态,重新运行起来。
  • 所以,blocking IO的特点就是在内核进行IO执行的两个阶段,用户线程都被block了。
  • BIO的优点:
    程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。
  • BIO的缺点:
    一般情况下,会为每个连接配套一条独立的线程,或者说一条线程维护一个连接成功的IO流的读写。在并发量小的情况下,这个没有什么问题。但是,当在高并发的场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上,BIO模型在高并发场景下是不可用的。

同步非阻塞NIO(None Blocking IO):

  • 在linux系统下,可以通过设置socket使其变为non-blocking。NIO模型中应用程序在一旦开始IO系统调用,会出现以下两种情况:
    (1)在内核缓冲区没有数据的情况下,系统调用会立即返回,返回一个调用失败的信息。
    (2)在内核缓冲区有数据的情况下,是阻塞的,直到数据从内核缓冲复制到用户进程缓冲。复制完成后,系统调用返回成功,应用进程开

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值