在Select的基础上学习poll

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;
                    }
                }
            }
        }
    }
}

3:项目连接

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

相关推荐
d111111111d2 小时前
STM32-UART抽象层封装调试
笔记·stm32·单片机·嵌入式硬件·学习
喜欢吃燃面2 小时前
Linux 信号保存机制深度解析:从内核数据结构到进程状态管理
linux·运维·数据结构·学习
云边有个稻草人2 小时前
【Linux系统】第十节—【进程概念】环境变量 | 详解,包会!
linux·环境变量·命令行参数·环境变量的特性·获取linux环境变量的方法·环境变量path·通过代码获取linux环境变量
秋雨梧桐叶落莳2 小时前
iOS——Masonry约束内容整理
开发语言·学习·macos·ios·objective-c·cocoa
IMPYLH2 小时前
Linux 的 stdbuf 命令
linux·运维·服务器·bash
郝学胜-神的一滴2 小时前
从底层看透Linux高性能服务器:epoll自定义封装与超时清理实战
linux·服务器·c++·网络协议·tcp/ip·unix
keyipatience2 小时前
12.GDB调试技巧与计算机体系结构解析
linux·运维·服务器
小夏子_riotous2 小时前
Docker学习路径——9、Docker 网络深度解析:从默认网络到自定义网络实战
linux·运维·网络·docker·容器·centos·云计算
峥无2 小时前
《read/write的秘密:文件描述符、重定向与用户态缓冲区》
linux·运维·服务器·进程