【Linux | 网络I/O模型】五种网络I/O模型详解

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 的工作过程如下:

  1. 发起系统调用 :应用程序通过系统调用(如 recv())请求从网络套接字中接收数据。

  2. 等待内核准备数据:内核检查该套接字是否有可用数据。如果没有数据可读,内核会让调用线程进入"阻塞"状态,并挂起该线程。

  3. 数据准备就绪:当网络数据到达(例如从远程主机接收到 TCP 数据包),内核将数据拷贝到内核缓冲区。

  4. 数据传输:数据准备好后,内核将数据从内核缓冲区拷贝到应用程序的用户空间缓冲区中。

  5. 返回结果recv() 调用返回,程序获得数据并继续执行。

在整个过程中,应用程序在 recv() 期间是"阻塞"的,即除非数据到达,进程无法继续执行。

想象你正在银行柜台办理业务。柜台的工作人员(即操作系统)负责处理你的请求(即 I/O 操作)。以下是如何用这个例子来理解阻塞 I/O:

  1. 排队等待:你(客户端)到银行柜台(服务器)前办理业务。柜台只有一个工作人员,且每次只能处理一个客户。

  2. 提交请求:你递交你的请求(例如取款请求)。此时,柜台工作人员需要时间来处理你的请求(如检查账户余额、准备现金等)。

  3. 阻塞等待:在处理你的请求期间,工作人员需要时间来完成工作。你(客户端)只能等待,不能继续进行其他业务。在这个等待期间,你无法离开柜台,也不能处理其他事务,只能在柜台前等待工作人员完成你的请求。

  4. 完成操作:工作人员完成你的请求后,将结果(例如现金)交给你。这时,你可以继续离开柜台,完成你的业务。

3.2**、非阻塞式****IO**

非阻塞 I/O 是一种 I/O 模型,和阻塞 I/O 相反。当应用程序发起 I/O 操作时,非阻塞 I/O 不会让程序等待数据的准备情况。如果数据没有准备好,系统立即返回一个状态,表明数据尚不可用,程序可以继续执行其他操作,而不需要一直等待。非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符。

3.2.1 非阻塞 I/O 的工作流程

仍以网络编程为例,非阻塞 I/O 的 recv 操作流程如下:

  1. 发起系统调用 :应用程序调用 recv() 请求从网络套接字接收数据。

  2. 立即返回 :如果数据未准备好,recv() 不会阻塞程序,它会立即返回并给出一个错误或状态(如 EAGAIN),表明数据不可用。程序可以继续执行其他任务,而不是在等待数据期间被挂起。

  3. 检查数据:程序可以在适当的时间再次尝试接收数据,直到数据准备完毕为止。

  4. 完成数据接收:当数据准备就绪时,程序可以成功读取数据。

想象你在一家餐厅点餐,但这家餐厅提供的是"取号等餐"的服务模式。这类似于非阻塞 I/O 的工作方式:

  1. 提交请求:你到柜台下单后,服务员给了你一个取号。此时,服务员告诉你,食物需要一段时间准备。

  2. 不需要等待:你不用一直站在柜台前等待食物的完成,而是可以拿着号码牌去做其他事情,例如找个位置坐下,刷手机、聊天等。柜台没有阻塞你,你可以继续执行其他任务。

  3. 检查状态:你可以时不时地看一下餐厅的屏幕(或者听广播)来查看自己的号有没有叫到。如果号还没到,你继续等,但不需要一直在柜台前等待。

  4. 完成操作:当你的号码被叫到时,你前去取餐,整个过程完成。

3.3**、IO多路复用**

I/O 多路复用是一种技术,用于在单个进程或线程中同时处理多个 I/O 操作。它允许程序同时监视多个 I/O 流(如多个网络套接字)的状态,并在数据准备好时进行相应的操作。这种技术可以显著提高资源利用率和程序的响应性。

3.3.1. I/O 多路复用的工作原理

I/O 多路复用的基本思想是:程序可以同时处理多个 I/O 操作,而不是每次只能处理一个 I/O 操作。它通过以下方式实现:

  1. 注册感兴趣的 I/O 流:程序向操作系统注册它感兴趣的多个 I/O 流,例如多个网络连接或文件描述符。

  2. 等待事件 :程序调用 I/O 多路复用函数(如 select()poll()epoll()kqueue()),这些函数会阻塞程序,直到至少一个注册的 I/O 流有事件发生(如数据到达)。

  3. 处理事件:操作系统通知程序哪些 I/O 流有数据准备好或发生了其他事件,程序可以对这些事件进行处理。

假设你在一个大型活动现场工作,你需要同时协调多个任务(如管理多个会议室的活动、处理多个客户的请求等)。这类似于 I/O 多路复用的工作方式:

  1. 注册任务:你登记需要处理的所有任务,例如每个会议室的活动、客户的咨询请求等。这些任务就像 I/O 流。

  2. 等待事件:你定期检查每个任务的状态,可能会通过通信工具(如对讲机、手机)等待其他人的反馈或通知。

  3. 处理任务:当你收到任务的反馈或通知时,你立即处理相关任务,例如安排会议室的设备、回答客户的问题等。你不需要等到一个任务完全完成后才开始处理下一个任务,而是可以同时处理多个任务的状态。

3.4**、信号驱动式****IO**

信号驱动式 I/O 是一种 I/O 模型,用于处理 I/O 操作时的事件通知。它依赖于操作系统发送信号来通知程序某些 I/O 事件的发生,从而避免了持续的轮询。这种方法通常用于处理异步 I/O 操作。

3.4.1 信号驱动式 I/O 的工作原理

信号驱动式 I/O 的基本流程如下:

  1. 设置信号处理 :程序首先注册一个信号处理程序(Signal Handler),该程序会处理特定的信号(如 SIGIO)。

  2. 设置 I/O 设备为信号驱动模式:程序通过系统调用将 I/O 设备(如网络套接字)设置为信号驱动模式。这样,当设备有数据准备好时,操作系统会向程序发送一个信号。

  3. 等待信号:程序处于等待状态,直到操作系统发送信号。此时,信号处理程序会被触发。

  4. 处理信号:信号处理程序被调用来处理 I/O 事件。例如,它可以读取网络数据、处理文件操作等。

想象你在家中等待快递员送货,而你知道快递员会按门铃通知你。这个场景类似于信号驱动式 I/O 的工作方式:

  1. 设置通知:你设置好门铃,知道快递员会按门铃来通知你。这个动作类似于信号驱动式 I/O 中的信号处理设置。

  2. 等待信号:你在家中做其他事情,但你随时准备好听到门铃声。这个状态类似于程序等待信号的状态。

  3. 接收信号:当快递员到达时,他按响门铃。这个动作就像操作系统发送一个信号通知程序有 I/O 事件发生。

  4. 处理信号:你听到门铃声后,去开门接收快递。这就像信号处理程序被调用来处理实际的 I/O 事件(例如读取数据)。

3.5**、异步****IO**

异步 I/O 是一种 I/O 操作模型,其中 I/O 操作的请求和处理是分开的。在这种模型中,程序发起一个 I/O 请求后,不需要等待操作完成,可以继续执行其他任务。当 I/O 操作完成时,系统会通知程序,程序可以根据通知来处理结果。这种模型可以提高程序的响应性和效率,特别是在处理大量并发 I/O 操作时。

2. 异步 I/O 的工作原理

异步 I/O 的基本流程如下:

  1. 发起 I/O 请求:程序发起一个 I/O 操作请求(如读取文件或网络数据),并提供一个回调函数或通知机制,用于处理操作完成后的结果。

  2. 继续执行其他任务:发起 I/O 请求后,程序可以继续执行其他任务,而不需要等待 I/O 操作完成。

  3. 等待通知:系统在后台处理 I/O 操作。当操作完成时,操作系统会通知程序。

  4. 处理结果:程序通过回调函数、事件通知或其他机制来处理 I/O 操作的结果。

设想你在餐厅里点餐,餐厅的工作流程类似于异步 I/O 模型:

  1. 发起请求:你向服务员点餐。这类似于程序发起 I/O 请求。服务员将你的点菜信息提交给厨房。

  2. 继续活动:你可以继续做其他事情,比如聊天、玩手机等。你不需要等待厨房准备食物的整个过程,而是可以继续享受你的时间。这就像程序在发起异步 I/O 请求后继续执行其他任务。

  3. 等待通知:厨房准备好你的食物后,服务员会来通知你。这个通知相当于 I/O 操作的完成通知。

  4. 处理结果:服务员将准备好的食物送到你的桌子上,你可以开始用餐。这就像程序处理异步 I/O 操作完成后的结果。

4**、五种网络IO模型对比**

总结比较

模型 发起 I/O 请求 等待 I/O 操作 处理结果 特点
阻塞 I/O 发起请求 阻塞等待完成 处理结果 简单易懂,但效率低
非阻塞 I/O 发起请求 立即返回(检查状态) 处理结果 轮询方式,效率较高
I/O 多路复用 发起请求 等待事件发生 处理已就绪事件 高效处理多个 I/O 操作
信号驱动式 I/O 发起请求 等待信号 处理信号 避免轮询,编程复杂
异步 I/O 发起请求 继续执行其他任务 处理结果(回调或通知) 高效处理大量并发操作
相关推荐
yunfanleo11 分钟前
docker run m3e 配置网络,自动重启,GPU等 配置渠道要点
linux·运维·docker
m512712 分钟前
LinuxC语言
java·服务器·前端
hakesashou18 分钟前
Python中常用的函数介绍
java·网络·python
C++忠实粉丝30 分钟前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
九州ip动态30 分钟前
做网络推广及游戏注册为什么要换IP
网络·tcp/ip·游戏
运维-大白同学34 分钟前
将django+vue项目发布部署到服务器
服务器·vue.js·django
Estar.Lee34 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
蝶开三月36 分钟前
php:使用socket函数创建WebSocket服务
网络·websocket·网络协议·php·socket
糖豆豆今天也要努力鸭42 分钟前
torch.__version__的torch版本和conda list的torch版本不一致
linux·pytorch·python·深度学习·conda·torch
G丶AEOM1 小时前
SSL/TLS,SSL,TLS分别是什么
网络·网络协议·网络安全