五种网络IO模型

五种模型出自:RFC标准。可参考: 《UNIX网络编程-卷一》 6.2
很多程序员是从高级语言的网络编程/文件操作了解到nio,继而了解到五种io模型的;

五种IO模型

  • 阻塞IO模型 BIO
  • 非阻塞IO模型 NIO
  • IO多路复用模型(select,poll,epoll…)
  • 信号驱动模型(SIGIO)
  • 异步IO(AIO,POSIX的aio_系列函数 Future-Listener机制 )

IO操作可分为两阶段看待:

  1. 进程向发起IO请求,等待数据准备;发起系统调用后进入内核态,内核操作数据到内核缓冲区

  2. 实际的IO操作,将数据从内核拷贝到进程空间中

  • 前四种IO模型都是同步IO操作,

    • 他们的区别在于第一阶段是否阻塞;而第二阶段都是阻塞的,即在内核数据copy到用户空间时都是阻塞的。
  • 异步 I/O模型的进程在这两个阶段都是运行着的。

阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞。

同步IO和异步IO的区别就在于第二个步骤是否阻塞,实际IO读写阶段会阻塞进程,而在异步IO中,是由操作系统帮忙做完IO工作再直接返回结果。

  • 同步是在等待什么?阻塞是通知进程后等他主动发起吗?进程是在空转等着通知,还是干着其他事情,轮询着等通知吗?

几个核心点:

  • 此处阻塞非阻塞说的是线程的状态,同步和异步说的是处理顺序
  • 同步IO和异步IO是针对用户应用程序和内核的交互

NIO(非阻塞IO模型)

NIO,即Non-Blocking IO,是非阻塞IO模型。

NIO存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的CPU资源。可以考虑IO复用模型去解决这个问题。

不阻塞了,但是轮询doge
阻塞不会参与cpu竞争,轮询会在占领cpu期间不断询问cpu是否准备好

非阻塞IO的流程如下:

图片

  1. 应用进程发起IO系统调用,内核态进行IO操作
  2. 应用进程轮询向操作系统内核,发起recvfrom读取数据。
  3. 操作系统内核数据没有准备好,立即返回EWOULDBLOCK错误码。
  4. 应用程序进程轮询调用,继续向操作系统内核发起recvfrom读取数据。
  5. 操作系统内核数据准备好了,从内核缓冲区拷贝到用户空间。
  6. 完成调用,返回成功提示。

对于NIO,如果TCP RecvBuffer有数据,就把数据从网卡读到内存,并且返回给用户;反之则直接返回0,永远不会阻塞。

常见的RPC框架,如Thrift,Dubbo

这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。

IO多路复用模型

IO复用模型核心思路:操作系统给我们提供一类函数,它们可以同时监控多个文件描述符fd的操作。
在C语言中,我们先调用select()或者epoll()库函数,传入要监控的fd集合的指针,这些库函数会进行响应的系统调用,此时会进入内核态;等任何一个socket返回内核数据就绪,系统调用返回,然后库函数返回,然后才执行写在后面的recvfrom系统调用。

多路指多个TCP连接(即 socket或者channel),复用指共同、重复利用一个或几个线程。

同NIO相比,解决轮询的方法:先阻塞进程,等epoll()等函数返回“内核数据准备好了”,再去进行IO调用。

猛一看,这怎么又回到阻塞了?原来是不再阻塞于真正的io,而是阻塞于select()/poll()/epoll()
同类似的多线程+BIO模型相比,节省了线程资源;
事实上,《unix网络编程》也提到,多路复用比 多线程+BIO 还多了次select()/poll()/epoll()系统调用(系统调用需要切换内核态,效率会降低)
多路复用io的优势是可以用少量线程处理更多的连接;如果使用多线程阻塞io,每条连接都需要一个线程;

Java的Selector对于Linux系统来说,有一个致命限制:同一个channel的select不能被并发的调用。因此,如果有多个I/O线程,必须保证:一个socket只能属于一个IoThread,而一个IoThread可以管理多个socket。

另外连接的处理和读写的处理通常可以选择分开,这样对于海量连接的注册和读写就可以分发。虽然read()和write()是比较高效无阻塞的函数,但毕竟会占用CPU,如果面对更高的并发则无能为力。img
对于Redis来说,由于服务端是全局串行的,能够保证同一连接的所有请求与返回顺序一致。这样可以使用单线程+队列,把请求数据缓冲。然后pipeline发送,返回future,然后channel可读时,直接在队列中把future取回来,done()就可以了。

常见的RPC框架,如Thrift,Dubbo
这种框架内部一般维护了请求的协议和请求号,可以维护一个以请求号为key,结果的result为future的map,结合NIO+长连接,获取非常不错的性能。

IO多路复用之select和poll

因为不如epoll(),所以这种历史遗留了解就好

linux下,C语言程序通过调用sys/select.h中的select函数,可以同时监控多个文件描述符fd(通过指针参数的形式将要监控的fd集合传入select函数;这个所谓的“集合”通常是整数数组,在poll中是则链表的形式)。
在select函数监控的fd中,只要有任何一个数据状态准备就绪了,select函数就会返回可读状态。
在IO复用编程模型中,我们在select()返回后,再发起recvfrom请求去读取数据。

高级语言C通过库函数系统调用
进入库函数select()后,由库函数封装好的系统调用,进入内核态;
直到由满足条件的fd或者时间到了,select()函数才会从内核返回,然后函数返回就绪的数量,然后用户态的进程才能执行下一句指令;因此说“阻塞于系统调用”

  • 那么这种阻塞参与cpu竞争吗

但是,select有几个缺点:

  • 单个进程监听的IO最大连接数(FD,文件描述符)设置了限制,默认是1024 (可修改宏定义) static final int MAX_FD = 1024
  • select函数返回的是可用的fd数量,仍然需要通过遍历fdset,找到就绪的描述符。随着数量增加而性能下降。
  • 由于系统调用在内核态,因此每次调用 select(),需要把 fd 集合从用户态空间拷贝到 内核态空间

与select相比,poll解决了连接数限制问题(用链表存储)。但是select和poll一样,在返回之后还是需要通过遍历文件描述符来获取已经就绪的socket。伴随着监视的描述符数量的增长,效率也会线性下降。

图片

NIO中,需要轮询多次轮询系统调用直到可以读取数据,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。

调用后select函数会阻塞住,等有数据 可读、可写、出异常 或者 超时 就会返回。

  • 这个阻塞是设置阻塞状态?
  • 还是说,因为在内核态还没出来,因此说他是阻塞了?
    • 这种状态下还参与cpu竞争吗?还是说参与竞争后也是线程执行的代码也是select中内核态的那一部分?
  • 操作系统的对线程的控制有阻塞态状态位这种说法吗?

IO多路复用之epoll

为了解决select/poll存在的问题,多路复用模型epoll诞生,它采用事件监听回调机制来实现,流程图如下:

图片

epoll先通过epoll_ctl()来注册一个fd,一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait()时便得到通知。这里去掉了遍历文件描述符的坑爹操作,而是采用监听事件回调的机制。

epoll()在2.6内核中提出的,对比select和poll,epoll更加灵活,没有描述符限制,用户态拷贝到内核态只需要使用事件通知

优点:

  1. 没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄

  2. 效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降(因为不用自己遍历)。通过callback机制通知,内核和用户空间mmap同一块内存实现

附录

JAVA中的I/O

1.4之前BIO

大型服务一般采用 C或者C++, 因为可以直接操作系统提供的异步IO,AIO.

NIO @Since(“1.4”)

NIO2.0 @Since(“1.7”),提供 AIO的功能,支持文件和网络套接字的异步IO.

计网中的多路复用

运输层:
在源主机,从不同套接字中收集数据块,生成报文段,然后将报文段传递到网络层,所有这些工作称为多路复用(multiplexing)
现在我们考虑接收主机怎样将一个到达的运输层报文段定向到适当的套接字。为此目的,每个运输层报文段中具有几个字段。
在接收端,运输层检查这些字段,标识出接收套接字,进而将报文段定向到该套接字。将运输层报文段中的数据交付到正确的套接字的工作称为多路分解(demultiplexing)。

----------《计算机网络:自顶向下方法》第七版,调整了语序
理解下来,指的是多个发送端的套接字的数据包装成报文段(报文段有标识目标的套接字),复用了网络层;到接受主机后,再在运输层之后被分解为不同套接字;
在这里插入图片描述
而网络IO模型中的I/O多路复用模型,是多个socket的fd复用少量的线程;

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值