1**、数据传输过程**
在 Linux 系统中,数据传输是通过 I/O 操作来实现的。I/O 操作是指数据从应用程序到内核,再到硬件设备(如磁盘、网络接口)的过程。 操作系统为了保护自己,设计了用户态、内核态两个状态。应用程序一般工作在用户态,当调用一些底层操作的时候(比如 IO 操作),就需要切换到内核态才可以进行。
服务器从网络接收的大致流程如下:
- 数据通过计算机网络传到网卡
- 把网卡的数据读取到socket缓冲区
- 把socket缓冲区读取到用户缓冲区,之后应用程序就可以使用
核心就是两次读取操作,五种网络 IO 模型的不同之处也就在于这两个读取操作怎么交互。
2**、两组重要概念**
阻塞(Blocking)与非阻塞(Non-Blocking)
1. 阻塞
-
定义:在阻塞模式下,当一个操作无法立即完成时,程序会被挂起,直到操作完成。这意味着在操作完成之前,程序会一直等待,不能继续执行其他任务。
-
特性:
- 等待:阻塞调用会使调用线程等待操作完成。
- 简单:编程模型较简单,易于理解和实现。
- 效率:在等待期间,线程无法做其他有用的工作,可能导致资源浪费。
-
例子:
- 文件读取 :如果应用程序调用
read()
从文件中读取数据,但文件中没有数据,线程会阻塞,直到有数据可读。 - 网络请求:如果应用程序发起网络请求,线程会阻塞,直到收到响应。
- 文件读取 :如果应用程序调用
2. 非阻塞
-
定义:在非阻塞模式下,当一个操作无法立即完成时,程序不会被挂起,而是立即返回。程序可以继续执行其他操作,并定期检查操作是否完成。
-
特性:
- 立即返回:非阻塞调用会立即返回,无论操作是否完成。
- 复杂:编程模型较复杂,需要轮询或回调机制来检查操作状态。
- 效率:可以在等待期间执行其他任务,利用 CPU 资源更高效。
-
例子:
- 文件读取 :如果应用程序调用
read()
并且文件中没有数据,调用会立即返回,应用程序需要再次调用read()
以获取数据。 - 网络请求:在非阻塞网络编程中,程序发起请求后立即返回,程序通过轮询或事件机制来处理响应。
- 文件读取 :如果应用程序调用
同步(Synchronous)与异步(Asynchronous)
1. 同步
-
定义:在同步模式下,操作会按照顺序执行,后续操作必须等待前一个操作完成。程序在执行下一步之前会等待当前操作完成。
-
特性:
- 顺序执行:操作按顺序执行,后续操作依赖于前面的操作。
- 简单:编程模型简单,因为操作是顺序进行的。
- 等待:在操作完成之前,程序不能执行其他任务。
-
例子:
- 同步 I/O:应用程序发起读取文件操作,必须等到读取完成后才能继续执行其他操作。
- 函数调用:普通函数调用会同步执行,调用者必须等待函数返回结果。
2. 异步
-
定义:在异步模式下,操作的执行不会阻塞程序的其他操作。程序可以发起操作后继续执行其他任务,并在操作完成时得到通知(通过回调、事件或未来对象等)。
-
特性:
- 并行执行:操作可以并行进行,程序不需要等待操作完成。
- 复杂:编程模型复杂,需要处理回调、事件、未来对象等异步机制。
- 效率:可以更高效地利用 CPU 资源和处理时间。
-
例子:
- 异步 I/O:应用程序发起读取文件操作后立即返回,并可以继续执行其他任务。操作完成时,系统通过回调或事件通知应用程序。
- 异步函数 :现代编程语言(如 JavaScript、Python)提供了异步函数(如
async/await
)来处理异步操作。
总结
-
阻塞 vs 非阻塞:
- 阻塞:操作无法立即完成时,程序会等待。
- 非阻塞:操作无法立即完成时,程序会立即返回,并可以继续执行其他任务。
-
同步 vs 异步:
- 同步:操作按顺序执行,后续操作等待前面的操作完成。
- 异步:操作可以并行进行,程序不需要等待操作完成,可以在操作完成时得到通知。
3**、五种网络IO模型**
3.1**、阻塞式****IO**
阻塞 I/O 是最基本的 I/O 模型。在该模型中,当进程或线程执行 I/O 操作时,它会被"阻塞",即等待操作的完成。如果操作无法立即完成,进程或线程会停止运行,直到数据准备就绪为止。简单来说,阻塞 I/O 的执行流程是"请求操作 -> 等待完成 -> 继续执行"。
3.1.1. 阻塞 I/O 的工作流程
以网络编程中的 recv
(接收数据)操作为例,阻塞 I/O 的工作过程如下:
-
发起系统调用 :应用程序通过系统调用(如
recv()
)请求从网络套接字中接收数据。 -
等待内核准备数据:内核检查该套接字是否有可用数据。如果没有数据可读,内核会让调用线程进入"阻塞"状态,并挂起该线程。
-
数据准备就绪:当网络数据到达(例如从远程主机接收到 TCP 数据包),内核将数据拷贝到内核缓冲区。
-
数据传输:数据准备好后,内核将数据从内核缓冲区拷贝到应用程序的用户空间缓冲区中。
-
返回结果 :
recv()
调用返回,程序获得数据并继续执行。
在整个过程中,应用程序在 recv()
期间是"阻塞"的,即除非数据到达,进程无法继续执行。
想象你正在银行柜台办理业务。柜台的工作人员(即操作系统)负责处理你的请求(即 I/O 操作)。以下是如何用这个例子来理解阻塞 I/O:
-
排队等待:你(客户端)到银行柜台(服务器)前办理业务。柜台只有一个工作人员,且每次只能处理一个客户。
-
提交请求:你递交你的请求(例如取款请求)。此时,柜台工作人员需要时间来处理你的请求(如检查账户余额、准备现金等)。
-
阻塞等待:在处理你的请求期间,工作人员需要时间来完成工作。你(客户端)只能等待,不能继续进行其他业务。在这个等待期间,你无法离开柜台,也不能处理其他事务,只能在柜台前等待工作人员完成你的请求。
-
完成操作:工作人员完成你的请求后,将结果(例如现金)交给你。这时,你可以继续离开柜台,完成你的业务。
3.2**、非阻塞式****IO**
非阻塞 I/O 是一种 I/O 模型,和阻塞 I/O 相反。当应用程序发起 I/O 操作时,非阻塞 I/O 不会让程序等待数据的准备情况。如果数据没有准备好,系统立即返回一个状态,表明数据尚不可用,程序可以继续执行其他操作,而不需要一直等待。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符。
3.2.1 非阻塞 I/O 的工作流程
仍以网络编程为例,非阻塞 I/O 的 recv
操作流程如下:
-
发起系统调用 :应用程序调用
recv()
请求从网络套接字接收数据。 -
立即返回 :如果数据未准备好,
recv()
不会阻塞程序,它会立即返回并给出一个错误或状态(如EAGAIN
),表明数据不可用。程序可以继续执行其他任务,而不是在等待数据期间被挂起。 -
检查数据:程序可以在适当的时间再次尝试接收数据,直到数据准备完毕为止。
-
完成数据接收:当数据准备就绪时,程序可以成功读取数据。
想象你在一家餐厅点餐,但这家餐厅提供的是"取号等餐"的服务模式。这类似于非阻塞 I/O 的工作方式:
-
提交请求:你到柜台下单后,服务员给了你一个取号。此时,服务员告诉你,食物需要一段时间准备。
-
不需要等待:你不用一直站在柜台前等待食物的完成,而是可以拿着号码牌去做其他事情,例如找个位置坐下,刷手机、聊天等。柜台没有阻塞你,你可以继续执行其他任务。
-
检查状态:你可以时不时地看一下餐厅的屏幕(或者听广播)来查看自己的号有没有叫到。如果号还没到,你继续等,但不需要一直在柜台前等待。
-
完成操作:当你的号码被叫到时,你前去取餐,整个过程完成。
3.3**、IO多路复用**
I/O 多路复用是一种技术,用于在单个进程或线程中同时处理多个 I/O 操作。它允许程序同时监视多个 I/O 流(如多个网络套接字)的状态,并在数据准备好时进行相应的操作。这种技术可以显著提高资源利用率和程序的响应性。
3.3.1. I/O 多路复用的工作原理
I/O 多路复用的基本思想是:程序可以同时处理多个 I/O 操作,而不是每次只能处理一个 I/O 操作。它通过以下方式实现:
-
注册感兴趣的 I/O 流:程序向操作系统注册它感兴趣的多个 I/O 流,例如多个网络连接或文件描述符。
-
等待事件 :程序调用 I/O 多路复用函数(如
select()
、poll()
、epoll()
或kqueue()
),这些函数会阻塞程序,直到至少一个注册的 I/O 流有事件发生(如数据到达)。 -
处理事件:操作系统通知程序哪些 I/O 流有数据准备好或发生了其他事件,程序可以对这些事件进行处理。
假设你在一个大型活动现场工作,你需要同时协调多个任务(如管理多个会议室的活动、处理多个客户的请求等)。这类似于 I/O 多路复用的工作方式:
-
注册任务:你登记需要处理的所有任务,例如每个会议室的活动、客户的咨询请求等。这些任务就像 I/O 流。
-
等待事件:你定期检查每个任务的状态,可能会通过通信工具(如对讲机、手机)等待其他人的反馈或通知。
-
处理任务:当你收到任务的反馈或通知时,你立即处理相关任务,例如安排会议室的设备、回答客户的问题等。你不需要等到一个任务完全完成后才开始处理下一个任务,而是可以同时处理多个任务的状态。
3.4**、信号驱动式****IO**
信号驱动式 I/O 是一种 I/O 模型,用于处理 I/O 操作时的事件通知。它依赖于操作系统发送信号来通知程序某些 I/O 事件的发生,从而避免了持续的轮询。这种方法通常用于处理异步 I/O 操作。
3.4.1 信号驱动式 I/O 的工作原理
信号驱动式 I/O 的基本流程如下:
-
设置信号处理 :程序首先注册一个信号处理程序(Signal Handler),该程序会处理特定的信号(如
SIGIO
)。 -
设置 I/O 设备为信号驱动模式:程序通过系统调用将 I/O 设备(如网络套接字)设置为信号驱动模式。这样,当设备有数据准备好时,操作系统会向程序发送一个信号。
-
等待信号:程序处于等待状态,直到操作系统发送信号。此时,信号处理程序会被触发。
-
处理信号:信号处理程序被调用来处理 I/O 事件。例如,它可以读取网络数据、处理文件操作等。
想象你在家中等待快递员送货,而你知道快递员会按门铃通知你。这个场景类似于信号驱动式 I/O 的工作方式:
-
设置通知:你设置好门铃,知道快递员会按门铃来通知你。这个动作类似于信号驱动式 I/O 中的信号处理设置。
-
等待信号:你在家中做其他事情,但你随时准备好听到门铃声。这个状态类似于程序等待信号的状态。
-
接收信号:当快递员到达时,他按响门铃。这个动作就像操作系统发送一个信号通知程序有 I/O 事件发生。
-
处理信号:你听到门铃声后,去开门接收快递。这就像信号处理程序被调用来处理实际的 I/O 事件(例如读取数据)。
3.5**、异步****IO**
异步 I/O 是一种 I/O 操作模型,其中 I/O 操作的请求和处理是分开的。在这种模型中,程序发起一个 I/O 请求后,不需要等待操作完成,可以继续执行其他任务。当 I/O 操作完成时,系统会通知程序,程序可以根据通知来处理结果。这种模型可以提高程序的响应性和效率,特别是在处理大量并发 I/O 操作时。
2. 异步 I/O 的工作原理
异步 I/O 的基本流程如下:
-
发起 I/O 请求:程序发起一个 I/O 操作请求(如读取文件或网络数据),并提供一个回调函数或通知机制,用于处理操作完成后的结果。
-
继续执行其他任务:发起 I/O 请求后,程序可以继续执行其他任务,而不需要等待 I/O 操作完成。
-
等待通知:系统在后台处理 I/O 操作。当操作完成时,操作系统会通知程序。
-
处理结果:程序通过回调函数、事件通知或其他机制来处理 I/O 操作的结果。
设想你在餐厅里点餐,餐厅的工作流程类似于异步 I/O 模型:
-
发起请求:你向服务员点餐。这类似于程序发起 I/O 请求。服务员将你的点菜信息提交给厨房。
-
继续活动:你可以继续做其他事情,比如聊天、玩手机等。你不需要等待厨房准备食物的整个过程,而是可以继续享受你的时间。这就像程序在发起异步 I/O 请求后继续执行其他任务。
-
等待通知:厨房准备好你的食物后,服务员会来通知你。这个通知相当于 I/O 操作的完成通知。
-
处理结果:服务员将准备好的食物送到你的桌子上,你可以开始用餐。这就像程序处理异步 I/O 操作完成后的结果。
4**、五种网络IO模型对比**
总结比较
模型 | 发起 I/O 请求 | 等待 I/O 操作 | 处理结果 | 特点 |
---|---|---|---|---|
阻塞 I/O | 发起请求 | 阻塞等待完成 | 处理结果 | 简单易懂,但效率低 |
非阻塞 I/O | 发起请求 | 立即返回(检查状态) | 处理结果 | 轮询方式,效率较高 |
I/O 多路复用 | 发起请求 | 等待事件发生 | 处理已就绪事件 | 高效处理多个 I/O 操作 |
信号驱动式 I/O | 发起请求 | 等待信号 | 处理信号 | 避免轮询,编程复杂 |
异步 I/O | 发起请求 | 继续执行其他任务 | 处理结果(回调或通知) | 高效处理大量并发操作 |