阅读导航
- 引言
- 一、poll简介
- 二、poll函数接口
- ⭕参数说明
- 三、pollfd结构体
- ⭕events和revents的取值
- 四、返回值
- 五、工作原理
- 六、优缺点
- ✅优点
- ✅缺点
- 七、 使用示例
- 🚨注意事项
- 总结
- 温馨提示
引言
在上一篇探讨了I/O多路转接之select
方法的基础上,本文将深入解析另一种强大的I/O多路转接技术——poll
。I/O多路转接作为高效处理多个输入输出流的关键技术,广泛应用于网络编程和文件操作中,旨在提升程序处理并发I/O事件的能力。通过poll
,我们可以进一步优化I/O性能,实现更加灵活和高效的资源调度。那么,poll
究竟如何工作?它相比select
又有哪些优势与差异?让我们一同揭开poll
的神秘面纱。
一、poll简介
poll函数是Linux系统下提供的一种I/O多路复用机制,它允许程序同时监视多个文件描述符(如socket描述符)的状态变化,以便在单个线程中高效地处理多个并发连接。
二、poll函数接口
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
⭕参数说明
- fds:一个指向
struct pollfd
结构数组的指针,每个元素代表一个需要监视的文件描述符及其事件。 - nfds:
fds
数组中元素的数量,即需要监视的文件描述符的数量。 - timeout:指定poll函数等待事件发生前的超时时间(毫秒)。如果
timeout
为-1,poll将无限期等待;如果为0,poll将立即返回,不阻塞;如果为正数,poll将等待指定毫秒数。
三、pollfd结构体
pollfd
结构体用于指定需要监视的文件描述符及其事件,其定义如下:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件,通过位或运算组合多个事件 */
short revents; /* 实际发生的事件,由内核设置 */
};
- fd:需要监视的文件描述符。
- events:用户感兴趣的事件集合,通过位或运算组合多个事件,如
POLLIN
(可读)、POLLOUT
(可写)等。 - revents:内核在调用返回时设置的实际发生的事件集合。
⭕events和revents的取值
在poll
函数中,events
和revents
字段都是位掩码(bitmask),它们用于指定和返回文件描述符上发生的事件。下表是一些常见的events
和revents
的取值:
事件 | 描述 | 是否可作为输入 | 是否可作为输出 |
---|---|---|---|
POLLIN | 数据(包括普通数据和优先数据)可读 | 是 | 是 |
POLLRDNORM | 普通数据可读 | 是 | 是 |
POLLRDBAND | 优先级带数据可读(Linux不支持) | 是 | 是 |
POLLPRI | 高优先级数据可读,比如TCP带外数据 | 是 | 是 |
POLLOUT | 数据(包括普通数据和优先数据)可写 | 是 | 是 |
POLLWRNORM | 普通数据可写 | 是 | 是 |
POLLWRBAND | 优先级带数据可写 | 是 | 是 |
POLLRDHUP | TCP连接被对方关闭,或者对方关闭了写操作。它由GNU引入 | 是 | 是 |
POLLERR | 错误 | 否 | 是 |
POLLHUP | 挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件 | 否 | 是 |
POLLNVAL | 文件描述符没有打开 | 否 | 是 |
🚨🚨注意:revents
字段可能在poll
函数返回时被设置为多个事件的组合,这表示同时发生了多个事件。例如,如果revents
被设置为POLLIN | POLLERR
,那么表示同时发生了可读事件和错误事件。
四、返回值
- 成功:返回
fds
数组中revents
域不为0的文件描述符个数,即准备好读、写或出错状态的文件描述符数量。 - 超时:如果在指定的
timeout
时间内没有事件发生,返回0。 - 失败:返回-1,并设置全局变量
errno
以指示错误原因,如EBADF
(无效的文件描述符)、EFAULT
(fds
指针指向的地址超出进程地址空间)、EINTR
(调用被信号中断)等。
五、工作原理
poll函数通过轮询的方式检测fds
数组中指定的文件描述符的状态。在调用poll函数后,程序会阻塞等待,直到以下情况之一发生:
- 指定的文件描述符上有事件发生(如可读、可写)。
- 等待时间超时(如果指定了超时时间)。
- 调用被信号中断。
当poll函数返回时,程序可以通过检查fds
数组中每个元素的revents
域来确定哪些文件描述符上发生了事件,并据此进行相应的处理。
六、优缺点
✅优点
- 无文件描述符数量限制:与select相比,poll没有最大文件描述符数量的限制(但实际上,当文件描述符数量非常大时,性能会下降)。
- 灵活性:允许用户指定多个感兴趣的事件,并可以根据实际发生的事件进行灵活处理。
- 超时控制:提供了超时控制机制,允许程序在等待事件发生的同时进行其他操作。
✅缺点
- 数据拷贝开销:与select类似,每次调用poll都需要将文件描述符集合从用户空间拷贝到内核空间,无论这些文件描述符是否就绪,这都会带来一定的开销。
- 轮询效率:当文件描述符数量非常多时,poll函数的轮询效率会显著下降。
七、 使用示例
以下是一个简单的使用poll函数来监视标准输入(文件描述符0)和普通文件读取事件的示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#define MAX_EVENTS 10
int main() {
struct pollfd fds[2];
char buf[1024];
int numfds, i;
// 监视标准输入
fds[0].fd = 0; // 文件描述符0代表标准输入
fds[0].events = POLLIN; // 等待读取事件
// 打开一个文件并监视其读取事件
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
fds[1].fd = fd;
fds[1].events = POLLIN; // 等待读取事件
numfds = 2; // 要监视的文件描述符数量
// 使用poll函数监视文件描述符
while (1) {
int ret = poll(fds, numfds, -1); // 无限期等待
if (ret == -1) {
// poll函数调用失败
perror("poll");
break;
}
// 检查哪个文件描述符上的事件发生了
for (i = 0; i < numfds; ++i) {
if (fds[i].revents & POLLIN) {
if (fds[i].fd == 0) {
// 标准输入有数据可读
if (fgets(buf, sizeof(buf), stdin) == NULL) {
// 处理输入结束的情况
break;
}
printf("Read from stdin: %s", buf);
} else if (fds[i].fd == fd) {
// 文件有数据可读
ssize_t nread = read(fd, buf, sizeof(buf) - 1);
if (nread == -1) {
// 处理读取错误
perror("read");
break;
}
buf[nread] = '\0'; // 确保字符串以null结尾
printf("Read from file: %s", buf);
}
}
// 可以在这里处理其他类型的事件,如POLLOUT、POLLERR等
}
}
close(fd); // 关闭打开的文件
return 0;
}
🚨注意事项
- 在使用poll函数时,需要确保
fds
数组中的所有文件描述符在调用poll函数之前都是有效的。 - poll函数返回后,
fds
数组中的revents
字段将被内核设置,以反映实际发生的事件。程序需要检查这些字段来确定哪个文件描述符上的事件发生了。 - 当poll函数返回时,它不会清空
fds
数组,因此可以连续多次调用poll函数,而无需重新初始化fds
数组(除非需要监视不同的文件描述符或事件)。
总结
poll函数是Linux系统下提供的一种强大的I/O多路复用机制,它允许程序同时监视多个文件描述符的状态变化,并通过轮询的方式检测事件的发生。尽管poll函数在灵活性和超时控制方面具有一定优势,但在处理大规模连接时可能会遇到性能瓶颈。因此,在实际应用中需要根据具体场景和需求选择合适的I/O多路复用机制。为了进一步优化大规模连接下的性能表现,Linux还提供了epoll这一更为高效的I/O多路复用机制,我们将在下一篇文章中深入探讨epoll的工作原理及其优势。
温馨提示
感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!