网络IO与IO多路复用

一、网络IO基础

  • 系统对象
    • 网络IO涉及用户空间调用IO的进程或线程以及内核空间的内核系统。
    • 例如,当进行read操作时,会经历两个阶段:
      1. 等待数据准备就绪。
      2. 将数据从内核拷贝到进程或线程中。
  • 多种网络IO模型的出现原因:由于上述两个阶段的不同情况,出现了多种网络IO模型。

二、阻塞IO(blocking IO)

  • 特点
    • 在Linux中,默认所有socket都是阻塞的。
    • 以读操作为例,当用户进程调用read系统调用,kernel开始IO的第一阶段(准备数据),对于网络IO,数据可能未到,kernel要等待,用户进程会被阻塞。
    • 直到kernel等到数据准备好,将数据从kernel拷贝到用户内存并返回结果,用户进程才解除阻塞状态。
    • 即阻塞IO在等待数据和拷贝数据两个阶段都阻塞进程。
    • 例如,使用listen()send()recv()等接口构建服务器/客户机模型,这些接口大多是阻塞型的。
    • 代码示例:
// 创建服务器端的socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// 绑定地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = INADDR_ANY;
bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
// 监听连接
listen(serverSocket, 5);
// 接受连接
int clientSocket = accept(serverSocket, NULL, NULL);
// 接收数据,在此处进程会阻塞,直到接收到数据或出错
char buffer[1024];
recv(clientSocket, buffer, sizeof(buffer), 0);
// 发送数据,在此处进程也可能阻塞
send(clientSocket, buffer, strlen(buffer), 0);
- 解释:上述代码首先创建了一个服务器端的socket,然后绑定地址和端口,接着监听连接。当调用`accept`时,如果没有连接请求,会阻塞等待。一旦有连接,调用`recv`接收数据时,会阻塞直到有数据到达。发送数据时也可能阻塞,例如网络拥堵或接收方接收缓冲区满。
  • 多线程/多进程改进方案
    • 为解决单个连接阻塞影响其他连接的问题,可在服务器端使用多线程或多进程。
    • 多线程可使用pthread_create()创建,多进程使用fork()创建。
    • 多线程开销相对小,适合为较多客户机服务;进程更安全,适合单个服务执行体需要大量CPU资源的情况。
    • 例如,服务器端为多个客户机提供服务时,可在主线程等待连接请求,有连接时创建新线程或新进程提供服务。
    • 但当需要同时响应大量(成百上千)连接请求时,多线程或多进程会严重占用系统资源,导致系统效率下降和假死。

三、非阻塞IO(non-blocking IO)

  • 特点
    • 通过设置socket为非阻塞,如使用fcntl( fd, F_SETFL, O_NONBLOCK );
    • 当用户进程发出read操作,若kernel数据未准备好,不会阻塞用户进程,而是立即返回error
    • 用户进程可根据返回结果判断数据是否准备好,未准备好可再次发送read操作。
    • 当数据准备好,kernel会将数据拷贝到用户内存并返回。
    • 例如,recv()接口在非阻塞状态下调用后立即返回,返回值有不同含义:
      • recv()返回值大于 0,表示接受数据完毕,返回值是接收到的字节数。
      • recv()返回 0,表示连接正常断开。
      • recv()返回 -1,且errno等于EAGAIN,表示recv操作未完成。
      • recv()返回 - 1,且errno不等于EAGAIN,表示recv操作遇到系统错误errno
    • 代码示例:
// 创建服务器端的socket
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
// 设置为非阻塞
fcntl(serverSocket, F_SETFL, O_NONBLOCK);
// 绑定地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
serverAddr.sin_addr.s_addr = INADDR_ANY;
bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
// 监听连接
listen(serverSocket, 5);
// 接受连接
int clientSocket = accept(serverSocket, NULL, NULL);
// 接收数据,此处不会阻塞,会根据返回值判断状态
char buffer[1024];
int recvResult;
while ((recvResult = recv(clientSocket, buffer, sizeof(buffer), 0)) == -1 && errno == EAGAIN) {// 数据未准备好,可进行其他操作或再次尝试接收
}
if (recvResult > 0) {// 处理接收到的数据
} else if (recvResult == 0) {// 连接断开
} else {// 其他错误
}
- 解释:在这个示例中,将服务器端socket设置为非阻塞后,调用`recv`时不会阻塞进程。如果`recv`返回-1且`errno`为`EAGAIN`,说明数据还未准备好,程序可继续执行其他操作或过段时间再尝试接收,而不是像阻塞IO那样一直等待。

四、多路复用IO(IO multiplexing)

  • 特点
    • 又称事件驱动IO(event driven IO),如select/epoll
    • 单个进程可同时处理多个网络连接的IO,基本原理是select/epoll函数不断轮询所负责的所有socket,当某个socket有数据到达,通知用户进程。
    • 流程:用户进程调用select会被阻塞,kernel监视select负责的socket,当有socket数据准备好,select返回,用户进程再调用read操作将数据从kernel拷贝到用户进程。
    • 虽然使用select需要两个系统调用(selectread),但优势是可在一个线程内处理多个socket的IO请求。
    • 代码示例:
#include <stdio.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>int main() {int serverSocket = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(8080);serverAddr.sin_addr.s_addr = INADDR_ANY;bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));listen(serverSocket, 5);fd_set readfds;FD_ZERO(&readfds);FD_SET(serverSocket, &readfds);int maxFd = serverSocket;while (1) {fd_set tempFds = readfds;int activity = select(maxFd + 1, &tempFds, NULL, NULL, NULL);if (FD_ISSET(serverSocket, &tempFds)) {int clientSocket = accept(serverSocket, NULL, NULL);FD_SET(clientSocket, &readfds);if (clientSocket > maxFd) {maxFd = clientSocket;}}for (int i = 0; i <= maxFd; i++) {if (FD_ISSET(i, &tempFds) && i!= serverSocket) {char buffer[1024];int valread = recv(i, buffer, 1024, 0);if (valread == 0) {close(i);FD_CLR(i, &readfds);} else {// 处理接收到的数据}}}}return 0;
}
- 解释:首先创建服务器socket,绑定并监听。`FD_ZERO`和`FD_SET`用于初始化和设置文件描述符集合。`select`函数会阻塞,等待集合中文件描述符的可读事件。当`serverSocket`有新连接时,添加新连接的socket到集合,并更新最大文件描述符。当其他socket可读时,接收数据并处理。
  • 接口原型
    • FD_ZERO(int fd, fd_set* fds):初始化fd_set
    • FD_SET(int fd, fd_set* fds):将句柄添加到fd_set
    • FD_ISSET(int fd, fd_set* fds):检查句柄是否在fd_set中。
    • FD_CLR(int fd, fd_set* fds):从fd_set中移除句柄。
    • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout):用于探测多个文件句柄的状态变化,readfdswritefdsexceptfds作为输入和输出参数,可设置超时时间。
  • 问题
    • 当需要探测的句柄值较大时,select()接口本身需要大量时间轮询,很多操作系统提供更高效接口如epoll(Linux)、kqueue(BSD)、/dev/poll(Solaris)等。
    • 该模型将事件探测和事件响应夹杂,若事件响应执行体庞大,会降低事件探测的及时性。
    • 可使用事件驱动库如libeventlibev库解决上述问题。

五、异步IO(Asynchronous I/O)

  • 特点
    • Linux下的异步IO主要用于磁盘IO读写操作,从内核2.6版本开始引入。
    • 用户进程发起read操作后可做其他事,kernel收到asynchronous read后立刻返回,不会阻塞用户进程。
    • kernel等待数据准备完成,将数据拷贝到用户内存,完成后给用户进程发送信号通知。
    • 代码示例(使用aio_read):
#include <aio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>void aio_completion_handler(union sigval sigval) {struct aiocb *req = (struct aiocb *)sigval.sival_ptr;if (aio_error(req) == 0) {char *buffer = (char *)malloc(aio_return(req));// 处理读取到的数据free(buffer);}aio_destroy(req);
}int main() {int fd = open("test.txt", O_RDONLY);if (fd < 0) {perror("open");return 1;}struct aiocb my_aiocb;memset(&my_aiocb, 0, sizeof(struct aiocb));my_aiocb.aio_fildes = fd;my_aiocb.aio_buf = malloc(1024);my_aiocb.aio_nbytes = 1024;my_aiocb.aio_offset = 0;my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;my_aiocb.aaiocb.sigev_notify_function = aio_completion_handler;my_aiocb.aio_sigevent.sigev_notify_attributes = NULL;my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;if (aio_read(&my_aiocb) < 0) {perror("aio_read");close(fd);return 1;}// 进程可做其他事情sleep(1);return 0;
}
- 解释:上述代码使用`aio_read`进行异步读操作。首先打开文件,设置`aiocb`结构(包含文件描述符、缓冲区、字节数等),并设置信号通知方式和处理函数。调用`aio_read`后,进程可以继续做其他事情,当读操作完成,会调用`aio_completion_handler`处理结果。
  • 重要性:异步IO是真正非阻塞的,对高并发网络服务器实现至关重要。

六、信号驱动IO(signal driven I/O, SIGIO)

  • 特点
    • 允许套接口进行信号驱动I/O并安装信号处理函数,进程继续运行不阻塞。
    • 当数据准备好,进程收到SIGIO信号,可在信号处理函数中调用I/O操作函数处理数据。
    • 优势在于等待数据报到达期间,进程可继续执行,避免了select的阻塞与轮询。

七、服务器模型Reactor与Proactor

  • Reactor模型
    • 是一种事件驱动机制,用于同步I/O。
    • 应用程序将处理I/O事件的接口注册到Reactor上,若相应事件发生,Reactor调用注册的接口(回调函数)。
    • 三个重要组件:
      1. 多路复用器:如selectpollepoll等系统调用。
      2. 事件分发器:将多路复用器返回的就绪事件分到对应的处理函数。
      3. 事件处理器:负责处理特定事件的处理函数。
    • 具体流程:
      1. 注册读就绪事件和相应事件处理器。
      2. 事件分离器等待事件。
      3. 事件到来,激活分离器,分离器调用事件对应的处理器。
      4. 事件处理器完成实际的读操作,处理数据,注册新事件,返还控制权。
    • 优点:响应快,编程相对简单,可扩展性和可复用性好。
    • 缺点:当程序需要使用多核资源时会有局限,因为通常是单线程的。
    • 代码示例:
#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>class EventHandler {
public:virtual void handleEvent(int fd) = 0;
};class Reactor {
private:int epollFd;std::vector<EventHandler*> handlers;
public:Reactor() {epollFd = epoll_create1(0);}~Reactor() {close(epollFd);}void registerHandler(int fd, EventHandler* handler) {struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN;epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &event);handlers.push_back(handler);}void handleEvents() {struct epoll_event events[10];int numEvents = epoll_wait(epollFd, events, 10, -1);for (int i = 0; i < numEvents; i++) {int fd = events[i].data.fd;for (EventHandler* handler : handlers) {handler->handleEvent(fd);}}}
};class EchoHandler : public EventHandler {
public:void handleEvent(int fd) override {char buffer[1024];int bytesRead = recv(fd, buffer, sizeof(buffer), 0);if (bytesRead > 0) {send(fd, buffer, bytesRead, 0);}}
};int main() {Reactor reactor;int serverSocket = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serverAddr;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(8080);serverAddr.sin_addr.s_addr = INADDR_ANY;bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr));listen(serverSocket, 5);EchoHandler handler;reactor.registerHandler(serverSocket, &handler);while (1) {reactor.handleEvents();}return 0;
}
- 解释:上述C++代码中,`Reactor`类负责创建`epoll`,注册和处理事件。`EventHandler`是抽象基类,`EchoHandler`是具体处理接收和发送的派生类。`main`函数创建服务器socket,注册`EchoHandler`到`Reactor`,并不断调用`handleEvents`处理事件。
  • Proactor模型
    • 最大特点是使用异步I/O,所有I/O操作都交由系统的异步I/O接口执行,工作线程只负责业务逻辑。
    • 具体流程:
      1. 处理器发起异步操作并关注I/O完成事件。
      2. 事件分离器等待操作完成事件。
      3. 分离器等待时,内核并行执行实际I/O操作并将结果存入用户缓冲区,通知分离器读操作完成。
      4. I/O完成后,通过事件分离器呼唤处理器。
      5. 事件处理器处理用户缓冲区中的数据。
    • 增加了编程复杂度,但给工作线程带来更高效率,可利用系统态的读写优化。
    • 在Windows上常用IOCP支持高并发,Linux上因aio性能不佳,主要以Reactor模型为主。
    • 也可使用Reactor模拟Proactor,但在读写并行能力上会有区别。

八、同步I/O和异步I/O的区别总结

  • 阻塞与非阻塞IO的区别
    • 调用阻塞IO会阻塞进程直到操作完成,非阻塞IO在kernel准备数据时会立刻返回。
  • 同步与异步IO的区别
    • 同步IO在做“IO operation”(如read系统调用)时会阻塞进程。阻塞IO、非阻塞IO、多路复用IO都属于同步IO。
    • 非阻塞IO在数据准备好时的拷贝数据阶段会阻塞进程;而异步IO在整个过程中,进程不会被阻塞,进程发起IO操作后直接做其他事,直到kernel发送信号通知完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/3173.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

菜品管理(day03)

公共字段自动填充 问题分析 业务表中的公共字段&#xff1a; 而针对于这些字段&#xff0c;我们的赋值方式为&#xff1a; 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。 在更新数据时, 将updateTime 设置为当前时间…

Python股票量化交易分析-开发属于自己的指标

需求&#xff1a;股票的量化交易指标很多&#xff0c;我想做一个自己的量化交易图表&#xff1a; 展示每天交易量和股价振幅的关系图进一步的话想知道单位金额对股价振幅的影响&#xff0c;最终实现大概估计需要多少买入成交量能拉升多少股价&#xff09; &#xff0c; 目前未…

python爬虫爬取淘宝商品比价||淘宝商品详情API接口

最近在学习北京理工大学的爬虫课程&#xff0c;其中一个实例是讲如何爬取淘宝商品信息&#xff0c;现整理如下&#xff1a; 功能描述&#xff1a;获取淘宝搜索页面的信息&#xff0c;提取其中的商品名称和价格 探讨&#xff1a;淘宝的搜索接口 翻页的处理 技术路线:requests…

OpenCV相机标定与3D重建(60)用于立体校正的函数stereoRectify()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 为已校准的立体相机的每个头计算校正变换。 cv::stereoRectify 是 OpenCV 中用于立体校正的函数&#xff0c;它基于已知的相机参数和相对位置&am…

1.17组会汇报

STRUC-BENCH: Are Large Language Models Good at Generating Complex Structured Tabular Data? STRUC-BENCH&#xff1a;大型语言模型擅长生成复杂的结构化表格数据吗&#xff1f;23年arXiv.org 1概括 这篇论文旨在评估大型语言模型&#xff08;LLMs&#xff09;在生成结构…

【机器学习实战入门】使用 Pandas 和 OpenCV 进行颜色检测

Python 颜色检测项目 今天的项目将非常有趣和令人兴奋。我们将与颜色打交道&#xff0c;并在项目过程中学习许多概念。颜色检测对于识别物体来说是必要的&#xff0c;它也被用作各种图像编辑和绘图应用的工具。 什么是颜色检测&#xff1f; 颜色检测是检测任何颜色名称的过程…

【k8s面试题2025】3、练气中期

体内灵气的量和纯度在逐渐增加。 文章目录 在 Kubernetes 中自定义 Service端口报错常用控制器Kubernetes 中拉伸收缩副本失效设置节点容忍异常时间Deployment 控制器的升级和回滚日志收集资源监控监控 Docker将 Master 节点设置为可调度 在 Kubernetes 中自定义 Service端口报…

数智化转型 | 星环科技Defensor 助力某银行数据分类分级

在数据驱动的金融时代&#xff0c;数据安全和隐私保护的重要性日益凸显。某银行作为数字化转型的先行者&#xff0c;面临着一项艰巨的任务&#xff1a;如何高效、准确地对分布在多个业务系统、业务库与数仓数湖中的约80万个字段进行数据分类和分级。该银行借助星环科技数据安全…

mac配置 iTerm2 使用lrzsz与服务器传输文件

mac配置 1. 安装支持rz和sz命令的lrzsz brew install lrzsz2. 下载iterm2-send-zmodem.sh和iterm2-recv-zmodem.sh两个脚本 # 克隆仓库 git clone https://github.com/aikuyun/iterm2-zmodem ~/iterm2-zmodem# 进入到仓库目录 cd ~/iterm2-zmodem# 设置脚本文件可执行权限 c…

redis 分布式重入锁

文章目录 前言一、分布式重入锁1、单机重入锁2、redis重入锁 二、redisson实现重入锁1、 添加依赖2、 配置 Redisson 客户端3、 使用 Redisson 实现重入锁4、 验证5、运行项目 三、redisson分布式锁分析1、获取锁对象2、 加锁3、订阅4、锁续期5、释放锁6、流程图 前言 通过前篇…

【git】如何删除本地分支和远程分支?

1.如何在 Git 中删除本地分支 本地分支是您本地机器上的分支&#xff0c;不会影响任何远程分支。 &#xff08;1&#xff09;在 Git 中删除本地分支 git branch -d local_branch_name git branch 是在本地删除分支的命令。-d是一个标志&#xff0c;是命令的一个选项&#x…

关于 Cursor 的一些学习记录

文章目录 1. 写在最前面2. Prompt Design2.1 Priompt v0.1&#xff1a;提示设计库的首次尝试2.2 注意事项 3. 了解 Cursor 的 AI 功能3.1 问题3.2 答案 4. cursor 免费功能体验5. 写在最后面6. 参考资料 1. 写在最前面 本文整理了一些学习 Cursor 过程中读到的或者发现的感兴趣…

使用python+pytest+requests完成自动化接口测试(包括html报告的生成和日志记录以及层级的封装(包括调用Json文件))

一、API的选择 我们进行接口测试需要API文档和系统&#xff0c;我们选择JSONPlaceholder免费API&#xff0c;因为它是一个非常适合进行接口测试、API 测试和学习的工具。它免费、易于使用、无需认证&#xff0c;能够快速帮助开发者模拟常见的接口操作&#xff08;增、删、改、…

【Rust自学】13.2. 闭包 Pt.2:闭包的类型推断和标注

13.2.0. 写在正文之前 Rust语言在设计过程中收到了很多语言的启发&#xff0c;而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。 在本章中&#xff0c;我们会讨论 Rust 的一…

无人机技术架构剖析!

一、飞机平台系统 飞机平台系统是无人机飞行的主体平台&#xff0c;主要提供飞行能力和装载功能。它由机体结构、动力装置、电气设备等组成。 机体结构&#xff1a;无人机的机身是其核心结构&#xff0c;承载着其他各个组件并提供稳定性。常见的机身材料包括碳纤维、铝合金、…

Axios封装一款前端项目网络请求实用插件

前端项目开发非常经典的插件axios大家都很熟悉&#xff0c;它是一个Promise网络请求库&#xff0c;可以用于浏览器和 node.js 支持的项目中。像一直以来比较火的Vue.js开发的几乎所有项目网络请求用的都是axios。那么我们在实际的项目中&#xff0c;有时候为了便于维护、请求头…

【c++继承篇】--继承之道:在C++的世界中编织血脉与传承

目录 引言 一、定义二、继承定义格式2.1定义格式2.2继承关系和访问限定符2.3继承后子类访问权限 三、基类和派生类赋值转换四、继承的作用域4.1同名变量4.2同名函数 五、派生类的默认成员构造函数5.1**构造函数调用顺序&#xff1a;**5.2**析构函数调用顺序&#xff1a;**5.3调…

LDD3学习8--linux的设备模型(TODO)

在LDD3的十四章&#xff0c;是Linux设备模型&#xff0c;其中也有说到这个部分。 我的理解是自动在应用层也就是用户空间实现设备管理&#xff0c;处理内核的设备事件。 事件来自sysfs和/sbin/hotplug。在驱动中&#xff0c;只要是使用了新版的函数&#xff0c;相应的事件就会…

Python基于Django的图像去雾算法研究和系统实现(附源码,文档说明)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

Python爬虫(5) --爬取网页视频

文章目录 爬虫爬取视频 指定url发送请求 UA伪装请求页面 获取想要的数据 解析定位定位音视频位置 存放视频完整代码实现总结 爬虫 Python 爬虫是一种自动化工具&#xff0c;用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持&#xff08;如…