就这一次把网路的几种IO模型以及Nginx基本原理彻底搞清楚

1 网络IO模型1.1 网络IO基本概念理解IO分别表示输入(input)和输出(output) 。 它描述的是计算机的数据流动的过程 , 因此IO第一大特征是有数据的流动;那么对于IO的整个过程大体上分为2个部分 ,第一个部分为IO的调用 , 第二个过程为IO的执行。 IO的调用指的就是系统调用 , IO的执行指的是在内核中相关数据的处理过程 , 这个过程是由操作系统完成的 , 与程序员无关 。
IO多路复用是指 内核 一旦发现进程指定的一个或者多个IO条件准备读取 , 它就通过该 进程, 目前支持I/O多路复用的系统调用有 select 、 poll 、 epoll, I/O多路复用就是通过一种机制 , 一个进程可以监视多个描述符(socket) , 一旦某个描述符就绪(一般是读就绪或者写就绪) , 能够通过程序进行相应的读写操作 。
描述符(socket)在windows中可以叫做句柄 。 我们可以理解成一个文件对应的ID 。 IO其实就是对
1.2 对同步 异步 阻塞 非阻塞在网络中的理解可以先看以前写的文章: 对同步 异步 阻塞 非阻塞在网络中的理解
阻塞IO:请求进程一直等待IO准备就绪 。
非阻塞IO:请求进程不会等待IO准备就绪 。
同步IO操作:导致请求进程阻塞 , 直到IO操作完成 。
异步IO操作:不导致请求进程阻塞 。
举个小例的来理解阻塞 , 非阻塞 , 同步和异步的关系 , 我们知道编写一个程序可以有多个函数 , 每一个函数的执行都是相互独立的;但是 , 对于一个程序的执行过程 , 每一个函数都是必须的 , 那么如果我们需要等待一个函数的执行结束然后返回一个结果(比如接口调用) , 那么我们说该函数的调用是阻塞的 , 对于至少有一个函数调用阻塞的程序 , 在执行的过程中 , 必定存在阻塞的一个过程 , 那么我们就说该程序的执行是同步的 , 对于异步自然就是所有的函数执行过程都是非阻塞的 。
这里的程序就是一次完整的IO , 一个函数为IO在执行过程中的一个独立的小片段 。 我们知道在Linux操作系统中内存分为 内核空间 和 用户空间, 而所有的IO操作都得获得内核的支持 , 但是由于用户态的进程无法直接进行内核的IO操作 , 所以内核空间提供了系统调用 , 使得处于用户态的进程可以间接执行IO操作 , IO调用的目的是将进程的内部数据迁移到外部即输出 , 或将外部数据迁移到进程内部即输入 。 而在这里讨论的数据通常是socket进程内部的数据 。
1.3 五种IO模型基本原理
就这一次把网路的几种IO模型以及Nginx基本原理彻底搞清楚文章插图
在上图中 , 每一个客户端会与服务端建立一次socket连接 , 而服务端获取连接后 , 对于所有的数据的读取都得经过操作系统的内核 , 通过系统调用内核将数据复制到用户进程的缓冲区 , 然后才完成客户端的进程与客户端的交互 。 那么根据系统调用的方式的不同分为阻塞和非阻塞 , 根据系统处理应用进程的方式不同分为同步和异步 。
模型1:阻塞式IO
就这一次把网路的几种IO模型以及Nginx基本原理彻底搞清楚文章插图
每一次客户端产生的socket连接实际上是一个文件描述符fd,而每一个用户进程读取的实际上也是一个个文件描述符fd,在该时期的系统调用函数会等待网络请求的数据的到达和数据从内核空间复制到用户进程空间 , 也就是说 , 无论是第一阶段的IO调用还是第二阶段的IO执行都会阻塞 , 那么就像图中所画的一样 , 对于多个客户端连接 , 只能开辟多个线程来处理 。
模型2:非阻塞IO模型
就这一次把网路的几种IO模型以及Nginx基本原理彻底搞清楚文章插图
对于阻塞IO模型来说最大的问题就体现在阻塞2字上 , 那么为了解决这个问题 , 系统的内核因此发生了改变 。 在内核中socket支持了非阻塞状态 。 既然这个socket是不阻塞的了 , 那么就可以使用一个进程处理客户端的连接 , 该进程内部写一个死循环 , 不断的询问每一个连接的网络数据是否已经到达 。 此时轮询发生在用户空间 , 但是该进程依然需要自己处理所有的连接 , 所以该时期为同步非阻塞IO时期 , 也即为NIO 。
模型3:IO多路复用在非阻塞IO模型中 , 虽然解决了IO调用阻塞的问题 , 但是产生了新的问题 , 如果现在有1万个连接 , 那么用户线程会调用1万次的系统调用read来进行处理 , 在用户空间这种开销太大 , 那么现在需要解决这个问题 , 思路就是让用户进程减少系统调用 , 但是用户自己是实现不了的 , 所以这就导致了内核发生了进一步变化 。 在内核空间中帮助用户进程遍历所有的文件描述符 , 将数据准备好的文件描述符返回给用户进程 。 该方式是同步阻塞IO , 因为在第一阶段的IO调用会阻塞进程 。