跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Linux 核心 IO 模型深析:Cmake 构建与 poll 多路转接实现

综述由AI生成Linux poll 多路转接 IO 模型原理与 C++ 实现。文章解析了 poll 系统调用接口、参数含义及与 select 的差异,结合 CMake 构建流程,展示了如何维护文件描述符数组、监听可读可写事件以及处理客户端连接与数据接收的完整代码示例。

PentesterX发布于 2026/2/28更新于 2026/5/2824 浏览
Linux 核心 IO 模型深析:Cmake 构建与 poll 多路转接实现

【一】Cmake 替代 make

理解:Cmake 中输入目标和源文件,可以自己调用 make 生成,更加简化,主流。

使用方法:

(1)先安装:需要 sudo 权限
# Ubuntu/Debian sudo apt update && sudo apt install cmake -y
# 验证安装(显示版本即成功)
cmake --version
(2)准备一个空目录

因为 Cmake 会产生一堆副文件,避免污染重要目录的源码,比如创建名为 build 的目录。

(3)三步上篮

在需要生成的源码同目录下创建 CMakeLists.txt 文件,添加下面的代码内容:第一行直接复制,第二行和第三行按照对应情况修改即可。

# 最低 CMake 版本要求(根据自己安装的版本调整,比如 3.10)
cmake_minimum_required(VERSION 3.10)
# 项目名(随便取,比如 myapp)
project(myapp)
# 生成可执行文件:可执行文件名为 myapp,编译的源文件是 main.c
add_executable(myapp main.c)
(4)效果

例如:用 cmake 指令调用 CMakeLists.txt 文件会直接生成 makefile 文件,再手动 make 指令。

如果要生成多个可执行程序:如果修改了源码,再重新 make /make clean 即可,和之前一样。

【二】poll 接口介绍

(1)函数原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(2)参数说明
(1)第一个参数

参数描述:指向结构体 pollfd 类型的指针(管理多个文件描述符可以是 struct pollfd 类型的数组)。

struct pollfd {
    int fd;             // 要监听的文件描述符(-1 表示忽略此结构体)
    short events;       // 要监听的事件(输入参数,由程序设置)
    short revents;      // 实际发生的事件(输出参数,由内核填充)
};

监听事件选项:events 由你设置选项,revents 由 poll 调用之后由操作系统给你填写。

事件标识含义(events 输入)含义(revents 输出)
POLLIN监听'可读'事件该 fd 有数据可读
POLLOUT监听'可写'事件该 fd 可写入数据
POLLERR无需主动设置该 fd 发生错误
POLLHUP无需主动设置该 fd 对应的连接关闭(如 socket 断开)
POLLNVAL无需主动设置fd 无效(如未打开)
(2)第二个参数

参数描述:数组中有效结构体的数量(必须大于 0),可理解为监听的文件描述符个数。

(3)第三个参数

参数描述:超时时间(单位:毫秒)。>0 最大返回时间;=0 非阻塞立刻返回;=-1 阻塞使用。

(3)返回值
  • 成功:返回就绪的文件描述符总数(可能为 0,即超时)
  • 失败:返回 -1,且设置 errno(如 EINTR 表示被信号中断,可重试)
(4)特点

(1)只要有事件就绪就会一直通知你,这和 select 通知特点一样

(2)监听个数由用户决定,受限于系统资源,而 select 受限于自身数组

(3)输入和输出分离,而 select 输入输出是合并的

(4)每次调用不需要重新设置监听集合

(5)函数使用
(1)建立关心数组
#define max_num_size 10
struct pollfd fds[max_num_size];
(2)初始化关心数组
void Initialize_struct() {
    for(int i=0;i<max_num_size;i++) {
        fds[i].fd=-1;
    }
}
(3)设置 poll

先将 listen 套接字添加到关心数组,再根据 poll 的返回值判断是否需要处理新链接。

void Deal() {
    // 初始化结构体
    Initialize_struct();
    // 将 listen 套接字添加到关心结构体
    fds[0].fd=_V.Fd();
    fds[0].events=POLLIN;
    for(;;) {
        // 计算关心的个数
        int nods=0;
        for(nods=0;nods<max_num_size;nods++) {
            if(fds[nods].fd==-1)continue;
            else nods++;
        }
        // 调用 poll
        int po = poll(fds, nods, 1000);
        // 判断事件
        switch (po) {
            case 0: {
                std::cout << "没有客户端访问我...." << std::endl;
                break;
            }
            case -1: {
                // log_message(LOG_LEVEL_ERROR,__FILE__,__LINE__,"错误码:%d,错误信息:%s",errno,strerror(errno));
                break;
            }
            default: {
                // 处理新链接
                Handle();
            }
        }
    }
}
(4)任务处理

遍历关心数组:如果是 listen 套接字并且该套接字准备就绪,就将 accept 返回的文件描述符重新添加到关心数组;如果不是 listen 套接字并且该套接字又准备就绪,说明是读端(只关心了读),可以直接 recv。

void Handle() {
    for(int i=0;i<max_num_size;i++) {
        // 如果是 listen 套接字且 listen 套接字准备就绪
        if(fds[i].fd==_V.Fd() && fds[i].revents & POLLIN) {
            Accept();
        } else if(fds[i].revents & POLLIN)//需要判断该文件描述符的读端是否就绪
        {
            Recv(i);
        }
    }
}
(5)添加新链接

找到下标为 -1 的空余结构体位置,将 accept 返回的进行添加。

void Accept() {
    // 获取 accept 文件描述符
    int fd = _V.Accept();
    // 添加到关心结构体
    int i;
    for(i=0;i<max_num_size;i++) {
        if(fds[i].fd==-1)break;
    }
    if(i==max_num_size) {
        std::cout<<"满了....."<<std::endl;
        close(fd);
    }
    // 添加
    fds[i].fd=fd;
    fds[i].events=POLLIN;
}
(6)读取数据

读取对应文件描述符即可。

void Recv(int i) {
    char buffer[1024] = {0};
    ssize_t d = recv(fds[i].fd, buffer, sizeof(buffer) - 1, 0);
    if (d > 0) {
        buffer[d] = 0;
        std::cout << "客户端发送了数据 : ";
        std::cout << buffer << std::endl;
    }else if (d == 0) {
        // 对方断开了连接
        close(fds[i].fd);
        fds[i].fd = -1;
        // 关闭当前的文件描述符,并且从数组中删掉
    } else {
        // 读取错误
        close(fds[i].fd);
        fds[i].fd = -1;
    }
}
(7)完整代码

注意:以下类中,Accept()为服务器 accept 函数。

// 辅助数组大小
#define max_num_size 10
class Media {
public:
    void Install() {
        _V.Socket(); // 绑定
        _V.Bind();
        _V.Listen();
    }
    void Initialize_struct() {
        for(int i=0;i<max_num_size;i++) {
            fds[i].fd=-1;
        }
    }
    void Accept() {
        // 获取 accept 文件描述符
        int fd = _V.Accept();
        // 添加到关心结构体
        int i;
        for(i=0;i<max_num_size;i++) {
            if(fds[i].fd==-1)break;
        }
        if(i==max_num_size) {
            std::cout<<"满了....."<<std::endl;
            close(fd);
        }
        // 添加
        fds[i].fd=fd;
        fds[i].events=POLLIN;
    }
    void Recv(int i) {
        char buffer[1024] = {0};
        ssize_t d = recv(fds[i].fd, buffer, sizeof(buffer) - 1, 0);
        if (d > 0) {
            buffer[d] = 0;
            std::cout << "客户端发送了数据 : ";
            std::cout << buffer << std::endl;
        }else if (d == 0) {
            // 对方断开了连接
            close(fds[i].fd);
            fds[i].fd = -1;
            // 关闭当前的文件描述符,并且从数组中删掉
        } else {
            // 读取错误
            close(fds[i].fd);
            fds[i].fd = -1;
        }
    }
    void Handle() {
        for(int i=0;i<max_num_size;i++) {
            // 如果是 listen 套接字且 listen 套接字准备就绪
            if(fds[i].fd==_V.Fd() && fds[i].revents & POLLIN) {
                Accept();
            } else if(fds[i].revents & POLLIN)//需要判断该文件描述符的读端是否就绪
            {
                Recv(i);
            }
        }
    }
    void Deal() {
        // 初始化结构体
        Initialize_struct();
        // 将 listen 套接字添加到关心结构体
        fds[0].fd=_V.Fd();
        fds[0].events=POLLIN;
        for(;;) {
            // 计算关心的个数
            int nods=0;
            for(nods=0;nods<max_num_size;nods++) {
                if(fds[nods].fd==-1)continue;
                else nods++;
            }
            // 调用 poll
            int po = poll(fds, nods, 1000);
            // 判断事件
            switch (po) {
                case 0: {
                    std::cout << "没有客户端访问我...." << std::endl;
                    break;
                }
                case -1: {
                    // log_message(LOG_LEVEL_ERROR,__FILE__,__LINE__,"错误码:%d,错误信息:%s",errno,strerror(errno));
                    break;
                }
                default: {
                    // 处理新链接
                    Handle();
                }
            }
        }
    }
private:
    Server _V;
    struct pollfd fds[max_num_size];
};

目录

  1. 【一】Cmake 替代 make
  2. (1)先安装:需要 sudo 权限
  3. Ubuntu/Debian sudo apt update && sudo apt install cmake -y
  4. 验证安装(显示版本即成功)
  5. (2)准备一个空目录
  6. (3)三步上篮
  7. 最低 CMake 版本要求(根据自己安装的版本调整,比如 3.10)
  8. 项目名(随便取,比如 myapp)
  9. 生成可执行文件:可执行文件名为 myapp,编译的源文件是 main.c
  10. (4)效果
  11. 【二】poll 接口介绍
  12. (1)函数原型
  13. (2)参数说明
  14. (1)第一个参数
  15. (2)第二个参数
  16. (3)第三个参数
  17. (3)返回值
  18. (4)特点
  19. (5)函数使用
  20. (1)建立关心数组
  21. (2)初始化关心数组
  22. (3)设置 poll
  23. (4)任务处理
  24. (5)添加新链接
  25. (6)读取数据
  26. (7)完整代码
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Ansible 批量部署 Nginx 实战指南
  • 卷积神经网络 CNN 经典架构解析与 PyTorch 实战
  • PyTorch 实战——基于文本引导的图像生成技术与 Stable Diffusion 实践
  • AI 提示词实战指南:覆盖 20+ 高频场景与技巧
  • 豆包高效学习:AI 时代教育破局与认知升级指南
  • 从 Web 到 API 驱动:Django REST Framework 重构智能合同审查系统
  • 字节跳动前端一面深度解析:React 原理与浏览器渲染
  • OpenClaw 构建飞书 AI 办公机器人:本地 Ollama 接入与 Skills 自动化
  • DooTask 轻量级项目管理与 AI 协同功能解析
  • MySQL 数据类型核心指南:选型、实战与避坑
  • Seq2Seq 模型实战:ScheduledEmbeddingTrainingHelper 原理与使用
  • Cursor 集成 MCP 服务实战指南:从配置到自动化任务执行
  • pyenv-win Python 多版本管理实战与效率优化方案
  • JavaScript 调试技巧与实用工具指南
  • 基于 GLM4.7 的 Claude Code GitHub 代码自动审查
  • AI 对话页流式处理架构:Web Streams 与 Fetch API 实践
  • AIGC 产品经理工作职责与职位要求解析
  • Spring Boot 全局异常处理与日志监控实战
  • Linux 命名管道(FIFO)通信:原理与跨进程实战
  • Java 大数据量 Excel 导入导出实现方案

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online