IO处理模式
PPC/TPC(process or thread per connection)
每个连接分配一个进程或线程来处理IO
模型简单,但是资源存在瓶颈。
Reactor
反应器模式,线程或进程复用连接:本质上就是IO多路复用+线程池或进程池
底层实现原理: 多路复用
所谓的多路复用请求,即multiplexing request,是指将两个或多个数据流合并到底层单一物理连接中的过程。TCP的多路复用请求会在一条物理连接上创建若干个虚拟连接,每个虚拟连接负责流转各自对应的数据流。其实严格来说,TCP并不能多路复用,它只是提供可靠的消息交付语义保证,比如自动重传丢失的报文。 更严谨地说,作为一个基于报文的协议,TCP能够被用于多路复用连接场景的前提是,上层的应用协议(比如HTTP)允许发送多条消息。本质上是应用复用了底层的链接,并承载多个不同的虚拟链接的数据流,由应用或框架来管理虚拟链接数据流与底层链接的关系
多路指的是多条连接,复用指的是多条连接复用同一个阻塞对象,这个阻塞对象与具体的实现有关。以Linux为例,如果是用的是select,复用的阻塞对象就是select用到的fd_set, 如果使用的是epoll,则就是epoll_create创建的文件描述符。
Linux IO模型
同步阻塞
当用户进程调用了recv()/recvfrom()这个系统调用,kernel就开始了IO的第一个阶段:准备数据
(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。第二个阶段:当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存
,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
同步非阻塞
当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有
IO多路复用(同步阻塞模型)
IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO
。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。当用户进程调用了select,那么整个进程会被block
,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回
。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
多路复用的特点是
通过一种机制一个进程能同时等待IO文件描述符
,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。对于监视的方式,又可以分为 select, poll, epoll三种方式。
上面的图和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。所以IO多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。
在I/O编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源,I/O多路复用的主要应用场景如下:
- 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字。
- 服务器需要同时处理多种网络协议的套接字。
了解了前面三种IO模式,在用户进程进行系统调用的时候,他们在等待数据到来的时候,处理的方式不一样:
- 直接等待
- 轮询
- select或poll轮询
从整个IO过程来看,他们都是顺序执行的,因此可以归为同步模型(synchronous)。都是进程主动等待且向内核检查状态.
同步是需要主动等待消息通知,而异步则是被动接收消息通知,通过回调、通知、状态等方式来被动获取消息。IO多路复用在阻塞到select阶段时,用户进程是主动等待并调用select函数获取数据就绪状态消息,并且其进程状态为阻塞。所以,把IO多路复用归为同步阻塞模式。
区别
IO处理模式与IO模型是不同的维度,IO模型是IO处理模式里选用的PPC或Reactor里的具体实现方式。
例如Reactor模式可以选用多路复用模型来实现,PPC(TPC)可以选用经典的同步模型来实现。