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

不同网络I/O模型的原理

目录

1、I/O的介绍

1.1、I/O 操作分类

1.2、I/O操作流程阶段

1.3、I/O分类

2、同步I/O

2.1、阻塞I/O

2.2、非阻塞I/O

2.3、I/O复用

2.4、信号驱动式I/O

3、异步I/O


前言

        在网络I/O之中,I/O操作往往会涉及到两个系统对象,一个是用户空间调用I/O的进程或者线程,另一个是内核空间的内核系统,当发生I/O操作时,会经历以下两个阶段:

  1. 等待数据准备就绪
  2. 将数据从内核拷贝到进程或线程中

因为在以上两个阶段上各有不同的情况,所以出现了多种I/O模型。

如下图所示:

关于内核态和用户态的介绍,可参考:操作系统的内核态和用户态场景-CSDN博客


1、I/O的介绍

         I/O 操作(Input/Output Operation)指的是计算机系统中与外部环境(如网络、磁盘、键盘、显示器等)进行数据交换的过程。

1.1、I/O 操作分类

1.网络输入(接收数据):

如 recv() 或 read() 函数,用于从网络连接中读取数据。


2.网络输出(发送数据):

如 send() 或 write() 函数,用于向网络连接发送数据。


3.网络连接管理:

如 accept() 函数,用于接受来自客户端的连接请求。


4.网络监听:

如 listen() 函数,用于监听进入的连接请求。

1.2、I/O操作流程阶段

通常用户进程中的一个完整I/O分为两个阶段:

 用户进程空间→内核空间 ,内核空间→设备空间;

整体模型图所示:

1.3、I/O分类

I/O分为内存I/O、网络I/O和磁盘I/O三种。

1、内存I/O

        涉及直接与计算机内存进行数据交换,如读取和写入内存中的数据。


2、网络I/O

        涉及通过网络接口进行的数据交换,例如从服务器下载文件或向服务器发送请求。


3、磁盘I/O

        涉及与存储设备(如硬盘驱动器或固态硬盘)进行数据交换,例如读取和写入磁盘上的文件。

        Linux中进程无法直接操作I/O设备,其必须通过系统调用请求内核来协助完成I/O操作。 内核会为每个I/O设备(例如硬盘、网络接口)维护一个缓冲区,缓冲区是内存中的一块区域,用于临时存储数据,以提高I/O操作的效率。

         对于一个输入操作来说,进程I/O系统调用(如 read())读取数据时,它会先看为该设备维护的缓冲区,看看是否已经有待处理的数据。没有的话再到设备(比如网卡设备)中读取(因为设备I/O一般速度较慢,需要等待)。

        如果缓冲区中没有数据,内核会发起设备I/O操作,从设备(例如网络接口卡、磁盘等)中读取数据。由于设备I/O操作通常较慢,需要时间来完成,内核会先等待数据到达设备缓冲区,再进行处理。

如下图所示:

所以,对于一个网络输入操作通常包括两个不同阶段:

        1、等待网络数据到达网卡,把数据从网卡读取到内核缓冲区,准备好数据。

        2、从内核缓冲区复制数据到用户进程空间。

⚠️注意:

        如果缓冲区中有数据:内核会直接从缓冲区中获取数据,并将这些数据复制到用户进程的地址空间。这种情况通常较快,因为数据已经在内核空间中,可以快速传递给用户进程。

小结

         Socket:在Linux中,socket是一种用于网络通信的抽象接口,提供了数据传输的机制。socket可以被看作是一个端点,允许进程通过它进行数据的发送和接收。

        流的抽象:在Linux中,socket被视为一种流(例如TCP流),它允许进程以流式方式读写数据。对于TCP协议来说,数据流是有序的、可靠的。

        流的抽象可以理解为像流水一样的数据传输方式,强调了数据传输的连续性和按序性,而不关心数据的具体格式。当使用socket进行网络通信时,网络层和传输层只处理数据的流动,应用层则负责数据的解析和处理。

        

        I/O操作:对socket的读写操作实际上是对网络流的操作。当进程发起对socket的读取操作时,内核会检查缓冲区是否有数据可用。如果有,数据会被传递给进程;如果没有,进程会被阻塞直到数据到达。


2、同步I/O

2.1、阻塞I/O

也称为BIO(Blocking I/O)。

1. 核心原理

阻塞式 I/O:每个连接由一个线程处理,线程在读写数据时会阻塞,直到数据准备好。

典型模型一请求一线程(One Thread Per Connection)。

底层机制:基于 InputStream 和 OutputStream 的阻塞操作。

整体原理图如下所示:

流程如下:

        第一步通常涉及等待数据从网络中到达,当所有等待分组到达时,它被复制到内核中的某个缓冲区。

        第二步是把数据从内核缓冲区复制到应用程序缓冲区。

        在Linux中,默认情况下,阻塞I/O的所有套接字都是阻塞的。

2.代码示例

// BIO 服务器示例
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {Socket socket = serverSocket.accept(); // 阻塞,等待连接new Thread(() -> {InputStream input = socket.getInputStream();byte[] buffer = new byte[1024];int len = input.read(buffer); // 阻塞,等待数据System.out.println(new String(buffer, 0, len));}).start();
}

3. 优点

  • 实现简单:代码结构直观,适合小规模应用。
  • 调试方便:线程模型清晰,便于排查问题。

4. 缺点

  • 性能瓶颈:高并发时,线程数激增,占用大量内存和 CPU,容易导致资源耗尽。
  • 扩展性差:不适合处理成千上万的并发连接。

5. 适用场景

  • 低并发场景:如小型客户端/服务器应用(如命令行工具、简单的 HTTP 服务)。
  • 简单协议处理:对性能要求不高的场景。

2.2、非阻塞I/O

允许应用程序在数据未准备好时不必等待,可以继续执行其他任务。

(Non-blocking I/O)模型如下所示:

流程如下:

        非阻塞的recvform系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error(EAGAIN或EWOULDBLOCK)。

         进程在返回之后,可以先处理其他的业务逻辑,稍后再发起recvform系统调用。 采用轮询的方式检查内核数据,直到数据准备好。再拷贝数据到进程,进行数据处理。

        在Linux下,可以通过设置套接字选项使其变为非阻塞。

总结:

        可以看到前三次调用recvfrom请求时,并没有数据返回,内核返回errno(EWOULDBLOCK),并不会阻塞进程。 当第四次调用recvfrom时,数据已经准备好了,于是将它从内核空间拷贝到程序空间,处理数据。

⚠️注意:但是将数据从内核拷贝到用户空间,这个阶段阻塞。

2.3、I/O复用

        多路监控:使用select、poll或epoll等系统调用来监控多个I/O流。当其中一个I/O流有数据可读或可写时,系统调用返回。适用于在单个线程内管理多个连接。

(I/O Multiplexing)模型如下所示:

流程如下:

        I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。

        它的基本原理是不再由应用程序自己监视连接,而由内核替应用程序监视文件描述符。通过 select、poll、epoll 等机制,允许一个进程同时监视多个文件描述符,当某个文件描述符就绪时再进行 IO 操作。这种模型下,程序可以同时处理多个连接,提高了并发处理能力。

示例:

         以select函数为例,当用户进程调用了select,那么整个进程会被阻塞,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好,select就会返回。 这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程。

⚠️注意:

  • 当客户处理多个描述符时,必须使用I/O复用;
  • 如果一个TCP服务器既要处理监听套接字,又要处理已连接的套接字,一般就要使用I/O复用;
  • 如果一个服务器既要处理TCP,又要处理UDP,一般就要使用I/O复用。
  • 如果一个服务器要处理多个协议或多个服务,一般就需要使用I/O复用。 

2.4、信号驱动式I/O

        该模型允许socket进行信号驱动I/O,并注册一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

模型如下图所示:

注意:

        虽然信号驱动IO在注册完信号处理函数以后,就可以做其他事情了。但是第二阶段拷贝数据的过程当中进程依然是被阻塞的。

3、异步I/O

    异步I/O的工作机制:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。

异步I/O与信号驱动I/O模型区别在于:

        信号驱动式I/O是有内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

模型如下图所示:

        异步I/O不是按顺序执行。用户进程进行aio_read系统调用之后,就可以去处理其他逻辑了,无论内核数据是否准备好,都会直接返回给用户进程,不会对进程造成阻塞。

        这是因为aio_read只向内核递交申请,并不关心有没有数据。 等到数据准备好了,内核直接复制数据到进程空间,然后内核向进程发送通知,此时数据已经在用户空间了,可以对数据进行处理。


总结

同步I/O和异步I/O的比较:

同步I/O:导致请求进程阻塞,直到I/O操作完成;
异步I/O:不导致请求进程阻塞;

简单的讲:就是是否参与了I/O操作;

        前四种I/O模型——阻塞式I/O模型、非阻塞式I/O模型、I/O复用(多路转接)和信号驱动式I/O模型都是同步I/O,第二步从数据从内核态copy到用户态的I/O操作(recvfrom)将进程阻塞。

        只有异步I/O模型是异步I/O。


参考文章:

1、【编程基础知识】网络I/O模型详解:从阻塞到异步_网络编程 阻塞-CSDN博客

2、五种I/O模型详解-CSDN博客

3、【网络编程下】五种网络IO模型-CSDN博客

相关文章:

  • 2506C++,C++时间库与C时间
  • C++/Qt 联合编程中的定时器使用陷阱:QObject::startTimer 报错详解
  • 华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio打造AingDesk AI聊天助手
  • iosAppStore上架流程,保姆级记录(3)
  • 基于 SpaCy 框架的依存句法分析实战指南
  • dpdk-testpmd 测试常用功能记录
  • 5. 相机拍摄简单构图
  • 音频导入规范
  • linux回收站
  • 安卓15开机启动Fallbackhome去除--成果展示
  • uniapp 页面栈一定深度后,回首页导航到新页面的解决方案
  • 【深度学习:进阶篇】--2.4.BN与神经网络调优
  • 基于RocketMQ源码理解顺序写、刷盘机制与零拷贝
  • 零基础实战:云开发家政维修小程序搭建指南
  • Cesium圆锥渐变色实现:融合顶点着色器、Canvas动态贴图与静态纹理的多方案整合
  • UE5 学习系列(五)导入贴图资产
  • 2025年的WWDC所更新的内容
  • 绝缘胶垫怎么选择,耐压、防滑、厚度、质量参数如何选择?
  • 商标注册小类怎么选?业务+战略双维度匹配
  • Cpp 知识3
  • 网站构建器/网站制作策划书
  • 一键生成微信小程序平台/济南seo小黑seo
  • 做动态网站的用工具/seo新闻
  • 老网站改版启用二级域名/百度平台商家联系方式
  • 黑客是如何攻击网站的/最新百度快速排名技术
  • 做胎儿羊水鉴定网站/关键词排名提升工具