IO多路复用:select、poll、epoll的底层区别

1. select

工作原理:
  • select 使用一个固定大小的数组来存储需要监控的文件描述符。这个数组的大小通常是限制在 FD_SETSIZE(通常为 1024)内。
  • 当调用 select 时,操作系统会检查每个文件描述符的状态,确定哪些描述符准备好进行 I/O 操作。
  • select 是阻塞的,即如果没有文件描述符准备好,它会阻塞调用线程直到有文件描述符变为可用状态,或达到超时。
优缺点:
  • 优点
    • 简单易用,适合于小规模的文件描述符监控。
  • 缺点
    • 不支持超过 FD_SETSIZE 的文件描述符。
    • 每次调用都需要将文件描述符数组复制到内核空间,性能开销大。
    • 每次调用时都要遍历整个文件描述符集合,效率低下,尤其是在大量文件描述符时。

2. poll

工作原理:
  • poll 使用一个可变大小的数组(pollfd 结构体数组)来存储需要监控的文件描述符。与 select 不同,poll 不再受到 FD_SETSIZE 的限制。
  • 调用 poll 时,它会检查 pollfd 数组中每个描述符的状态,并返回准备好 I/O 操作的文件描述符数量。
  • poll 也是阻塞的。
优缺点:
  • 优点
    • 不受 FD_SETSIZE 的限制,可以处理任意数量的文件描述符。
    • 结构更灵活,能够更容易地扩展。
  • 缺点
    • 仍然需要在每次调用时遍历整个数组,性能问题仍然存在。
    • 每次调用时,文件描述符状态的更新需要复制数据到内核,性能开销依旧。

3. epoll

工作原理:
  • epoll 是为了解决 select 和 poll 的一些性能瓶颈而设计的。它使用一个文件描述符来表示一个 epoll 实例。
  • epoll 通过 epoll_ctl 添加和删除文件描述符,而不是在每次调用中传递整个文件描述符数组。它维护了一个内核中的事件表。
  • epoll_wait 只返回准备好 I/O 操作的文件描述符,而不是遍历所有描述符。
  • 可以选择边缘触发(Edge Triggered)和水平触发(Level Triggered)模式。
  • 在水平触发模式下,当某个文件描述符(FD)变为可读、可写或发生异常时,epoll_wait 会返回该文件描述符的事件。只要文件描述符的状态满足条件(如可读),即使后续调用 epoll_wait 也会继续返回该文件描述符,直到应用程序将所有数据读出或写入完成。应用程序可以多次读取数据,确保数据不会丢失。适合需要确保数据完整处理的场景。
  • 在边缘触发模式下,当文件描述符的状态发生变化(例如从不可读变为可读)时,epoll_wait 会返回该文件描述符。一旦状态变化通知后,只有在后续状态再次变化时(如读入数据后再次变为可读),才会再次触发通知。如果没有读取所有可用数据,可能会错过后续的事件通知。适合高性能和高并发的场景。
优缺点:
  • 优点
    • 高效:适用于大量并发连接,尤其是在高并发的场景下,性能显著提高。
    • 不需要在每次调用时复制文件描述符的状态,减少了开销。
    • 只返回准备好的描述符,避免了遍历所有描述符的开销。
  • 缺点
    • API 比较复杂,使用上相对 select 和 poll 难度较高。
    • 只在 Linux 系统中可用,移植性较差。

总结

特性selectpollepoll
文件描述符限制有 (FD_SETSIZE)没有没有
数据结构固定大小的位图可变大小的结构体数组内核维护的事件表
调用性能O(n),每次遍历O(n),每次遍历O(1),只返回活跃的文件描述符
内存复制每次调用都需要每次调用都需要仅在添加/删除时需要
触发模式水平触发水平触发支持水平触发和边缘触发
适用场景小型应用中型应用大型高并发应用
  • select:简单但有文件描述符数量限制,性能在高并发情况下不佳。
  • poll:解决了 select 的限制,但性能依然受到遍历数组的影响。
  • epoll:最适合高并发场景,提供更高的性能和更灵活的接口,适合处理大量文件描述符。

代码示例

每个示例都创建了一个简单的 TCP 服务器,接收客户端的连接请求并处理数据。

1. 使用 select

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>#define PORT 8080
#define MAX_CLIENTS 10int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};// 创建 socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置 socket 选项setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听listen(server_fd, MAX_CLIENTS);fd_set read_fds;int max_sd;while (true) {// 清空 fd_setFD_ZERO(&read_fds);FD_SET(server_fd, &read_fds);max_sd = server_fd;// 添加客户端 socketsfor (int i = 0; i < MAX_CLIENTS; i++) {if (FD_ISSET(i, &read_fds) && i > 0) {FD_SET(i, &read_fds);if (i > max_sd) max_sd = i;}}// 调用 selectint activity = select(max_sd + 1, &read_fds, nullptr, nullptr, nullptr);if ((activity < 0) && (errno != EINTR)) {std::cerr << "select error" << std::endl;}// 处理新连接if (FD_ISSET(server_fd, &read_fds)) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}std::cout << "New connection: " << new_socket << std::endl;FD_SET(new_socket, &read_fds);}// 处理客户端请求for (int i = 0; i < MAX_CLIENTS; i++) {if (FD_ISSET(i, &read_fds)) {int valread = read(i, buffer, 1024);if (valread == 0) {// 客户端断开连接std::cout << "Client disconnected: " << i << std::endl;close(i);FD_CLR(i, &read_fds);} else {buffer[valread] = '\0';std::cout << "Received from client " << i << ": " << buffer << std::endl;send(i, buffer, valread, 0); // 回送数据}}}}return 0;
}

2. 使用 poll

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>#define PORT 8080
#define MAX_CLIENTS 10int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};// 创建 socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置 socket 选项setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听listen(server_fd, MAX_CLIENTS);struct pollfd fds[MAX_CLIENTS + 1];fds[0].fd = server_fd;fds[0].events = POLLIN;for (int i = 1; i <= MAX_CLIENTS; i++) {fds[i].fd = -1; // 初始化客户端 sockets}while (true) {int activity = poll(fds, MAX_CLIENTS + 1, -1); // 等待无限期if ((activity < 0) && (errno != EINTR)) {std::cerr << "poll error" << std::endl;}// 处理新连接if (fds[0].revents & POLLIN) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}std::cout << "New connection: " << new_socket << std::endl;for (int i = 1; i <= MAX_CLIENTS; i++) {if (fds[i].fd == -1) {fds[i].fd = new_socket;fds[i].events = POLLIN;break;}}}// 处理客户端请求for (int i = 1; i <= MAX_CLIENTS; i++) {if (fds[i].fd > 0 && (fds[i].revents & POLLIN)) {int valread = read(fds[i].fd, buffer, 1024);if (valread == 0) {// 客户端断开连接std::cout << "Client disconnected: " << fds[i].fd << std::endl;close(fds[i].fd);fds[i].fd = -1; // 标记为无效} else {buffer[valread] = '\0';std::cout << "Received from client " << fds[i].fd << ": " << buffer << std::endl;send(fds[i].fd, buffer, valread, 0); // 回送数据}}}}return 0;
}

3. 使用 epoll

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>#define PORT 8080
#define MAX_CLIENTS 10int main() {int server_fd, new_socket;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[1024] = {0};// 创建 socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置 socket 选项setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);bind(server_fd, (struct sockaddr *)&address, sizeof(address));// 监听listen(server_fd, MAX_CLIENTS);// 创建 epoll 实例int epoll_fd = epoll_create1(0);struct epoll_event event, events[MAX_CLIENTS];// 添加 server socket 到 epollevent.events = EPOLLIN;event.data.fd = server_fd;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);while (true) {int num_events = epoll_wait(epoll_fd, events, MAX_CLIENTS, -1);for (int i = 0; i < num_events; i++) {if (events[i].data.fd == server_fd) {// 处理新连接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}std::cout << "New connection: " << new_socket << std::endl;// 将新连接的 socket 添加到 epollevent.events = EPOLLIN;event.data.fd = new_socket;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event);} else {// 处理客户端请求int valread = read(events[i].data.fd, buffer, 1024);if (valread == 0) {// 客户端断开连接std::cout << "Client disconnected: " << events[i].data.fd << std::endl;close(events[i].data.fd);} else {buffer[valread] = '\0';std::cout << "Received from client " << events[i].data.fd << ": " << buffer << std::endl;send(events[i].data.fd, buffer, valread, 0); // 回送数据}}}}return 0;

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

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

相关文章

CTFHUB技能树之SQL——时间盲注

开启靶场&#xff0c;打开链接&#xff1a; 说明这关对所有信息都做了统一输出&#xff0c;换成延时注入试试 输入&#xff1a; 1 and sleep(15) &#xff08;这里不知道为什么加上--倒是会影响sleep()函数的触发&#xff0c;从而没有延时感&#xff09; 可以观察到有明显的延…

C++11新特性(4)

目录 1.包装器 2.线程库 2.1thread类的简单介绍 2.2线程函数参数 2.3原子性操作库(atomic) 2.4lock_guard与unique_lock 2.5mutex的种类 1. std::mutex 2. std::recursive_mutex 3. std::timed_mutex 4. std::recursive_timed_mutex 2.6lock_guard 2.7unique_lock 3.支持两个线…

【vue】指令补充+样式绑定+计算属性+侦听器

代码获取 知识总结 ⼀、指令补充 1.指令修饰符 1.1 什么是指令修饰符&#xff1f; 所谓指令修饰符就是让指令的 功能更强⼤&#xff0c;书写更便捷 1.2 分类 1.2.1 按键修饰符 keydown.enter&#xff1a;当enter键按下时触发 keyup.enter&#xff1a;当enter键抬起时触…

如何看待阿里通义千问团队发布Qwen2.5 MATH,效果怎么样,这是中国的草莓吗?

Qwen2.5-Math的发布标志着在数学问题解决领域的一个重要进展。这个由阿里通义千问团队发布的模型系列&#xff0c;通过结合Chain-of-Thought (CoT)和Tool-integrated Reasoning (TIR)技术&#xff0c;提升了对中英文数学问题的解决能力。Qwen2.5-Math系列包括基础模型和经过指令…

CentOS 7 yum失效的解决办法

文章目录 一、CentOS 7停止维护导致yum失效的解决办法解决方案 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、CentOS 7停止维护导致yum失效的解决办法 020 年&#xff0c;CentOS 项目与红帽联合宣布将全部投资转向 CentOS Stream&#xff0c;这是…

个人健康系统|个人健康数据管理系统|基于小程序+java的个人健康数据管理系统设计与实现(源码+数据库+文档)

个人健康数据管理系统 目录 基于小程序java的个人健康数据管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师…

C#学习笔记(一)

C#学习笔记&#xff08;一&#xff09; 简介第一章 上位机开发环境之 VS 使用和.NET 平台基础一、安装软件二、创建项目三、第一个Hello world四、解决方案与项目五、Debug 和 Release 的区别六、代码的生产过程七、CLR的其它功能 简介 C# .NET工控上位机开发 在工控领域&…

VMware虚拟机连不上网络,但VMware网络服务和网络适配器均正常

此教程适用于VMware虚拟机连不上网络&#xff0c;但检查VMware网络服务和网络适配器均正常的场景&#xff0c;遇到问题的为凝思Linux6.0.80系统&#xff0c;其他系统遇到同样问题应该也可以试试 问题描述&#xff1a; 使用凝思Linux系统&#xff0c;配置了两个网卡&#xff1…

web API基础

作用和分类 作用: 就是使用 JS 去操作 html 和浏览器 分类&#xff1a; DOM (文档对象模型)、 BOM &#xff08;浏览器对象模型&#xff09; 什么是DOM DOM (Document Object Model) 译为文档对象模型&#xff0c;是 HTML 和 XML 文档的编程接口。 HTML DOM 定义了访问和操作 …

2024 OSCAR|《开源体系建设路径模式洞察与建议》即将发布

近年来&#xff0c;开源体系建设受到高度重视&#xff0c;国家软件发展战略和“十四五”规划纲要均对开源作出重要部署&#xff0c;为我国开源体系建设和发展指明了方向。9月25日&#xff0c;工业和信息化部党组书记、部长金壮指出要加强开源体系建设&#xff0c;助推产业高质量…

数据结构——树和森林

目录 树的存储结构 1、双亲表示法 2、孩子链表 3、孩子兄弟表示法 树与二叉树的转换 将树转换为二叉树 将二叉树转换为树 森林与二叉树的转化 森林转换成二叉树 二叉树转换为森林 树和森林的遍历 1、 树的遍历&#xff08;三种方式&#xff09; 2、森林的遍历 树的存…

Zico 2 靶机 - 详细流程

✨ 准备工作 靶机 && kali 环境要求 机器名网络配置靶机Zico 2NAT 模式攻击机kaliNAT 模式 靶机下载链接&#xff1a;zico2: 1 ~ VulnHub 打开 VMware&#xff0c;将 zico2.ova 拖拽到 VMware 中 设置 虚拟机名称(A) - 存储路径(P)- 导入 若是&#xff0c;…

Android复杂问题分析工具bugreportz详解

文章目录 bugreportz详细介绍功能与作用使用方法生成详细报告检查进度bugreportz 的优势分析报告 如何分析1. 解压 ZIP 文件2. 分析主要文件2.1 bugreport.txt2.2 logcat.txt2.3 kernel.log / last_kmsg2.4 events.log2.5 traces.txt2.6 dumpstate_board.txt 3. 工具支持4. 重点…

《深度学习》OpenCV 光流估计 原理、案例解析

目录 一、光流估计 1、什么是光流估计 2、原理 3、光流估计算法 1&#xff09;基于局部方法 2&#xff09;和基于全局方法 4、光流估计的前提 1&#xff09;亮度恒定 2&#xff09;小运动 3&#xff09;空间一致 二、案例实现 1、读取视频 2、特征检测 3、处理每…

案例实践 | 以长安链为坚实底层,江海链助力南通民政打造慈善应用标杆

案例名称-江海链 ■ 实施单位 中国移动通信集团江苏有限公司南通分公司、中国移动通信集团江苏有限公司 ■ 业主单位 江苏省南通市民政局 ■ 上线时间 2023年12月 ■ 用户群体 南通市民政局、南通慈善总会等慈善组织及全市民众 ■ 用户规模 全市近30家慈善组织&#…

【RoadRunner】自动驾驶模拟3D场景构建 | 软件简介与视角控制

&#x1f4af; 欢迎光临清流君的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落 &#x1f4af; &#x1f525; 个人主页:【清流君】&#x1f525; &#x1f4da; 系列专栏: 运动控制 | 决策规划 | 机器人数值优化 &#x1f4da; &#x1f31f;始终保持好奇心&…

秋招突击——8/6——万得数据面试总结

文章目录 引言正文面经整理一1、讲一下java的多态&#xff0c;重载&#xff0c;重写的概念&#xff0c;区别2、说一下Java的数组&#xff0c;链表的结构&#xff0c;优缺点3、创建java线程的方式有哪些&#xff0c;具体说说4、创建线程池呢、每个参数的意义5、通过那几种方式保…

普通索引和唯一索引,应该怎么选择?

普通索引和唯一索引&#xff0c;应该怎么选择&#xff1f; 普通索引&#xff0c;不能保证字段的唯一性&#xff0c;所以普通索引会比唯一索引要多N次判断&#xff0c;比如判断下一条记录是否和目标相同。 InnoDB的数据其实是按页来取的&#xff0c;也就是说要拿到某一个数据&a…

AndroidStudio配置MQTT连接云平台EMQX

引言 本篇博客主要介绍mqtt和emqx配置连接实现数据收发&#xff0c;我会从基础的本机连接到手机和本机连接再到手机实现mqtt连接云平台&#xff0c;大家可以根据需要自行选择观看&#xff08;后面两个教程都建立在mqtt和emqx下载完成的基础上&#xff0c;若没有下载完成&#x…

黎巴嫩爆炸事件分析:硬件国产自主可控的意义

黎巴嫩近期发生的寻呼机爆炸事件&#xff0c;不仅对当地社会造成了冲击&#xff0c;也在全球范围内引发了对通信设备安全性的深刻反思。这一事件凸显了在全球化背景下&#xff0c;电子产品安全性的重要性&#xff0c;以及自主可控技术在保障国家安全和公共安全中的关键作用。 …