1:poll的核心定位
poll 是Linux 下为解决 select 缺陷而设计的 IO 多路复用系统调用。
- 核心能力:一个线程同时监控多个文件描述符(fd),阻塞等待直到有 fd 就绪。
- 设计目标:解决 select 的fd 数量上限、参数必须重置、使用繁琐问题。
- 本质:用结构体数组替代 select 的位图,多路复用逻辑不变,底层实现优化。
2:poll的核心结构体
poll不再使用位图fd_set,而是使用结构体数组管理每个fd
cpp
struct pollfd {
int fd; // 待监控的文件描述符
short events; // 用户→内核:我要监控什么事件(输入)
short revents; // 内核→用户:实际发生了什么事件(输出)
};
1:fd
- 要监控的文件描述符(socket fd)。
- fd = -1 :表示该位置无效,内核会直接忽略这个元素。
2:events
- 输入参数:用户告诉内核「我需要监控这个 fd 的哪些事件」。
- 可通过位运算 同时监控多个事件(如
POLLIN | POLLOUT)。
3:revents
- 输出参数:内核返回给用户「这个 fd 实际发生了哪些事件」。
- 内核只会修改 revents ,不会覆盖 events,这是 poll 不用重置的关键。
3:poll函数原型
cpp
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
1:fds
struct pollfd结构体数组的首地址。- 存放所有需要监控的 fd 信息。
2:nfds
- 结构体数组的有效长度(内核需要遍历的元素个数)。
- 不用像 select 一样算
max_fd+1,直接传数组长度即可。
3:timeout
-1:永久阻塞,直到有 fd 就绪。0:非阻塞,立即返回,只检测当前状态。>0:等待指定毫秒数,超时返回 0。
返回值
- >0 :就绪的文件描述符总数。
- =0:超时,没有任何 fd 就绪。
- =-1:调用失败,错误码存于 errno。
4:poll核心事件类型
poll 支持的事件比 select 丰富,服务器开发只需要重点记 3 个:
1:POLLIN(读就绪)
监听socket:有新客户端连接
已连接socket:接收缓冲区有数据/对端关闭连接
2:POLLOUT(写就绪)
发送缓冲区有空余空间,可以无阻塞send
3:POLLERR(异常就绪)
文件描述符发生错误
5:poll的完整流程
6:poll 的就绪条件(和 select 完全一致)
1:读就绪( POLLIN)
监听socket:有新连接请求
已连接socket:接收缓冲区有数据
已连接socket:对端发送关闭(recv返回0)
2:写写就绪(POLLOUT)
发送缓冲区有空闲空间
非阻塞connect成功/失败
3:异常就绪(POLLERR)
文件描述符发生错误。
7:poll的优缺点
优点(解决了 select 的 3 大硬伤)
- 初始化 pollfd 数组 把数组中所有元素的
fd置为-1,标记为无效位置。 - 加入监控 把监听 socket 的 fd 填入数组,设置
events = POLLIN(监控读事件)。 - 调用 poll 内核开始遍历数组,只检查
fd != -1的元素,阻塞等待就绪。 - 内核标记就绪 内核检测到某个 fd 就绪,就把对应元素的
revents置为对应事件。 - poll 返回 返回就绪 fd 数量,不会修改 events,不用重置监控集合。
- 用户态遍历 遍历 pollfd 数组,检查
revents判断哪个 fd 就绪。 - 处理事件监听 fd 就绪 → accept 新连接;普通 fd 就绪 → recv 读数据。
- 循环执行 新连接加入数组,断开连接的 fd 置为
-1,继续循环。
无 fd 数量上限 仅受系统内存限制,不用修改内核,默认可支持上万 fd。 无需重置监控集合 events(输入)和 revents(输出)分离,内核不覆盖用户设置。
缺点(和 select 一样的底层缺陷)
- 内核 O (n) 遍历每次调用都要遍历所有 fd,fd 越多效率越低。
- 用户态→内核态拷贝每次都要拷贝整个 pollfd 数组,fd 数量大时开销高。
- 跨平台差 仅支持 Linux/Unix,Windows 不支持。
- 使用更简单 不用维护
max_fd,不用位图操作(FD_SET/FD_ZERO)。 - 事件更灵活支持位运算,可同时监控读、写、异常事件。
- 使用更简单 不用维护
8:poll和select的对比
| 理论点 | select | poll |
|---|---|---|
| 数据结构 | 位图(fd_set) | 结构体数组(pollfd) |
| fd 数量上限 | 固定 4096 | 无上限(内存限制) |
| 参数重置 | 必须重置位图 | 无需重置(输入输出分离) |
| 最大 fd 计算 | 需要算 max_fd+1 | 不需要,传数组长度 |
| 内核遍历效率 | O(n) | O(n) |
| 跨平台 | 全平台支持 | 仅 Linux/Unix |
9:项目级别的Poll的应用
只是在上一篇文章的Select的实现改成了Poll的实现
1:PoolLoop.hpp
cpp
#pragma once
#include "EventLoop.hpp"
#include "../connection/Connection.hpp"
#include <vector>
#include <poll.h>
#include <mutex>
class PollLoop : public EventLoop{
public:
//接口和SelectPoll一样
bool init() override;
bool addConnection(Connection* conn) override;
void run() override;
private:
std::vector<pollfd> m_pollFds;//poll核心结构体数组
std::vector<Connection*> m_connections;//储存连接对象的指针,方便管理
std::mutex mtx;//线程锁
};
2:PollLoop.cpp
cpp
#include "PollLoop.hpp"
#include "../../config/Config.hpp"
#include "../log/Logger.hpp"
#include "../connection/Connection.hpp"
#include <iostream>
#include <algorithm>
//事件初始化
bool PollLoop::init()
{
m_pollFds.clear();
m_connections.clear();
LOG_INFO("SelectPool initialized successfully");
return true;
}
//添加客户端到poll监听
bool PollLoop::addConnection(Connection* conn)
{
if(conn==nullptr)
{
LOG_FATAL("Poll addconnection faild:null connetion pointer");
return false;
}
std::lock_guard<std::mutex> lock(mtx);
int clientfd= conn->getFd();
/*struct pollfd {
int fd; // 待监控的文件描述符
short events; // 用户→内核:我要监控什么事件(输入)
short revents; // 内核→用户:实际发生了什么事件(输出)*/
//浮躁pollfd结构体监听读事件
pollfd pfd{};
pfd.fd = clientfd;
pfd.events = POLLIN;//监听客户端发送的数据
m_pollFds.push_back(pfd);//加入核心结构体数组
m_connections.push_back(conn);//加入连接管理队列
LOG_INFO("PollLoop added connection fd=%d",clientfd);
return true;
}
//Poll事件循环
void PollLoop::run()
{
LOG_INFO("PollLoop event loop started %p",pthread_self());
//保证只打印一次
bool has_valid_event=true;
while(true)
{
//线程安全拷贝
std::vector<pollfd> tmpPollFds;
std::vector<Connection*> tmpConns;
{
std::lock_guard<std::mutex> lock(mtx);
tmpPollFds = m_pollFds;
tmpConns = m_connections;
}//锁立即释放,无死锁
//poll系统调用
//参数1:pollfd数组首地址
//参数2:数组大小
//参数3:超时时间(ms)
//返回值,>0就绪时间总数;==0超时;==-1出错
int eventnum= poll(tmpPollFds.data(),tmpPollFds.size(),5000);
if(eventnum<=0)
{
continue;
}
else
{
if(has_valid_event)
{
LOG_INFO("Poll Success");
has_valid_event=false;
}
}
//处理就绪事件
for(size_t i =0;i<tmpPollFds.size();i++)
{
//只处理就绪事件
if(tmpPollFds[i].revents & POLLIN)
{
int ready_fd= tmpPollFds[i].fd;
//找到对应链接
for(auto conn:tmpConns)
{
if(conn->getFd()==ready_fd)
{
//找到了
conn->readData();//处理客户端消息
break;
}
}
}
}
}
}