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

Poll 服务器实战教学:从 Select 迁移到更高效的多路复用

文章目录

  • 引言
  • 一、从 Select 到 Poll:核心修改点解析
    • 1. 数据结构替换:fd_set → struct pollfd 数组
    • 2. 移除最大 FD 跟踪:_max_fd成员变量
    • 3. 事件注册方式:从FD_SET到填充pollfd数组
    • 4. 等待事件:select() → poll()
    • 5. 就绪事件处理:从FD_ISSET到检查revents
  • 二、Poll 服务器的核心注意事项
  • 三、Poll 相较于 Select 的优化点
  • 四、Poll 的局限性
  • 五、测试效果与 Select 对比
  • 六、总结与迁移建议

引言

在 Linux 网络编程中,I/O 多路复用是实现并发服务的核心技术。上一篇我们实现了基于 Select 的服务器,但其存在文件描述符(FD)数量限制、需维护最大 FD 等痛点。本文将详解如何将 Select 服务器迁移至 Poll 模型,分析 Poll 的优化点与局限性,并通过代码展示具体实现。

一、从 Select 到 Poll:核心修改点解析

Poll 与 Select 同属 I/O 多路复用技术,但在数据结构和使用方式上有显著差异。以下是从 SelectServer 迁移到 PollServer 的关键修改:

1. 数据结构替换:fd_set → struct pollfd 数组

Select 使用 fd_set(位图)存储待监听的 FD,而 Poll 采用 struct pollfd 结构体数组,每个元素对应一个 FD 的监听配置:

// Poll使用的核心结构体(定义于<poll.h>)
struct pollfd {int   fd;         // 待监听的文件描述符short events;     // 关注的事件(输入:如POLLIN表示可读)short revents;    // 实际发生的事件(输出:由内核填充)
};

PollServer 中,我们用 std::vector<struct pollfd> 管理所有需要监听的 FD(监听套接字 + 客户端套接字),替代 Select 中的 fd_set

// PollServer.hpp中事件循环的核心数据结构
std::vector<struct pollfd> fds;  // 替代select中的fd_set

2. 移除最大 FD 跟踪:_max_fd成员变量

Select 需要传入最大 FD+1 作为第一个参数,因此必须维护 _max_fd 变量。而 Poll 通过数组长度确定监听范围,无需跟踪最大 FD,直接移除 _max_fd

// 对比:Select需要维护_max_fd,Poll无需此变量
// SelectServer中的成员:int _max_fd; 
// PollServer中无此成员,通过fds.size()确定监听数量

3. 事件注册方式:从FD_SET到填充pollfd数组

Select 每次循环需用 FD_ZERO 重置位图,再用 FD_SET 添加 FD;Poll 则通过清空并重新填充 pollfd 数组实现:

// Poll中的事件注册(替代Select的FD_ZERO/FD_SET)
fds.clear();  // 清空数组(替代FD_ZERO)// 添加监听套接字(关注可读事件)
struct pollfd listen_pfd;
listen_pfd.fd = _listen_socket->Fd();
listen_pfd.events = POLLIN;  // 关注可读事件(新连接)
listen_pfd.revents = 0;      // 重置输出事件
fds.push_back(listen_pfd);// 添加所有客户端套接字(关注可读事件)
for (const auto& [fd, client] : _clients) {struct pollfd client_pfd;client_pfd.fd = fd;client_pfd.events = POLLIN;  // 关注可读事件(数据到达)client_pfd.revents = 0;fds.push_back(client_pfd);
}

4. 等待事件:select() → poll()

Poll 的核心函数 poll() 参数更简洁,无需分别传入读 / 写 / 异常集合,直接传入 pollfd 数组及长度:

// Select的调用(需传入max_fd+1、读/写/异常集合、超时)
int ready = select(_max_fd + 1, &read_fds, nullptr, nullptr, nullptr);// Poll的调用(只需传入数组、长度、超时)
int ready = poll(fds.data(), fds.size(), -1);  // -1表示无限等待

5. 就绪事件处理:从FD_ISSET到检查revents

Select 通过 FD_ISSET 判断 FD 是否就绪,Poll 则通过 revents 字段(内核填充)判断事件类型:

// Select中判断就绪:遍历所有FD检查FD_ISSET
if (FD_ISSET(fd, &read_fds)) { ... }// Poll中判断就绪:遍历pollfd数组检查revents
for (const auto& pfd : fds) {if (pfd.revents & POLLIN) {  // 可读事件就绪if (pfd.fd == _listen_socket->Fd()) {HandleNewConnection();  // 监听套接字:新连接} else {HandleClientData(pfd.fd);  // 客户端套接字:数据处理}}
}

二、Poll 服务器的核心注意事项

  1. revents 的正确解读
    events 是输入参数(告诉内核关注哪些事件),revents 是输出参数(内核告知实际发生的事件)。需通过 revents 判断事件,而非 events。常见事件标志:

    • POLLIN:数据可读(新连接 / 客户端数据);
    • POLLERR:错误事件(如对方断开连接);
    • POLLHUP:挂起事件(连接被关闭)。
  2. pollfd 数组的动态维护
    客户端连接断开后,需从 _clients 映射中删除对应的 FD,下次循环重建 pollfd 数组时会自动排除该 FD,无需手动清理(对比 Select 需在 fd_set 中清除,否则会误判)。

  3. 超时参数的合理设置
    poll() 的第三个参数为超时时间(毫秒),-1 表示无限等待,0 表示非阻塞立即返回。实际应用中可设置超时(如 100ms),避免服务器永久阻塞,便于处理定时任务。

  4. 异常事件的处理
    即使未在 events 中设置 POLLERR,内核仍可能在 revents 中返回错误事件(如客户端强制断开),需在代码中补充处理:

if (pfd.revents & (POLLERR | POLLHUP)) {// 处理连接异常:关闭FD并清理HandleClientDisconnect(pfd.fd);
}

三、Poll 相较于 Select 的优化点

  1. 突破 FD 数量限制
    Select 的 fd_set 大小固定(默认 1024,由 FD_SETSIZE 定义),超过则无法监听;Poll 通过动态数组管理 FD,仅受系统最大文件描述符限制(可通过 ulimit -n 调整),支持更多客户端并发。
  2. 无需维护最大 FD
    Select 需跟踪 _max_fd 并传入 select(),增加代码复杂度;Poll 直接通过数组长度确定监听范围,简化逻辑。
  3. 事件类型更灵活
    Select 需为读、写、异常事件分别创建 fd_set;Poll 通过 events 字段为每个 FD 单独设置事件(如同时监听可读 + 可写),更灵活。
  4. 减少不必要的 FD
    重置 Select 的 fd_set 会被内核修改,每次循环需重新 FD_ZERO 并添加所有 FD;Poll 的 pollfd 数组虽也需重建,但逻辑更直观(清空后重新添加有效 FD 即可)。

四、Poll 的局限性

尽管 Poll 优化了 Select 的诸多问题,但仍存在以下不足:

  1. 轮询效率仍低
    无论是否有事件就绪,Poll 都需遍历整个 pollfd 数组检查 revents,当 FD 数量庞大(如 10 万 +)时,遍历耗时显著,效率下降。
  2. 无事件通知机制
    Poll 仍属于 “轮询” 模型,内核不会主动通知哪些 FD 就绪,需用户态遍历判断;而 epoll 通过 “回调” 机制直接返回就绪 FD 列表,无需轮询。
  3. 每次循环需重建数组
    虽然比 Select 的 fd_set 操作简单,但 Poll 仍需在每次循环中重建 pollfd 数组(添加监听 FD 和客户端 FD),存在一定开销。
  4. 不支持边缘触发(ET)
    Poll 仅支持水平触发(LT):只要 FD 有数据未处理,就会持续通知;边缘触发(ET)仅在状态变化时通知一次,可减少冗余事件,Poll 不支持此模式(epoll 支持)。

五、测试效果与 Select 对比

使用与 Select 服务器相同的客户端代码测试 PollServer,功能表现一致(支持多客户端并发连接、数据回显),但内部实现更高效:

  1. 启动服务器
    ./PollServer 8888
    # 输出:PollServer 初始化成功,监听端口:8888,listen_fd:3
    
  2. 多客户端连接
    新客户端连接,fd:4,地址:[127.0.0.1:43210]
    新客户端连接,fd:5,地址:[127.0.0.1:43211]
    [127.0.0.1] 发送数据: Hello Poll!
    [127.0.0.1] 发送数据: 测试并发
    
  3. 高并发场景差异
    当客户端数量超过 1024 时,Select 服务器会因 fd_set 限制无法处理新连接,而 PollServer 可继续正常工作(需提前调整系统 FD 限制)。

六、总结与迁移建议

Poll 是 Select 的升级版本,解决了 FD 数量限制和最大 FD 维护问题,更适合中高并发场景。从 Select 迁移到 Poll 的核心是:

  • struct pollfd 数组替代 fd_set
  • 移除 _max_fd,通过数组长度管理 FD;
  • revents 判断事件,替代 FD_ISSET
    但 Poll 仍未解决轮询效率问题,在高并发(1 万 + 客户端)场景下,建议进一步迁移到 epoll 模型。下一篇我们将详解 epoll 的实现与优势。
http://www.dtcms.com/a/578230.html

相关文章:

  • 代码管理——VS Code|Git
  • SkyWalking运维之路(Java探针接入)
  • 四川省建设厅注册中心网站wordpress主页加音乐
  • 广州企业网站模板建站专业做阿里巴巴网站的公司
  • 网站的ftp账号和密码谷歌外贸建站
  • 全球生物识别加密U盘市场:安全需求驱动增长,技术迭代重塑格局
  • 从 ChatGPT 到 OpenEvidence:AI 医疗的正确打开方式
  • 彩票网站自己可以做吗网站广告设计
  • 面试后查缺补漏--cmake,makefiles,g++,gcc(AI写)
  • C++入门(三) (算法竞赛)
  • 遵义市网站建设网络营销产品推广
  • Docker-仓库-镜像制作
  • 网站增值业务一手项目对接app平台
  • easyExcel单元格动态合并示例
  • 《嵌入式驱动(十一):I2C子系统架构》
  • Android 多版本Toast版本区别以及使用可能遇到的问题
  • 集团高端网站建设公司wordpress侧边栏文件
  • 【Java面向对象编程入门:接口、继承与多态】
  • 百度联盟的网站怎么做企业网站的设计要求有哪些
  • 别墅花园装修设计公司wordpress 网址优化
  • 云服务器可以做虚拟机吗?
  • 计算机操作系统:文件存储空间的管理
  • 【stm32协议外设篇】- PAJ7620手势识别传感器
  • 网站增加权重吗免费ip地址
  • “工业数据库怎么选”之一:深度解析 PI System vs TDengine
  • 如何做好网站盘锦网站建设策划
  • k8s kubelet Nameserver limits exceeded
  • 供暖季技术实战:益和热力用 TDengine 时序数据库破解热力数据处理难题
  • 蔡甸建设局网站jsp源码做网站
  • AI代码开发宝库系列:Dify本地化部署和应用