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

Linux 多路转接技术详解

详细阐述了 Linux 下的多路转接技术,涵盖 select、poll 和 epoll 三种系统调用接口。重点解析了 select 的参数、工作流程及 Echo 服务器实现代码,并分析其优缺点与适用场景。同时简要介绍了 poll 和 epoll 的工作原理及优势,指出 epoll 因性能更优成为实际生产环境中的首选方案,适用于高并发网络服务开发。

链路追踪发布于 2026/3/21更新于 2026/8/557 浏览
Linux 多路转接技术详解

多路转接技术

在之前学习五种 IO 模型时,我们认识到了 IO 的本质是等待 + 拷贝,而多路转接技术可以让等待的过程重叠,即同时等待多个文件描述符的就绪状态。本文将介绍三种实现多路转接的系统调用接口,实际最常用的是 epoll,一些老旧机器上只兼容 select,而 poll 并不常用。

1. select

select 是系统提供的一个多路转接接口。

  • select 系统调用可以让程序同时监视多个文件描述符上的事件是否就绪。
  • select 的核心工作就是等待,当监视的多个文件描述符中有一个或多个事件就绪时,select 才会成功返回并将对应文件描述符的就绪事件告知调用者。

1.1 select 系统调用及参数介绍

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

  • nfds:需要监视的文件描述符中,最大的文件描述符值 +1(select 底层使用 for 循环遍历实现,该值是为了界定遍历范围)。
  • readfds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的读事件是否就绪,返回时内核告知用户哪些文件描述符的读事件已经就绪。
  • writefds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的写事件是否就绪,返回时内核告知用户哪些文件描述符的写事件已经就绪。
  • exceptfds:输入输出型参数,调用时用户告知内核需要监视哪些文件描述符的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪。
  • timeout:输入输出型参数,调用时由用户设置 select 的等待时间,返回时表示 timeout 的剩余时间。

参数 timeout 的取值:

  • NULL/nullptr:select 调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
  • 0:select 调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,select 检测后都会立即返回。
  • 特定的时间值:select 调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后 select 进行超时返回。

返回值说明:

  • 如果函数调用成功,则返回有事件就绪的文件描述符个数。
  • 如果 timeout 时间耗尽,则返回 0。
  • 如果函数调用失败,则返回 -1,同时错误码会被设置。

select 调用失败时,错误码可能被设置为:EBADF、EINTR、EINVAL、ENOMEM。

(1)fd_set 类型

fd_set 类型可以理解为一个位图,每个比特位位置代表是哪个文件描述符,值代表是否就绪。

调用 select 函数之前就需要用 fd_set 结构定义出对应的文件描述符集,然后将需要监视的文件描述符添加到文件描述符集当中。系统提供了一组专门的接口用于操作:

void FD_CLR(int fd, fd_set *set); // 清除描述词组 set 中相关 fd 的位
 ; 
; 
; 
int
FD_ISSET
(int fd, fd_set *set)
// 测试描述词组 set 中相关 fd 的位是否为真
void FD_SET(int fd, fd_set *set)
// 设置描述词组 set 中相关 fd 的位
void FD_ZERO(fd_set *set)
// 清除描述词组 set 的全部位
(2)timeval 结构

传入 select 函数的最后一个参数 timeout 是一个指向 timeval 结构的指针,timeval 结构用于描述一段时间长度,包含 tv_sec(秒)和 tv_usec(微秒)两个成员。

(3)socket 就绪条件

读就绪

  • socket 内核接收缓冲区中的字节数大于等于低水位标记 SO_RCVLOWAT。
  • TCP 通信中对端关闭连接,对该 socket 读返回 0。
  • 监听的 socket 上有新的连接请求。
  • socket 上有未处理的错误。

写就绪

  • socket 内核发送缓冲区中的可用字节数大于等于低水位标记 SO_SNDLOWAT。
  • socket 的写操作被关闭,触发 SIGPIPE 信号。
  • socket 使用非阻塞 connect 连接成功或失败之后。
  • socket 上有未读取的错误。

1.2 select 基本工作流程

利用 select 多路转接实现一个简单的 Echo 服务器,该服务器要做的就是读取客户端发来的数据并进行打印:

  1. 先初始化服务器,完成套接字的创建、绑定和监听。
  2. 定义一个 fd_array 辅助数组用于保存监听套接字和已建立连接的套接字,初始时将监听套接字添加到 fd_array 数组当中。
  3. 服务器开始循环调用 select 函数,检测读事件是否就绪,如果就绪则执行对应的操作。
  4. 每次调用 select 函数之前,都需要定义一个读文件描述符集 readfds,并将 fd_array 辅助数组当中保存的文件描述符依次设置进 readfds 当中。
  5. 当 select 检测到数据就绪时会将读事件就绪的文件描述符设置进 readfds 当中,此时通过提取 readfds 中的信息得知哪些文件描述符已经就绪。
    • 如果读事件就绪的是监听套接字,则调用 accept 函数获取新连接,并将该连接对应的套接字添加到 fd_array 数组当中。
    • 如果读事件就绪的是与客户端建立连接的套接字,则调用 read 函数读取客户端发来的数据并打印输出。
  6. 如果客户端将连接关闭,服务器应调用 close 关闭该套接字,并从 fd_array 辅助数组当中清除。

为什么要有辅助数组?

  • 随着客户端连接的建立和断开,需要监听的文件描述符集合会动态变化。select 调用后,只有发生事件的文件描述符会被保留,未发生事件的会被清除。
  • 因此,每次调用 select 之前,都需要重新构建文件描述符集合。辅助数组可以用来存储当前所有需要监听的文件描述符。

说明

  • 服务器刚开始运行时,fd_array 数组当中只有监听套接字,后续每次调用 accept 获取到新连接后,都会将新连接对应的套接字添加到 fd_array 当中。
  • 由于调用 select 时还需要传入被监视的文件描述符中最大文件描述符值 +1,因此每次在遍历 fd_array 对 readfds 进行重新设置时,还需要记录最大文件描述符值。

1.3 select 技术实现 Echo 服务器

(1)构造服务器

对于一个 Tcp 服务器来说,首先需要创建一个 listen 套接字,然后在该 listen 套接字上等待获取新连接。我们将获取新连接的行为看作读事件,所以在构造时,创建完 listen 套接字后,还需要将 listen 套接字放入辅助数组。

SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>()) {
    InetAddr addr("0", _port);
    _listensock->BuildListenSocket(addr);
    for (int i = 0; i < N; i++) {
        _fd_array[i] = defaultfd;
    }
    // listensocket 等待新连接到来,等价于对方给我发送数据!我们作为读事件统一处理
    // 新连接到来 等价于 读事件就绪!
    // 首先要将 listensock 添加到 select 中!
    _fd_array[0] = _listensock->SockFd();
}
(2)服务器核心逻辑 Loop

服务器首先必须要有一个 Loop() 方法,是服务器执行的主逻辑,服务器就是一个死循环。

我们需要利用 select 监视读事件,所以每次循环都需要首先初始化出来一个 fd_set 结构,并利用 FD_ZERO 将该文件描述符集内容清空,然后将辅助数组中保存的就绪的文件描述符通过 FD_SET 函数赋值给文件描述符集,注意更新最大文件描述符的值。(这里我们只考虑读事件)。

然后根据 select 的返回值打印日志,当 select 返回值 > 0 时,证明监视的套接字中有读事件发生,此时对读事件进行处理。

void Loop() {
    while (true) {
        fd_set rfds;
        FD_ZERO(&rfds);
        int max_fd = defaultfd;
        for (int i = 0; i < N; i++) {
            if (_fd_array[i] == defaultfd) continue;
            FD_SET(_fd_array[i], &rfds);
            if (max_fd < _fd_array[i]) {
                max_fd = _fd_array[i];
            }
        }
        struct timeval timeout = {0, 0};
        // select 同时等待的 fd,是有上限的。因为 fd_set 是具体的数据类型,有自己的大小!
        // rfds 是一个输入输出型参数,每次调用,都要对 rfds 进行重新设定!
        int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr);
        switch (n) {
            case 0: LOG(INFO, "timeout, %d.%d\n", timeout.tv_sec, timeout.tv_usec); break;
            case -1: LOG(ERROR, "select error...\n"); break;
            default: LOG(DEBUG, "Event Happen. n : %d\n", n); HandlerEvent(rfds); break;
        }
    }
}
(3)对读事件进行处理 HandlerEvent

调用该函数时,证明此时 rfds 文件描述符集已经被设定,文件描述符集中就是哪些文件描述符发生读事件。我们需要遍历辅助数组中保存的文件描述符,检测他们在 rfds 中是否被设定,如果被设定证明在该文件描述符上有事件发生需要进行处理。

void HandlerEvent(fd_set &rfds) {
    for (int i = 0; i < N; i++) {
        if (_fd_array[i] == defaultfd) continue;
        if (FD_ISSET(_fd_array[i], &rfds)) {
            // 处理具体事件逻辑
        }
    }
}

2. poll

poll 是另一种多路转接接口,了解即可。

2.1 poll 系统调用及参数介绍

2.2 poll 技术实现 Echo 服务器

2.3 poll 优缺点

3. epoll

epoll 是 Linux 下性能更好的多路转接接口。

3.1 epoll 系统调用及参数介绍

3.2 epoll 工作原理

  • 回调机制

3.3 eventpoll 与文件

3.4 epoll 技术实现 Echo 服务器

3.5 epoll 优点

3.6 epoll 工作方式

目录

  1. 多路转接技术
  2. 1. select
  3. 1.1 select 系统调用及参数介绍
  4. (1)fd_set 类型
  5. (2)timeval 结构
  6. (3)socket 就绪条件
  7. 1.2 select 基本工作流程
  8. 1.3 select 技术实现 Echo 服务器
  9. (1)构造服务器
  10. (2)服务器核心逻辑 Loop
  11. (3)对读事件进行处理 HandlerEvent
  12. 2. poll
  13. 2.1 poll 系统调用及参数介绍
  14. 2.2 poll 技术实现 Echo 服务器
  15. 2.3 poll 优缺点
  16. 3. epoll
  17. 3.1 epoll 系统调用及参数介绍
  18. 3.2 epoll 工作原理
  19. 3.3 eventpoll 与文件
  20. 3.4 epoll 技术实现 Echo 服务器
  21. 3.5 epoll 优点
  22. 3.6 epoll 工作方式
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • DooTask 升级指南:新增 AI 助手与协作功能
  • Rust 异步编程实战:构建高性能 WebSocket 服务
  • Git 推送报错 src refspec main does not match any 解决
  • Python 使用 Web Unlocker API 抓取亚马逊数据
  • 在 Cursor 中配置和使用 MCP 服务指南
  • Java 注解与反射实战:自定义日志与参数校验注解实现
  • 常见 AIGC 降重工具评测与推荐
  • 基于 TypeScript 的 Pretext 引擎突破前端文本测量瓶颈
  • Codex Multi-Agent 与 Claude Agent Teams 架构对比与选型指南
  • Java 微服务架构设计模式:云原生分布式系统实战
  • 利用大模型构造数据集并微调模型实战
  • 数据结构:顺序表详解与模拟实现
  • WebMCP:浏览器原生 AI 交互新范式
  • SpringBoot+Vue 语言考试信息报名系统管理平台
  • Python 行为树编程实战技巧:构建智能决策系统
  • RTX50 系列显卡与 CUDA、PyTorch 及 Python 版本兼容性指南
  • 智能家居中的边缘计算与隐私保护
  • Linux 文件本质揭秘:从磁盘文件到一切皆文件
  • Libvio.link 爬虫技术原理与工作流程解析
  • Python 住宅代理自动化采集音乐数据实战

相关免费在线工具

  • 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