IO多路复用-poll的使用详解【C语言】

1.什么是poll

poll 是一种用于监控多个文件描述符状态的系统调用,它可以等待多个文件描述符上的事件发生。它与 selectepoll 类似,但在某些场景下使用更为方便。

poll的机制与select类似,与select在本质上没有多大差别,使用方法也类似,下面的是对于二者的对比:

  • 内核对应文件描述符的检测也是以线性的方式进行轮询,根据描述符的状态进行处理
  • poll和select检测的文件描述符集合会在检测过程中频繁的进行用户区和内核区的拷贝,它的开销随着文件描述符数量的增加而线性增大,从而效率也会越来越低。
  • select检测的文件描述符个数上限是1024,poll没有最大文件描述符数量的限制
  • select可以跨平台使用,poll只能在Linux平台使用

2.poll函数

2.1struct pollfd

struct pollfdpoll 函数中用来描述每个文件描述符及其关注事件的结构体。

struct pollfd {int fd;         // 文件描述符short events;   // 要监视的事件掩码short revents;  // 实际发生的事件掩码
};
fd:需要监视的文件描述符。
events:关注的事件掩码,可以是以下宏的组合:POLLIN:可读事件。POLLOUT:可写事件。POLLERR:发生错误事件。POLLHUP:对端关闭连接事件。POLLNVAL:非法请求事件。
revents:实际发生的事件掩码,由系统填充并返回给调用者。

2.2 poll 函数

功能

  • poll 函数用于等待多个文件描述符上的事件发生。
  • 它会阻塞进程,直到指定的文件描述符中至少有一个文件描述符上有事件发生,或者超过了指定的超时时间。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
fds:指向 struct pollfd 数组的指针,数组中每个元素描述一个文件描述符及其关注的事件。
nfds:fds 数组中元素的数量。这是第一个参数数组中最后一个有效元素的下标 + 1(也可以指定参数1数组的元素总个数)
timeout:超时时间,以毫秒为单位。传递 -1 表示无限阻塞,传递 0 表示立即返回,传递正整数表示超时时间。
返回值:
返回值为发生事件的文件描述符的数量,或者特定的返回码(如 -1 表示出错)。
如果超时时间到期而没有任何文件描述符发生事件,则返回 0。

注意事项和细节

  • poll 函数在等待期间会修改传入的 fds 数组中每个 pollfd 结构体的 revents 字段,用于指示实际发生的事件。
  • 如果 poll 返回值大于 0,可以遍历 fds 数组来确定具体哪些文件描述符发生了事件,以及发生了哪些事件。
  • 需要注意处理返回值为 -1 的情况,表示 poll 调用发生了错误。
  • 使用 poll 时应注意合理设置超时时间,避免无限阻塞造成程序无响应。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/poll.h>#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024
#define SERVER_PORT 8888int main() {int server_fd, client_fds[MAX_CLIENTS], max_clients = MAX_CLIENTS;struct sockaddr_in server_addr, client_addr;socklen_t client_len;struct pollfd fds[MAX_CLIENTS + 1]; // +1 用于服务器端套接字char buffer[BUFFER_SIZE];int num_clients = 0;// 创建服务器端套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("无法创建套接字");exit(EXIT_FAILURE);}// 初始化服务器地址结构memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(SERVER_PORT);// 将服务器套接字绑定到指定地址if (bind(server_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {perror("绑定失败");exit(EXIT_FAILURE);}// 监听传入连接请求if (listen(server_fd, 5) < 0) {perror("监听失败");exit(EXIT_FAILURE);}printf("回显服务器正在端口 %d 上监听...\n", SERVER_PORT);// 初始化 pollfd 结构体数组,用于服务器套接字和客户端套接字fds[0].fd = server_fd;fds[0].events = POLLIN; // 监听可读事件// 初始化客户端文件描述符数组for (int i = 1; i < MAX_CLIENTS + 1; ++i) {fds[i].fd = -1; // -1 表示未使用的槽位}while (1) {// 调用 poll 并阻塞等待事件发生int ready = poll(fds, num_clients + 1, -1);if (ready < 0) {perror("Poll 调用失败");exit(EXIT_FAILURE);}// 检查服务器套接字是否有事件发生if (fds[0].revents & POLLIN) {// 接受传入连接client_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr *) &client_addr, &client_len);if (client_fd < 0) {perror("接受连接失败");continue;}// 将新客户端添加到数组中if (num_clients < max_clients) {for (int i = 1; i < max_clients + 1; ++i) {if (fds[i].fd == -1) {fds[i].fd = client_fd;fds[i].events = POLLIN;client_fds[num_clients++] = client_fd;printf("客户端已连接:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));break;}}} else {printf("客户端过多,连接被拒绝。\n");close(client_fd);}}// 检查客户端是否有数据for (int i = 1; i < num_clients + 1; ++i) {if (fds[i].fd != -1 && (fds[i].revents & POLLIN)) {// 从客户端读取数据int bytes_received = recv(fds[i].fd, buffer, BUFFER_SIZE, 0);if (bytes_received <= 0) {// 客户端关闭连接或发生错误if (bytes_received == 0) {printf("客户端 %d 断开连接。\n", fds[i].fd);} else {perror("接收数据失败");}close(fds[i].fd);fds[i].fd = -1;memmove(&fds[i], &fds[i + 1], (num_clients - i) * sizeof(struct pollfd));num_clients--;} else {// 将数据原样回送给客户端send(fds[i].fd, buffer, bytes_received, 0);}}}}return 0;
}

从上面的测试代码可以得知,使用poll和select进行IO多路转接的处理思路是完全相同的,但是使用poll编写的代码看起来会更直观一些,select使用的位图的方式来标记要委托内核检测的文件描述符(每个比特位对应一个唯一的文件描述符),并且对这个fd_set类型的位图变量进行读写还需要借助一系列的宏函数,操作比较麻烦。而poll直接将要检测的文件描述符的相关信息封装到了一个结构体struct pollfd中,我们可以直接读写这个结构体变量。

另外poll的第二个参数有两种赋值方式,但是都和第一个参数的数组有关系:

  • 使用参数1数组的元素个数
  • 使用参数1数组中存储的最后一个有效元素对应的下标值 + 1

内核会根据第二个参数传递的值对参数1数组中的文件描述符进行线性遍历,这一点和select也是类似的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8888
#define BUFFER_SIZE 1024int main() {int client_fd;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];// 创建客户端套接字if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("无法创建套接字");exit(EXIT_FAILURE);}// 初始化服务器地址结构memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);// 将IP地址从文本转换为网络地址if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {perror("inet_pton 失败");exit(EXIT_FAILURE);}// 连接到服务器if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("连接服务器失败");exit(EXIT_FAILURE);}printf("连接到服务器 %s:%d 成功\n", SERVER_IP, SERVER_PORT);// 发送和接收数据while (1) {printf("请输入要发送的消息 (输入 \"quit\" 退出):");fgets(buffer, BUFFER_SIZE, stdin);// 发送数据if (send(client_fd, buffer, strlen(buffer), 0) < 0) {perror("发送数据失败");break;}// 如果输入 quit 则退出if (strncmp(buffer, "quit", 4) == 0)break;// 接收服务器的回复int bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);if (bytes_received < 0) {perror("接收数据失败");break;} else if (bytes_received == 0) {printf("服务器断开连接\n");break;} else {buffer[bytes_received] = '\0';printf("从服务器接收到的消息:%s\n", buffer);}}// 关闭套接字close(client_fd);return 0;
}

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

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

相关文章

xinput1-3.dll文件丢失找不到的修复方法

在电脑使用过程中&#xff0c;遇到“xinput1-3.dll丢失”或“找不到xinput1-3.dll”这类错误提示&#xff0c;可能会导致某些游戏或应用程序无法正常运行。以下是修复步骤&#xff0c;帮助您快速找回缺失的dll文件。 一、xinput1-3.dll的作用 xinput1-3.dll是Microsoft Direct…

Android APP 基于RecyclerView框架工程(知识体系积累)

说明&#xff1a;这个简单的基于RecyclerView的框架作用在于自己可以将平时积累的一些有效demo整合起来&#xff08;比如音视频编解码的、opengles的以及其他也去方向的、随着项目增多&#xff0c;工程量的增加&#xff0c;后期想高效的分析和查找并不容易&#xff09;&#xf…

1.Fabric框架

要了解Fabric&#xff0c;首先要知道Hyperledger开源项目。 2015年12月&#xff0c;由开源世界的旗舰组织Linux基金会牵头&#xff0c;30家初始企业成员共同宣布Hyperledger联合项目成立。Hyperledger 超级账本&#xff0c;是首个面向企业应用场景的分布式账本平台&#xff0c…

【每日一练】python编写一个简易计算器

程序代码: #循环语句&#xff0c;条件为真所以循环执行 while True: #定义两个数的变量和运算符号 num1 float(input("第一个数:")) num2 float(input("第一个数:")) syminput("选择运算符 - * /&#xff1a;") #判断运算符号 …

Camera Raw:评级和标签

在 Camera Raw 中&#xff0c;评级 Rating和标签 Label功能为摄影师和图像编辑者提供了一种高效的图像组织和管理方法。通过这些功能&#xff0c;用户可以轻松地对照片进行分类、标记和筛选&#xff0c;以便在大量图像中快速找到需要的照片。 ◆ ◆ ◆ 设置星级 Set Rating 星…

Inconsistent Query Results Based on Output Fields Selection in Milvus Dashboard

题意&#xff1a;在Milvus仪表盘中基于输出字段选择的不一致查询结果 问题背景&#xff1a; Im experiencing an issue with the Milvus dashboard where the search results change based on the selected output fields. Im working on a RAG project using text data conv…

【Java面向对象】二进制I/O

文章目录 1.二进制文件2.二进制 I/O 类2.1 FileInputStream 和 FileOutputStream2.2 FilterInputStream和 FilterOutputStream2.3 DatalnputStream 和 DataOutputStream2.4 BufferedInputStream 和 BufferedOutputStream2.5 ObjectInputStream 和 ObjectOutputStream 2.6 Seria…

防御保护课-防火墙接口配置实验

一、实验拓扑 &#xff08;我做实验用的图如下&#xff09; 二、实验要求 1.防火墙向下使用子接口分别对应生产区和办公区 2.所有分区设备可以ping通网关 三、实验思路 配IP&#xff1b; 划分vlan并配置vlan&#xff1b; 配置路由和安全策略。 四、实验配置 1、画图并…

开源模型应用落地-FastAPI-助力模型交互-进阶篇-RequestDataclasses(三)

一、前言 FastAPI 的高级用法可以为开发人员带来许多好处。它能帮助实现更复杂的路由逻辑和参数处理&#xff0c;使应用程序能够处理各种不同的请求场景&#xff0c;提高应用程序的灵活性和可扩展性。 在数据验证和转换方面&#xff0c;高级用法提供了更精细和准确的控制&#…

【Linux】进程间通信之-- 共享内存与信号量的介绍(下)

前言 上一篇&#xff0c;我们由进程间通信&#xff0c;引入并讲述了管道、匿名管道和命名管道&#xff0c;本节&#xff0c;将继续学习进程间通信的另一种方式之&#xff0c;共享内存。还要学习几个系统调用接口&#xff0c;并演示两个进程通过共享内存来进行通信。。。 目录 1…

工业控制:CANOpen(控制器局域网络)协议快速学习

文章目录 背景协议介绍CAN总线协议CANOpen协议介绍CANOpen诞生背景CANOpen的对象字典 CANOpen的服务数据对象&#xff08;SDO&#xff09; 参考附录问题CAN总线竞争原理在CAN协议中&#xff0c;帧中的ID是发送者的ID还是接收者的ID&#xff1f; 背景 目前很多CANOpen介绍的文章…

循环机制(event loop)之宏任务和微任务

一、前言 js任务分为同步任务和异步任务&#xff0c;异步任务又分为宏任务和微任务&#xff0c;其中异步任务属于耗时的任务。 二、宏任务和微任务有哪些&#xff1f; 宏任务&#xff1a;整体代码script、setTimeout、setInterval、setImmediate&#xff08;Node.js&#xff…

【ARM】SMMU系统虚拟化整理

目录 1.MMU的基本介绍 1.1 特点梳理 2.功能 DVM interface PTW interface 2.1 操作流程 2.1.1 StreamID 2.1.2 安全状态&#xff1a; 2.1.3 HUM 2.1.4 可配置的操作特性 Outstanding transactions per TBU QoS 仲裁 2.2 Cache结构 2.2.1 Micro TLB 2.2.2 Macro…

第四周:机器学习笔记

第四周学习周报 摘要Abstract机器学习任务攻略1.loss on training data1.1 training data的loss过大怎么办&#xff1f;1.2 training data的loss小&#xff0c;但是testing data loss大怎么办&#xff1f; 2. 如何选择一个中最好的模型&#xff1f;2.1 Cross Validation&#x…

知名在线市场 Etsy 允许在其平台上销售 AI 艺术品,但有条件限制|TodayAI

近日&#xff0c;以手工和复古商品著称的在线市场 Etsy 宣布&#xff0c;将允许在其平台上销售 AI 生成的艺术品。这一举措引发了广泛关注和争议。尽管 Etsy 正在接受 AI 艺术的潮流&#xff0c;但平台对这一类商品的销售设置了一些限制。 根据 Etsy 新发布的政策&#xff0c;…

mysql存储引擎和备份

索引 事务 存储引擎 概念&#xff1a;存储引擎&#xff0c;就是一种数据库存储数据的机制&#xff0c;索引的技巧&#xff0c;锁定水平。 存储引擎。存储的方式和存储的格式。 存储引擎也属于mysql当中的组件&#xff0c;实际上操作的&#xff0c;执行的就是数据的读写I/O。…

华为OD机试2024年C卷D卷 - 构成指定长度字符串的个数/字符串拼接(Java)

华为OD机试&#xff08;C卷D卷&#xff09;2024真题目录 题目描述&#xff1a;构成指定长度字符串的个数 (本题分值200) 给定 M&#xff08;0 < M ≤ 30&#xff09;个字符&#xff08;a-z&#xff09;&#xff0c;从中取出任意字符&#xff08;每个字符只能用一次&#x…

科普文:银行信贷系统概叙

信贷业务流程 资金需求者提交申请&#xff1a;资金需求者通过不同渠道&#xff08;如APP、网站、门店等&#xff09;提交贷款申请。 系统交互完成审批&#xff1a;系统通过自动化和人工相结合的方式&#xff0c;对贷款申请进行初步筛选和审批。 系统交互完成策略判断&#xf…

rsync文件远程同步

目录 一、什么是rsync远程同步 二、实操rsync远程文件同步 1、配置rsync同步源 2、客户端部署 3、增量备份​编辑 4、删除文件 5、如何实现免交互登录 6、crontab rsync 实现定时同步 7、使用ssh实现rsync数据同步【☆】 如何使用ssh免交互实现数据同步&#xff1f;…

Golang | Leetcode Golang题解之第260题只出现一次的数字III

题目&#xff1a; 题解&#xff1a; func singleNumber(nums []int) []int {xorSum : 0for _, num : range nums {xorSum ^ num}lsb : xorSum & -xorSumtype1, type2 : 0, 0for _, num : range nums {if num&lsb > 0 {type1 ^ num} else {type2 ^ num}}return []in…