当前位置: 首页 > backend >正文

【Linux网络】五种IO模型与阻塞IO

IO

在Linux网络环境里,IO(Input/Output)指的是网络数据在系统与外部网络(像其他设备、服务器或者客户端)之间进行传输的过程。

它是网络编程和系统性能优化的核心内容。

  • IO :INPUT和OUTPUT(站在进程角度)
  • 关于IO,read和write都是阻塞式的IO
  • 网络传输数据问题,本质上就是IO问题
  • 如何更好的理解IO问题,就需要更好的理解什么叫做高效的IO?
    • 首先明确,任何通信场景其IO通信场景,效率上一定是会有上限的 。
    • 抽象来说:IO=等+拷贝
    • 在拷贝的角度来说,我们需要充分利用硬件资源以及网络资源
    • 在“等”的角度来说,我们需要减少IO中“等”的权重

网络IO的核心流程

网络IO包含两个关键阶段:

  • 数据就绪:数据从网络传输到网卡,再由网卡传至内核缓冲区。
  • 数据拷贝:数据从内核缓冲区复制到用户空间的应用程序。

网络IO模型

Linux支持多种网络IO模型,它们的主要差异在于如何处理这两个阶段(阻塞、非阻塞、同步、异步)。

常见的网络IO模型有:

  • 阻塞IO(Blocking IO)
  • 非阻塞IO(Non-blocking IO)
  • IO多路复用(IO Multiplexing)
  • 信号驱动IO(Signal-driven IO)
  • 异步IO(Asynchronous IO)

注意:
前四种IO模型都属于同步IO,等的方式不同,但还是自己在深度参与IO过程,只要是自己参与了IO过程,就是同步IO

钓鱼例子

  • 张三:眼睛盯着鱼漂,鱼漂动了,钓鱼
    • 阻塞IO
  • 李四 :查看鱼漂是否动了,如果没有,就看手机刷抖音,期间不断查看鱼漂是否动了
    • 非阻塞IO(轮巡)
  • 王五 : 带来一车鱼竿插在岸边,来回检测哪一个鱼竿上的鱼漂动了
    • IO多路复用
  • 赵六 :在鱼漂上系上一个铃铛,开始钓鱼后刷抖音,直到铃铛响起,开始钓上鱼。
    • 信号驱动IO
  • 田七:我雇佣一个人给我钓鱼,钓到的鱼给我。
    • 异步IO

上述例子中:

  • 鱼竿:文件描述符
  • 钓鱼佬:进程

1. 阻塞IO(最基础的模型)

  • 工作方式:应用程序调用系统调用后会被阻塞,一直等到数据就绪并被复制到用户空间,或者出现错误时才会返回。所有的套接字,默认
    都是阻塞方式
  • 特点:编程逻辑简单,但同一时间只能处理一个连接,容易造成性能瓶颈。
    在这里插入图片描述

2. 非阻塞IO

  • 工作方式:应用程序调用系统调用后会立即返回。如果数据未就绪,会返回EWOULDBLOCK错误。应用程序需要不断轮询来检查数据是否就绪。
  • 特点:非阻塞IO能够避免线程长时间阻塞,但往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询.这对CPU来说是较大的浪费,一般只有特定场景下才使用.
    在这里插入图片描述
3. IO多路复用(高性能服务器的核心)
  • 工作方式:借助selectpollepoll等系统调用,一个进程可以同时监视多个文件描述符(FD)。当某个FD的数据就绪时,就会通知应用程序进行处理。
  • 优势:能够用单线程处理大量并发连接,显著减少了系统开销。
    在这里插入图片描述

虽然从流程图上看起来和阻塞IO类似.实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态.

4. 信号驱动IO
  • 工作方式:应用程序先通过sigaction注册信号处理函数,然后继续执行其他任务。当数据就绪时,内核会发送SIGIO信号,应用程序在信号处理函数中进行数据读取操作。
  • 特点:属于异步通知模式,但数据拷贝阶段仍然是同步的。

在这里插入图片描述

5. 异步IO(真正的异步模型)
  • 工作方式:应用程序通过aio_read等接口发起异步IO请求,然后继续执行后续操作。内核会自动完成数据的读取和拷贝工作,完成后通过回调函数或者信号通知应用程序。
  • 特点:整个IO过程都是异步的,极大地提高了系统的并发处理能力。
    在这里插入图片描述
    小结:
  • 任何IO过程中,都包含两个步骤.第一是等待,第二是拷贝.
  • 而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间.让IO更高效,最核心的办法就是让等待的时间尽量少

高级IO重要概念

在这里,我们要强调几个概念

同步通信 vs 异步通信(synchronous communication/ asynchronous communication)

同步和异步是消息通信机制。

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用的结果
  • 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

另外,我们回忆在讲多进程多线程的时候,也提到同步和互斥。这里的同步通信和进程之间的同步是完全不相干的概念。

  • 进程/线程同步也是进程/线程之间直接的制约关系。
  • 是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。尤其是在访问临界资源的时候。

同学们以后在看到“同步”这个词,一定要先搞清楚大背景是什么。这个同步,是同步通信异步通信的同步,还是同步与互斥的同步。

阻塞 vs 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

其他高级IO

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO。

我们此处重点讨论的是I/O多路转接

非阻塞IO

fcntl

一个文件描述符,默认都是阻塞IO。
函数原型如下:

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

fcntl的cmd的值不同,后面追加的参数也不相同。
fcntl函数有5种功能:

  • 复制一个现有的描述符(cmd=F_DUPFD)。
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)。
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)。
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)。
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)。

我们此处只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞。

实现函数SetNoBlock

基于fcntl,我们实现一个SetNoBlock函数,将文件描述符设置为非阻塞。

void SetNoBlock(int fd) {int fl = fcntl(fd, F_GETFL);if (fl < 0) {perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
  • 使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图)。
  • 然后再使用F_SETFL将文件描述符设置回去。设置回去的同时,加上一个O_NONBLOCK参数。

轮询方式读取标准输入

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>void SetNoBlock(int fd) {int fl = fcntl(fd, F_GETFL);if (fl < 0) {perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int main() {SetNoBlock(0);while (1) {char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");sleep(1);continue;}printf("input:%s\n", buf);}return 0;
}
http://www.xdnf.cn/news/7195.html

相关文章:

  • 多模态信息提取:打通数据价值的“最后一公里”
  • Linux进程信号(二)之信号产生1
  • 【Linux】第二十章 管理基本存储
  • Redis进阶知识
  • 数据库blog2_数据结构与效率
  • 选择之困:如何挑选合适的 Python 环境与工具——以 Google Colaboratory 为例
  • 0-1背包问题(求最优值和构造最优解)
  • 苍穹外卖--修改菜品
  • C++中的四种强制转换
  • web中路径问题
  • Leetcode134加油站
  • u深度学习 神经网络图像数据的预处理全解
  • RDD-数据清洗
  • 02 Nginx虚拟主机
  • 【Linux】第十七章 归档和传输文件
  • 为什么el-select组件在下拉选择后无法赋值
  • 机器学习西瓜书
  • 我的电赛(简易的波形发生器大一暑假回顾)
  • 字节跳动开源通用图像定制模型DreamO,支持风格转换、换衣、身份定制、多条件组合等多种功能~
  • 【android bluetooth 协议分析 01】【HCI 层介绍 4】【LeSetEventMask命令介绍】
  • 【C语言】字符串函数及其部分模拟实现
  • JavaScript:元宇宙角色动作与移动
  • 6.2.5图的基本操作
  • TYUT-企业级开发教程-第二章
  • 学习STC51单片机05(芯片为STC89C52RC)
  • 发布时将多个bpl 打包成一个bpl的方法,或者说:不需要vcl60.bpl情况下 18.5K的exe 照常可以运行。
  • deepseek系列论文汇总(时至2025.5)
  • 2023 睿抗机器人开发者大赛CAIP-编程技能赛-高职组(省赛)解题报告 | 珂学家
  • AGI大模型(24):通过LangChain的接口来调用OpenAI对话
  • 【AWS入门】Amazon Bedrock简介