在Select的基础上学习poll
1poll的核心定位poll 是Linux 下为解决 select 缺陷而设计的 IO 多路复用系统调用。核心能力一个线程同时监控多个文件描述符fd阻塞等待直到有 fd 就绪。设计目标解决 select 的fd 数量上限、参数必须重置、使用繁琐问题。本质用结构体数组替代 select 的位图多路复用逻辑不变底层实现优化。2poll的核心结构体poll不再使用位图fd_set而是使用结构体数组管理每个fdstruct pollfd { int fd; // 待监控的文件描述符 short events; // 用户→内核我要监控什么事件输入 short revents; // 内核→用户实际发生了什么事件输出 };1fd要监控的文件描述符socket fd。fd -1表示该位置无效内核会直接忽略这个元素。2events输入参数用户告诉内核「我需要监控这个 fd 的哪些事件」。可通过位运算同时监控多个事件如POLLIN | POLLOUT。3revents输出参数内核返回给用户「这个 fd 实际发生了哪些事件」。内核只会修改 revents不会覆盖 events这是 poll 不用重置的关键。3poll函数原型#include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout);1fdsstruct pollfd结构体数组的首地址。存放所有需要监控的 fd 信息。2nfds结构体数组的有效长度内核需要遍历的元素个数。不用像 select 一样算max_fd1直接传数组长度即可。3timeout-1永久阻塞直到有 fd 就绪。0非阻塞立即返回只检测当前状态。0等待指定毫秒数超时返回 0。返回值0就绪的文件描述符总数。0超时没有任何 fd 就绪。-1调用失败错误码存于 errno。4poll核心事件类型poll 支持的事件比 select 丰富服务器开发只需要重点记 3 个1POLLIN读就绪监听socket有新客户端连接已连接socket接收缓冲区有数据/对端关闭连接2POLLOUT写就绪发送缓冲区有空余空间可以无阻塞send3POLLERR异常就绪文件描述符发生错误5poll的完整流程6poll 的就绪条件和 select 完全一致1读就绪 POLLIN监听socket有新连接请求已连接socket接收缓冲区有数据已连接socket对端发送关闭recv返回02写写就绪POLLOUT发送缓冲区有空闲空间非阻塞connect成功/失败3异常就绪POLLERR文件描述符发生错误。7poll的优缺点优点解决了 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) 遍历每次调用都要遍历所有 fdfd 越多效率越低。用户态→内核态拷贝每次都要拷贝整个 pollfd 数组fd 数量大时开销高。跨平台差仅支持 Linux/UnixWindows 不支持。使用更简单不用维护max_fd不用位图操作FD_SET/FD_ZERO。事件更灵活支持位运算可同时监控读、写、异常事件。8poll和select的对比理论点selectpoll数据结构位图fd_set结构体数组pollfdfd 数量上限固定 4096无上限内存限制参数重置必须重置位图无需重置输入输出分离最大 fd 计算需要算 max_fd1不需要传数组长度内核遍历效率O(n)O(n)跨平台全平台支持仅 Linux/Unix9项目级别的Poll的应用只是在上一篇文章的Select的实现改成了Poll的实现1PoolLoop.hpp#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::vectorpollfd m_pollFds;//poll核心结构体数组 std::vectorConnection* m_connections;//储存连接对象的指针,方便管理 std::mutex mtx;//线程锁 };2PollLoop.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(connnullptr) { LOG_FATAL(Poll addconnection faild:null connetion pointer); return false; } std::lock_guardstd::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_eventtrue; while(true) { //线程安全拷贝 std::vectorpollfd tmpPollFds; std::vectorConnection* tmpConns; { std::lock_guardstd::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(eventnum0) { continue; } else { if(has_valid_event) { LOG_INFO(Poll Success); has_valid_eventfalse; } } //处理就绪事件 for(size_t i 0;itmpPollFds.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; } } } } } }3项目连接https://github.com/silin-code/study-code/tree/c47bb5ef582a35c62c6f3fe80f214f76659e52f2/Project_TCPServer_Plus