【笔记】poll,实现I/O多路复用的一种机制,是 select 的改进版
我来为你讲解 poll。它和 select 一样,是用于实现I/O多路复用的一种机制,可以看作是 select 的改进版。
🛠 通俗理解 Poll
你可以继续用“前台管家”的比喻来理解 poll。如果说 select 管家用的是固定大小的纸质名单来记录需要照看的连接,那么 poll 管家则用上了一张可动态扩展的电子清单。
这个改变带来一个核心优势:poll 不再有监视连接数量的上限限制(而 select 通常有1024的限制)。
📊 Poll 与 Select 的核心区别
下表清晰地展示了两者的主要差异:
特性 | Select | Poll |
---|---|---|
连接数限制 | 有(通常1024) | 无 |
高效性 | 每次需拷贝整个描述符集合 | 只需拷贝一次感兴趣的事件数组 |
使用便利性 | 需要维护三个独立的描述符集合 | 使用统一的 pollfd 结构数组,更清晰 |
事件检测 | 返回后,集合被修改,需遍历所有描述符 | 返回后,通过检查 revents 字段判断具体事件 |
🧩 Poll 的工作机制:使用 pollfd 结构体
poll 的核心是 pollfd 结构体数组。每个 pollfd 结构体就像管家电子清单上的一条详细待办事项,明确指出了要监控哪个连接,以及关心什么事件。
struct pollfd {
int fd; // 文件描述符(需要监视的连接)
short events; // 请求监视的事件(你关心它发生什么,如可读)
short revents; // 实际发生的事件(返回时由内核填充,表示实际发生了什么)
};
常用的事件标志(events/revents):
· POLLIN:数据可读(相当于 select 的可读事件)
· POLLOUT:数据可写(相当于 select 的可写事件)
· POLLERR:发生错误
🚀 Poll 的工作三步曲
使用 poll 同样遵循三个步骤,但更简洁:
-
准备清单:初始化一个 pollfd 结构体数组。
为每个需要监视的连接创建一个 pollfd,设置好 fd 和关心的 events(比如 POLLIN)。 -
派遣等待:调用 poll 函数。
c
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
· fds:指向 pollfd 结构体数组的指针。
· nfds:数组的大小(即要监视的连接总数)。
· timeout:超时时间(毫秒)。-1 表示阻塞等待。
- 处理结果:检查返回并处理事件。
· 返回值 > 0:表示有事件发生的连接数量。
· 遍历 pollfd 数组,检查每个结构的 revents 字段是否被设置(如 if (fds[i].revents & POLLIN))。
· 对 revents 被设置的连接进行相应的 I/O 操作。
💡 简单示例:服务器监听
这是一个使用 poll 的简单服务器监听逻辑伪代码:
// 创建监听socket,绑定,监听…
struct pollfd fds[200]; // 假设最多监控200个连接
int fd_count = 1; // 初始只有监听socket
fds[0].fd = listen_sock;
fds[0].events = POLLIN; // 关心新连接
while (1) {
int ret = poll(fds, fd_count, -1); // 阻塞等待
if (ret > 0) {
// 1. 检查是否有新连接
if (fds[0].revents & POLLIN) {
int new_client = accept(…);
// 将 new_client 加入 fds 数组,设置 events 为 POLLIN
fd_count++;
}
// 2. 检查所有客户端连接是否有数据可读
for (int i = 1; i < fd_count; i++) {
if (fds[i].revents & POLLIN) {
// 读取 fds[i].fd 上的数据
}
}
}
}
⚠️ Poll 的局限性
虽然 poll 解决了 select 的连接数限制问题,但它仍然存在一个性能瓶颈:每次调用 poll,不管连接是否活跃,内核都需要线性扫描整个 pollfd 列表。当同时维护数万个连接,但只有少数活跃时,这种扫描效率很低。
因此,在需要处理极高并发(如C10K问题及以上)的场景下,Linux 系统通常会选择更高效的 epoll,而 BSD 系统则会选择 kqueue。
💎 总结
简单来说,poll 是 select 的增强版,它通过使用一个可扩展的结构体数组,突破了连接数量的限制,使得程序能够更高效地管理更多的网络连接。虽然在高并发场景下已被 epoll/kqueue 超越,但它仍然是理解 I/O 多路复用演进过程中的重要一环。
希望这个解释对你有帮助!如果想了解更高性能的 epoll,我们可以继续。