Linux 中的poll、select和epoll有什么区别?
poll 和 select 是Linux 系统中用于多路复用 I/O 的系统调用,它们允许一个程序同时监视多个文件描述符,以便在任何一个文件描述符准备好进行 I/O 操作时得到通知。
一、select
select 是一种较早的 I/O 多路复用机制,具有以下特点:
-
接口:
-
select使用三个文件描述符集合(读、写、异常)来监视文件描述符的状态。 -
函数签名为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); -
nfds是需要监视的文件描述符的数量(通常是最大文件描述符加一)。 -
fd_set是一个位图结构,用于表示文件描述符集合。 -
timeout指定等待事件发生的超时时间。
-
-
限制:
select的文件描述符数量受到 FD_SETSIZE 的限制,通常为 1024。这意味着它不适合处理非常大量的并发连接。
-
效率:
- 每次调用
select都需要重新初始化文件描述符集合,因此在处理大量文件描述符时效率较低。
- 每次调用
-
可移植性:
select是 POSIX 标准的一部分,因此在许多操作系统上都可用。
select 示例:同时监视多个文件描述符的 I/O 事件
这个例子展示了如何使用 select 来监视两个文件描述符:标准输入(通常是终端输入)和一个网络套接字。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd, newsockfd;struct sockaddr_in servaddr, cliaddr;socklen_t clilen;char buffer[BUFFER_SIZE];fd_set readfds;int maxfd;// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 初始化服务器地址结构memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 监听if (listen(sockfd, 5) < 0) {perror("listen failed");close(sockfd);exit(EXIT_FAILURE);}// 接受一个连接clilen = sizeof(cliaddr);newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if (newsockfd < 0) {perror("accept failed");close(sockfd);exit(EXIT_FAILURE);}while (1) {// 清空文件描述符集合FD_ZERO(&readfds);// 将标准输入和新套接字添加到集合中FD_SET(STDIN_FILENO, &readfds);FD_SET(newsockfd, &readfds);maxfd = (STDIN_FILENO > newsockfd) ? STDIN_FILENO : newsockfd;// 使用 select 监视if (select(maxfd + 1, &readfds, NULL, NULL, NULL) < 0) {perror("select error");break;}// 检查标准输入是否有数据if (FD_ISSET(STDIN_FILENO, &readfds)) {if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {printf("Input from stdin: %s", buffer);}}// 检查套接字是否有数据if (FD_ISSET(newsockfd, &readfds)) {int n = read(newsockfd, buffer, BUFFER_SIZE);if (n <= 0) {printf("Client disconnected\n");break;}buffer[n] = '\0';printf("Received from client: %s", buffer);}}close(newsockfd);close(sockfd);return 0;
}
二、poll
poll 是 select 的改进版本,提供了一些更灵活的特性:
-
接口:
-
poll使用一个结构体数组来监视文件描述符的状态。 -
函数签名为:
int poll(struct pollfd *fds, nfds_t nfds, int timeout); -
pollfd结构体包含文件描述符及其感兴趣的事件。 -
timeout以毫秒为单位,指定等待事件发生的超时时间。
-
-
无数量限制:
poll不受 FD_SETSIZE 限制,因此可以监视的文件描述符数量仅受系统资源的限制。
-
效率:
- 由于
poll使用数组而不是位图,每次调用不需要重新初始化整个集合,但仍然需要扫描整个数组,因此在处理非常大量文件描述符时效率也受到限制。
- 由于
-
事件类型:
poll可以监视更多类型的事件,如挂起和优先级数据。
poll 示例:监视标准输入和一个网络套接字
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd, newsockfd;struct sockaddr_in servaddr, cliaddr;socklen_t clilen;char buffer[BUFFER_SIZE];struct pollfd fds[2];// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 初始化服务器地址结构memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 监听if (listen(sockfd, 5) < 0) {perror("listen failed");close(sockfd);exit(EXIT_FAILURE);}// 接受一个连接clilen = sizeof(cliaddr);newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if (newsockfd < 0) {perror("accept failed");close(sockfd);exit(EXIT_FAILURE);}// 设置 poll 文件描述符fds[0].fd = STDIN_FILENO;fds[0].events = POLLIN;fds[1].fd = newsockfd;fds[1].events = POLLIN;while (1) {// 使用 poll 监视int ret = poll(fds, 2, -1);if (ret < 0) {perror("poll error");break;}// 检查标准输入是否有数据if (fds[0].revents & POLLIN) {if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {printf("Input from stdin: %s", buffer);}}// 检查套接字是否有数据if (fds[1].revents & POLLIN) {int n = read(newsockfd, buffer, BUFFER_SIZE);if (n <= 0) {printf("Client disconnected\n");break;}buffer[n] = '\0';printf("Received from client: %s", buffer);}}close(newsockfd);close(sockfd);return 0;
}
三、epoll
epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,用于处理大量文件描述符的事件。它是 poll 和 select 系统调用的改进版本,专为解决在处理大量并发连接时的性能问题而设计
epoll 的主要特点包括:
- 高效性:与
select和poll不同,epoll不需要在每次调用时重新传递所有的文件描述符集。它通过一个文件描述符来管理感兴趣的事件,减少了内核和用户空间之间的数据拷贝。 - 水平触发和边缘触发:
epoll支持两种触发模式。水平触发(Level-Triggered)模式类似于select和poll的工作方式,而边缘触发(Edge-Triggered)模式则更加高效,但也更复杂,需要更仔细的事件处理。 - 支持大规模连接:
epoll能够更好地处理大规模并发连接,因此非常适合用在高性能服务器应用中,如网络服务器或代理服务器。 - 事件注册和监听分离:通过
epoll_ctl可以动态地添加、修改或删除监听的文件描述符,而epoll_wait用于等待事件的发生。
使用 epoll 通常涉及三个主要的系统调用:
epoll_create:创建一个epoll实例。epoll_ctl:注册、修改或删除感兴趣的事件。epoll_wait:等待事件的发生并获取事件列表。
epoll 是 Linux 特有的 I/O 多路复用机制,专为处理大量并发连接而设计。与 select 和 poll 相比,epoll 更高效,因为它在内核中维护一个事件表,避免了每次调用都需要重新传递完整的文件描述符集合。
应用示例:监视标准输入和一个网络套接字。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10int main() {int sockfd, newsockfd, epollfd;struct sockaddr_in servaddr, cliaddr;socklen_t clilen;char buffer[BUFFER_SIZE];struct epoll_event ev, events[MAX_EVENTS];int nfds;// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 初始化服务器地址结构memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 监听if (listen(sockfd, 5) < 0) {perror("listen failed");close(sockfd);exit(EXIT_FAILURE);}// 创建 epoll 实例epollfd = epoll_create1(0);if (epollfd == -1) {perror("epoll_create1 failed");close(sockfd);exit(EXIT_FAILURE);}// 将监听套接字添加到 epoll 实例中ev.events = EPOLLIN;ev.data.fd = sockfd;if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) {perror("epoll_ctl: sockfd");close(sockfd);close(epollfd);exit(EXIT_FAILURE);}// 将标准输入添加到 epoll 实例中ev.events = EPOLLIN;ev.data.fd = STDIN_FILENO;if (epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {perror("epoll_ctl: stdin");close(sockfd);close(epollfd);exit(EXIT_FAILURE);}while (1) {// 等待事件发生nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait failed");break;}for (int n = 0; n < nfds; ++n) {if (events[n].data.fd == sockfd) {// 处理新连接clilen = sizeof(cliaddr);newsockfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if (newsockfd == -1) {perror("accept failed");continue;}// 将新连接添加到 epoll 实例中ev.events = EPOLLIN;ev.data.fd = newsockfd;if (epoll_ctl(epollfd, EPOLL_CTL_ADD, newsockfd, &ev) == -1) {perror("epoll_ctl: newsockfd");close(newsockfd);continue;}} else if (events[n].data.fd == STDIN_FILENO) {// 处理标准输入if (fgets(buffer, BUFFER_SIZE, stdin) != NULL) {printf("Input from stdin: %s", buffer);}} else {// 处理来自客户端的数据int n = read(events[n].data.fd, buffer, BUFFER_SIZE);if (n <= 0) {if (n == 0) {printf("Client disconnected\n");} else {perror("read error");}close(events[n].data.fd);} else {buffer[n] = '\0';printf("Received from client: %s", buffer);}}}}close(sockfd);close(epollfd);return 0;
}
解释
- 创建套接字:首先创建一个 TCP 套接字并绑定到指定端口,然后开始监听连接。
- 创建
epoll实例:使用epoll_create1创建一个新的epoll实例。 - 注册文件描述符:将监听套接字和标准输入文件描述符注册到
epoll实例中,以便监视它们的可读事件。 - 事件循环:使用
epoll_wait等待事件发生。当有事件发生时,检查是哪种事件并进行相应处理:- 如果是监听套接字有事件,表示有新连接到达,使用
accept接受连接并将新套接字注册到epoll实例中。 - 如果是标准输入有事件,读取输入并处理。
- 如果是客户端套接字有事件,读取数据并处理。
- 如果是监听套接字有事件,表示有新连接到达,使用
- 清理:在程序结束时关闭所有打开的文件描述符。
四、总结
poll和select机制
- 用户空间到内核空间的拷贝:每次调用
select或poll时,用户必须将所有需要监视的文件描述符列表传递给内核。这意味着每次调用都需要将这些数据从用户空间复制到内核空间,这在监视大量文件描述符时效率较低。 - 线性扫描:内核需要扫描所有文件描述符以确定哪些文件描述符有事件发生。这种线性扫描在文件描述符数量很大时效率不高。
epoll机制
- 内核维护事件表:
epoll通过epoll_create创建一个epoll实例,这个实例在内核中维护一个事件表。这个表记录了所有已经注册的文件描述符及其感兴趣的事件类型。 - 增量更新:通过
epoll_ctl,用户可以增量地添加、修改或删除文件描述符及其感兴趣的事件。这意味着用户只需要在文件描述符集合发生变化时进行更新,而不是每次等待事件时都传递整个集合。 - 事件驱动:
epoll_wait返回的不是所有文件描述符的状态,而是已经准备好进行 I/O 操作的文件描述符列表。这使得epoll在处理大量文件描述符时更加高效,因为它只返回有事件的文件描述符。
在 select/poll 中,每次都需要将完整的文件描述符集合传递给内核,而 epoll 通过维护一个事件表,只在有事件发生时通知用户空间,从而提高了效率
相关文章:
Linux 中的poll、select和epoll有什么区别?
poll 和 select 是Linux 系统中用于多路复用 I/O 的系统调用,它们允许一个程序同时监视多个文件描述符,以便在任何一个文件描述符准备好进行 I/O 操作时得到通知。 一、select select 是一种较早的 I/O 多路复用机制,具有以下特点ÿ…...
单片机-STM32 WIFI模块--ESP8266 (十二)
1.WIFI模块--ESP8266 名字由来: Wi-Fi这个术语被人们普遍误以为是指无线保真(Wireless Fidelity),并且即便是Wi-Fi联盟本身也经常在新闻稿和文件中使用“Wireless Fidelity”这个词,Wi-Fi还出现在ITAA的一个论文中。…...
linux日志排查相关命令
实时查看日志 tail -f -n 100 文件名 -f:实时查看 -n:查看多少行 直接查看日志文件 .log文件 cat 文件名 .gz文件 zgcat 文件名 在日志文件搜索指定内容 .log文件 grep -A 3 “呀1” 文件名 -A:向后查看 3:向后查看行数 “呀1”:搜…...
每日一题-二叉搜索树与双向链表
将二叉搜索树转化为排序双向链表 问题描述 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表,要求空间复杂度为 O(1),时间复杂度为 O(n),并且不能创建新的结点,只能调整树中结点的指针指向。 数据范围 …...
【多视图学习】Self-Weighted Contrastive Fusion for Deep Multi-View Clustering
Self-Weighted Contrastive Fusion for Deep Multi-View Clustering 用于深度多视图聚类的自加权对比融合 TMM 2024 代码链接 论文链接 0.摘要 多视图聚类可以从多个视图中探索共识信息,在过去二十年中越来越受到关注。然而,现有的工作面临两个主要挑…...
ASK-HAR:多尺度特征提取的深度学习模型
一、探索多尺度特征提取方法 在近年来,随着智能家居智能系统和传感技术的快速发展,人类活动识别(HAR)技术已经成为一个备受瞩目的研究领域。HAR技术的核心在于通过各种跟踪设备和测量手段,如传感器和摄像头࿰…...
C语言:数据的存储
本文重点: 1. 数据类型详细介绍 2. 整形在内存中的存储:原码、反码、补码 3. 大小端字节序介绍及判断 4. 浮点型在内存中的存储解析 数据类型结构的介绍: 类型的基本归类: 整型家族 浮点家族 构造类型: 指针类型&…...
深入理解动态规划(dp)--(提前要对dfs有了解)
前言:对于动态规划:该算法思维是在dfs基础上演化发展来的,所以我不想讲的是看到一个题怎样直接用动态规划来解决,而是说先用dfs搜索,一步步优化,这个过程叫做动态规划。(该文章教你怎样一步步的…...
单片机基础模块学习——数码管(二)
一、数码管模块代码 这部分包括将数码管想要显示的字符转换成对应段码的函数,另外还包括数码管显示函数 值得注意的是对于小数点和不显示部分的处理方式 由于小数点没有单独占一位,所以这里用到了两个变量i,j用于跳过小数点导致的占据其他字符显示在数…...
【大数据】机器学习----------强化学习机器学习阶段尾声
一、强化学习的基本概念 注: 圈图与折线图引用知乎博主斜杠青年 1. 任务与奖赏 任务:强化学习的目标是让智能体(agent)在一个环境(environment)中采取一系列行动(actions)以完成一个…...
flink写parquet解决timestamp时间格式字段问题
背景 Apache Parquet 是一种开源的列式数据文件格式,旨在实现高效的数据存储和检索。它提供高性能压缩和编码方案(encoding schemes)来批量处理复杂数据,并且受到许多编程语言和分析工具的支持。 在我们通过flink写入parquet文件的时候,会遇到timestamp时间格式写入的问题。…...
redis实现lamp架构缓存
redis服务器环境下mysql实现lamp架构缓存 ip角色环境192.168.242.49缓存服务器Redis2.2.7192.168.242.50mysql服务器mysql192.168.242.51web端php ***默认已安装好redis,mysql 三台服务器时间同步(非常重要) # 下载ntpdate yum -y install…...
正则表达式中常见的贪婪词
1. * 含义:匹配前面的元素零次或者多次。示例:对于正则表达式 a*,在字符串 "aaaa" 中,它会匹配整个 "aaaa",因为它会尽可能多地匹配 a 字符。代码示例(Python):…...
CF 339A.Helpful Maths(Java实现)
题目分析 输入一串式子,输出从小到大排列的式子 思路分析 如上所说核心思路,但是我要使用笨方法,输入一串式子用split分割开,但是此时需要用到转义字符,即函数内参数不能直接使用“”,而是“\\”。分割开后…...
SQL 指南
SQL 指南 引言 SQL(Structured Query Language,结构化查询语言)是一种用于管理关系数据库系统的标准计算机语言。自1970年代问世以来,SQL已经成为了数据库管理和数据操作的事实标准。本文旨在为初学者和有经验的数据库用户提供一个全面的SQL指南,涵盖SQL的基础知识、高级…...
DDD架构实战第七讲总结:分层模型和代码组织
云架构师系列课程之DDD架构实战第七讲总结:分层模型和代码组织 一、引言 在前几讲中,我们介绍了领域驱动设计(DDD)的基本构造块和生命周期模型中的聚合。本讲将重点讨论如何将这些构造块和代码组织起来,探讨分层架构和六边形模型,以及如何组织代码结构。 二、工厂和资…...
Python “字典” 实战案例:5个项目开发实例
Python “字典” 实战案例:5个项目开发实例 内容摘要 本文包括 5 个使用 Python 字典的综合应用实例。具体是: 电影推荐系统配置文件解析器选票统计与排序电话黄页管理系统缓存系统(LRU 缓存) 以上每一个实例均有完整的程序代…...
(一)QT的简介与环境配置WIN11
目录 一、QT的概述 二、QT的下载 三、简单编程 常用快捷键 一、QT的概述 简介 Qt(发音:[kjuːt],类似“cute”)是一个跨平台的开发库,主要用于开发图形用户界面(GUI)应用程序,…...
在 Windows 系统上,将 Ubuntu 从 C 盘 迁移到 D 盘
在 Windows 系统上,如果你使用的是 WSL(Windows Subsystem for Linux)并安装了 Ubuntu,你可以将 Ubuntu 从 C 盘 迁移到 D 盘。迁移过程涉及导出当前的 Ubuntu 发行版,然后将其导入到 D 盘的目标目录。以下是详细的步骤…...
vue2的$el.querySelector在vue3中怎么写
这个也属于直接操作 dom 了,不建议在项目中这样操作,不过我是在vue2升级vue3的时候遇到的,是以前同事写的代码,也没办法 先来看一下对比 在vue2中获取实例是直接通过 this.$refs.xxx 获取绑定属性 refxxx 的实例,并且…...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
