当前位置: 首页 > news >正文

在Select的基础上学习poll

1:poll的核心定位

poll 是Linux 下为解决 select 缺陷而设计的 IO 多路复用系统调用

  • 核心能力:一个线程同时监控多个文件描述符(fd),阻塞等待直到有 fd 就绪。
  • 设计目标:解决 select 的fd 数量上限、参数必须重置、使用繁琐问题。
  • 本质:用结构体数组替代 select 的位图,多路复用逻辑不变,底层实现优化。

2:poll的核心结构体

poll不再使用位图fd_set,而是使用结构体数组管理每个fd

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函数原型

#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的对比

理论点selectpoll
数据结构位图(fd_set)结构体数组(pollfd)
fd 数量上限固定 4096无上限(内存限制)
参数重置必须重置位图无需重置(输入输出分离)
最大 fd 计算需要算 max_fd+1不需要,传数组长度
内核遍历效率O(n)O(n)
跨平台全平台支持仅 Linux/Unix

9:项目级别的Poll的应用

只是在上一篇文章的Select的实现改成了Poll的实现

1:PoolLoop.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::vector<pollfd> m_pollFds;//poll核心结构体数组 std::vector<Connection*> m_connections;//储存连接对象的指针,方便管理 std::mutex mtx;//线程锁 };

2:PollLoop.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; } } } } } }

3:项目连接

https://github.com/silin-code/study-code/tree/c47bb5ef582a35c62c6f3fe80f214f76659e52f2/Project_TCPServer_Plus

http://www.jsqmd.com/news/707599/

相关文章:

  • VS Code 远程容器环境卡顿、构建失败、端口映射失效(2024最新避坑图谱)
  • AI头像生成器小白指南:避开新手常见坑点
  • 2026年4月国内心理咨询机构推荐:五家口碑服务评测对比领先职场压力焦虑失眠 - 品牌推荐
  • 贪心算法(Greedy Algorithm)详解:从理论到C++实践
  • 月饼包装设计公司哪家专业靠谱?做爆款月饼礼盒设计,优先选哲仕品牌策略设计公司 - 设计调研者
  • nli-MiniLM2-L6-H768保姆级教程:Windows/Mac/Linux三平台NLI本地化部署
  • GLM-4.1V-9B-Base入门必备:JDK1.8环境下Java客户端调用指南
  • 靠谱的新疆生态修复排名情况
  • 动态规划专题(10):最优三角剖分问题
  • 2025-2026年美国专利申请代理机构推荐:五大口碑服务评测评价知名高校技术转化授权难题 - 品牌推荐
  • 使用 PHP TrueAsync 改造 Laravel 协程异步化的可行路径
  • 雁塔区底盘异响松散推荐哪家
  • 《三步构建QClaw防幻觉体系,告别虚假信息》
  • AzurLaneAutoScript:告别重复操作的游戏自动化智能脚本终极指南
  • C++实例讲解四种类型转换的使用
  • AXI pSRAM设计及SoC集成验证
  • 【数据集】分省公共数据开放平台明细数据(2010-2025年)
  • MCP 2026车载适配失败率高达67%?揭秘TOP3硬件抽象层(HAL)配置陷阱及修复代码级方案
  • 电钢琴深度解析:从参数到家用场景适配指南
  • MySQL:Fuzzy Checkpoint
  • 全新二级域名分发系统网站源码_终极最强版
  • 贝叶斯网络滚动轴承故障识别算法设计与实现【附代码】
  • 华硕笔记本性能优化革命:5步告别臃肿控制中心,体验极致轻量化
  • GHelper:华硕笔记本终极性能优化免费指南,释放硬件潜能
  • G-Helper深度配置指南:解锁华硕笔记本隐藏功能的5个实战技巧
  • Iwara下载工具完整指南:如何快速高效地批量下载Iwara视频
  • AI智能体记忆系统:Memstate-skill实现持久化、版本化项目上下文管理
  • 【开源文本编辑器】轻量级免费文本编辑器——Notepad++ 完全教程 开发运维办公学习必备
  • Kotlin的@DslMarker:防止DSL作用域污染
  • 百度网盘提取码智能获取工具:3秒破解资源访问密码的高效解决方案