【Linux高级 I/O(3)】如何使用阻塞 I/O 与非阻塞 I/O?——poll()函数

news/2025/3/31 8:45:02/

poll()函数介绍

        系统调用 poll()与 select()函数很相似,但函数接口有所不同。在 select()函数中,我们提供三个 fd_set 集合,在每个集合中添加我们关心的文件描述符;而在 poll()函数中,则需要构造一个 struct pollfd 类型的数组,每个数组元素指定一个文件描述符以及我们对该文件描述符所关心的条件(数据可读、可写或异常情况)。poll()函数原型如下所示:

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);

        函数参数含义如下:

        fds:指向一个 struct pollfd 类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件,稍后介绍 struct pollfd 结构体类型。

        nfds:参数 nfds 指定了 fds 数组中的元素个数,数据类型 nfds_t 实际为无符号整形。

        timeout:该参数与 select()函数的 timeout 参数相似,用于决定 poll()函数的阻塞行为,具体用法如下:

  •  如果 timeout 等于-1,则 poll()会一直阻塞(与 select()函数的 timeout 等于 NULL 相同),直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回。
  • 如果 timeout 等于 0,poll()不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态。
  • 如果 timeout 大于 0,则表示设置 poll()函数阻塞时间的上限值,意味着 poll()函数最多阻塞 timeout 毫秒,直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止。

        struct pollfd

        结构体 struct pollfd 结构体如下所示:

struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};

        fd 是一个文件描述符,struct pollfd 结构体中的 events 和 revents 都是位掩码,调用者初始化 events 来指定需要为文件描述符 fd 做检查的事件。当 poll()函数返回时,revents 变量由 poll()函数内部进行设置,用于说明文件描述符 fd 发生了哪些事件(注意,poll()没有更改 events 变量),我们可以对 revents 进行检查,判断文件描述符 fd 发生了什么事件。

        应将每个数组元素的 events 成员设置为下表中所示的一个或几个标志,多个标志通过位或运算符 ( | )组合起来,通过这些值告诉内核我们关心的是该文件描述符的哪些事件。同样,返回时,revents 变量由内核设置为表中所示的一个或几个标志。

         表中第一组标志(POLLIN、POLLRDNORM、POLLRDBAND、POLLPRI、POLLRDHUP)与数据可读相关;第二组标志(POLLOUT、POLLWRNORM、POLLWRBAND)与可写数据相关;而第三组标志(POLLERR、POLLHUP、POLLNVAL)是设定在 revents 变量中用来返回有关文件描述符的附加信息, 如果在 events 变量中指定了这三个标志,则会被忽略。

        如果我们对某个文件描述符上的事件不感兴趣,则可将 events 变量设置为 0;另外,将 fd 变量设置为文件描述符的负值(取文件描述符 fd 的相反数-fd),将导致对应的 events 变量被 poll()忽略,并且 revents 变量将总是返回 0,这两种方法都可用来关闭对某个文件描述符的检查。

        在实际应用编程中,一般用的最多的还是 POLLIN 和 POLLOUT。对于其它标志这里不再进行介绍了。

        poll()函数返回值

        poll()函数返回值含义与 select()函数的返回值是一样的,有如下几种情况:

  • 返回-1 表示有错误发生,并且会设置 errno。
  • 返回 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了。
  • 返回一个正整数表示有一个或多个文件描述符处于就绪态了,返回值表示 fds 数组中返回的 revents 变量不为 0 的 struct pollfd 对象的数量。

        下面代码演示了使用 poll()函数来实现 I/O 多路复用操作,同时读取键盘和鼠标。其实就是将上次select()代码进行了修改,使用 poll 替换 select。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>#define MOUSE "/dev/input/event3"int main(void){char buf[100];int fd, ret = 0, flag;int loops = 5;struct pollfd fds[2];/* 打开鼠标设备文件 */fd = open(MOUSE, O_RDONLY | O_NONBLOCK);if (-1 == fd) {perror("open error");exit(-1);}/* 将键盘设置为非阻塞方式 */flag = fcntl(0, F_GETFL); //先获取原来的 flagflag |= O_NONBLOCK; //将 O_NONBLOCK 标准添加到 flagfcntl(0, F_SETFL, flag); //重新设置 flag/* 同时读取键盘和鼠标 */fds[0].fd=0;fds[0].events = POLLIN;fds[0].revents = 0;fds[1].fd = fd;fds[1].events = POLLIN;fds[1].revents = 0;while (loops--) {ret = poll(fds, 2, -1);if (0 > ret) {perror("poll error");goto out;}else if (0 == ret) {fprintf(stderr, "poll timeout.\n");continue;}/* 检查键盘是否为就绪态 */if(fds[0].revents&POLLIN){ret = read(0, buf, sizeof(buf));if (0 < ret)printf("键盘: 成功读取<%d>个字节数据\n", ret);}/* 检查鼠标是否为就绪态 */if(fds[1].revents&POLLIN) {ret = read(fd, buf, sizeof(buf));if (0 < ret)printf("鼠标: 成功读取<%d>个字节数据\n", ret);}}
out:/* 关闭文件 */close(fd);exit(ret);
}

        struct pollfd 结构体的 events 变量和 revents 变量都是位掩码,所以可以使用"revents & POLLIN"按位与的方式来检查是否发生了相应的 POLLIN 事件,判断鼠标或键盘数据是否可读。测试结果:

总结

        在使用 select()或 poll()时需要注意一个问题,当监测到某一个或多个文件描述符成为就绪态(可以读或写)时,需要执行相应的 I/O 操作,以清除该状态,否则该状态将会一直存在;调用 select()函数监测鼠标和键盘这两个文件描述符,当 select()返回时,通过 FD_ISSET()宏判断文件描述符上 是否可执行 I/O 操作;如果可以执行 I/O 操作时,应在应用程序中对该文件描述符执行 I/O 操作,以清除文件描述符的就绪态,如果不清除就绪态,那么该状态将会一直存在,那么下一次调用 select()时,文件描述符已经处于就绪态了,将直接返回。

        同理对于 poll()函数来说亦是如此,当 poll()成功返回时,检查文件描述符是否称为就绪态,如果文件描述符上可执行 I/O 操作时,也需要对文件描述符执行 I/O 操作,以清除就绪状态。


http://www.ppmy.cn/news/77900.html

相关文章

2023全球最佳医院榜单及简要介绍

作为医学类的访问学者、博士后及联合培养博士们&#xff0c;都希望到世界知名医院进行临床研修交流及科研学习。2023 年世界最佳医院排行榜的发布为申请者提供了目标平台&#xff0c;现知识人网小编整理刊出。 近期&#xff0c;《新闻周刊》和全球数据公司 Statista 推出了2023…

ES6中class继承

1.简介 说明&#xff1a;class可以通过extends关键字实现继承&#xff0c;让子类继承父亲的属性和方法 class Fun {constructor(x, y) {this.x xthis.y y}talk() {console.log("talk方法");}tell() {console.log("tell方法");}}class Fun1 extends Fun …

数据分析真的很火吗?真的有很多企业需要这样的岗位吗?求大佬指点。

“我是去年毕业的&#xff0c;因为疫情影响&#xff0c;整个就业环境都很不好&#xff0c;很多企业都裁员了。加上疫情三年基本都是玩过去&#xff0c;也没啥一技之长&#xff0c;就业就更难了。听说现在做数据分析的人很多&#xff0c;我身边的朋友都在转行做数据分析。 其实…

为什么要放弃 $ 语法糖提案

《最新&#xff0c;Vue 中的响应性语法糖已废弃》 本文标题中的 $ 语法糖指的就是上文中的响应式语法糖 (Reactivity Transform)&#xff0c;那为什么不写 Reactivity Transform 呢&#xff1f;因为这个名实在是太长了… 大家觉得被废弃是因为分不清是正常变量还是响应式变量…

C++ STL篇

C STL篇 1 STL1.1 STL的诞生1.2 STL基本概念1.3 STL六大部件1.4 容器概念1.5 算法概念1.6 迭代器概念 2 初识容器算法迭代器2.1 vector存放内置数据类型2.2 vector存放自定义数据类型2.3 Vector容器嵌套 3 string容器3.1 string构造函数3.2 string赋值操作3.3 string字符串拼接…

Linux网络基础-4

在之前的网络基础博客中&#xff0c;我们对网络进行了概要解释&#xff0c;了解了应用层和传输层的知名协议。接下来我们来对网络层的典型协议进行解析。 目录 1.网络层协议 2.IP协议 2.1协议格式 2.2地址管理 2.3特殊网络 2.3.1私网的组建 2.3.2特殊IP地址 2.4路由选…

香港服务器如何操作域名解析让网站上线?

​  网站上线是一个需要多个步骤的过程&#xff0c;其中之一就是解析IP。在这个过程中&#xff0c;您需要将您的网站域名解析到香港服务器IP地址&#xff0c;以便访问者可以通过域名来访问您的网站。 下面是解析IP的一般步骤&#xff1a; 1. 获取服务器IP地址&#xff1a;首先…

SpringBoot 3.1现已推出,惊艳新特性带来前所未有的开发体验

一、介绍 1.1 新特性概述 经过半年的沉淀 Spring Boot 3.1于2023年5月18日正式发布了&#xff0c;带来了许多令人兴奋的新特性和改进。本篇博客将详细介绍Spring Boot 3.1的新特性、升级说明以及核心功能的改进。 同时&#xff0c;2.6.x 版本线已经停止维护了&#xff0c;最新…