poll 系统调用详解
poll 是 Linux/Unix 系统中用于 多路复用 I/O 操作 的系统调用,允许程序同时监听多个文件描述符(File Descriptor, FD)的状态变化(如可读、可写、错误等)。它是 select 的改进版,解决了 select 的某些局限性。
- 核心功能
监听多个文件描述符:同时监控多个 FD 的状态(如套接字、管道、设备文件等)。
阻塞与非阻塞:可设置超时时间,实现阻塞或非阻塞等待。
事件驱动:当任一被监听的 FD 就绪(如数据可读、可写)时,立即返回。
- 函数原型
c
#include <poll.h>
int poll(struct pollfd fds, nfds_t nfds, int timeout);
参数说明
参数 类型 说明
fds struct pollfd 指向 pollfd 结构体数组,每个元素描述一个待监听的 FD 和事件。
nfds nfds_t 数组 fds 的长度(即监听的 FD 数量)。
timeout int 超时时间(毫秒):
-
0:等待指定毫秒后返回。
- 0:非阻塞,立即返回。
- -1:阻塞,直到有事件发生。
返回值
返回值 说明
0 返回就绪的 FD 数量(可处理的事件数)。
0 超时且无 FD 就绪。
-1 出错(如被信号中断),可通过 errno 获取错误码(如 EINTR)。
- struct pollfd 结构体
c
struct pollfd {
int fd; // 监听的文件描述符
short events; // 监听的事件(按位掩码)
short revents; // 实际发生的事件(由内核填充)
};
常用事件标志
事件 说明
POLLIN FD 有数据可读(如 Socket 收到数据)。
POLLOUT FD 可写(如 Socket 发送缓冲区未满)。
POLLERR FD 发生错误(仅用于 revents,不可设置在 events 中)。
POLLHUP FD 挂起(如对端关闭连接)。
POLLNVAL FD 未打开(无效描述符)。 - 工作流程
初始化 pollfd 数组:
指定需要监听的 FD 和事件(如 POLLIN)。
调用 poll:
阻塞等待,直到 FD 就绪或超时。
检查返回值:
若 >0,遍历 pollfd 数组,通过 revents 判断哪些 FD 就绪。
若 0,处理超时逻辑。
若 -1,处理错误。
- 代码示例
监听标准输入和Socket
c
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
int main() {
struct pollfd fds[2];
// 监听标准输入(fd=0)
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
// 监听一个假设的Socket(fd=4)
fds[1].fd = 4;
fds[1].events = POLLIN | POLLOUT;
while (1) {
int ret = poll(fds, 2, 5000); // 阻塞5秒
if (ret == -1) {
perror("poll error");
break;
} else if (ret == 0) {
printf("Timeout.\n");
continue;
}
// 检查哪些FD就绪
if (fds[0].revents & POLLIN) {
printf("stdin is readable.\n");
}
if (fds[1].revents & POLLIN) {
printf("socket is readable.\n");
}
if (fds[1].revents & POLLOUT) {
printf("socket is writable.\n");
}
}
return 0;
}
6. poll vs select
特性 poll select
FD 数量限制 无限制(基于数组) 受 FD_SETSIZE 限制(通常1024)
效率 高(无需每次重置 FD 集合) 低(需每次重建 fd_set)
内核实现 更现代 较老旧
可移植性 多数 Unix/Linux 所有平台
7. 常见问题
(1) 为什么用 poll 而不是 select?
poll 支持更多的 FD(select 默认限制为1024)。
poll 不需要每次调用前重新初始化 FD 集合。
(2) poll 的缺点
水平触发(LT):就绪事件需及时处理,否则会重复通知(对比 epoll 的 ET 模式)。
性能问题:当监听大量 FD 时,遍历数组效率较低(此时 epoll 更优)。
(3) 替代方案
epoll(Linux特有):高性能多路复用,适合海量连接。
kqueue(FreeBSD/macOS):类似 epoll。
- 总结
poll 适用场景:
✅ 监听少量到中等数量的 FD(如 <1000)。
✅ 需要跨平台兼容性(相比 epoll)。
核心步骤:
初始化 pollfd 数组。
调用 poll 阻塞等待。
遍历检查 revents。
扩展学习:
epoll(Linux 高性能网络编程)。
libevent(跨平台事件库,封装 poll/epoll)。