【网络】【Linux】多路转接技术

Linux 中的多路转接技术(IO 多路复用 / I/O Multiplexing) 是高并发网络服务器最核心的技术手段之一。

它解决的核心问题:一个线程/进程如何高效地同时“监视”大量 socket(文件描述符),在其中任意一个或多个“就绪”(可读、可写、异常)时才去处理,而不是每个 socket 都单独开线程或反复轮询。

Linux 目前主流提供了三种实现方式:select → poll → epoll(从老到新、从差到好)。

一、核心概念对比表(2025-2026 视角)

特性selectpollepoll (主流)
系统调用select()poll()epoll_create1() + epoll_ctl() + epoll_wait()
最大文件描述符数量通常 1024(FD_SETSIZE)无硬限(取决于内存)无硬限(通常几十万)
内核-用户态拷贝开销每次调用拷贝整个 fd_set(O(n))每次拷贝整个 pollfd 数组(O(n))只拷贝就绪事件(O(1) 均摊)
就绪事件返回方式修改传入的 fd_set修改 pollfd.revents返回独立的事件链表(epoll_event 数组)
是否支持边缘触发水平触发(LT)水平触发(LT)支持 LT / ET(边缘触发)
是否支持 O(1) 获取就绪数否(需遍历位图)否(需遍历数组)是(只返回就绪个数)
性能(1w 连接,100 活跃)★☆☆☆☆★★☆☆☆★★★★★
跨平台性极好(POSIX)好(POSIX)仅 Linux
当前主流使用场景遗留系统、小型服务中型服务、跨平台需求Nginx、Redis、libevent、Go runtime 等
引入内核版本很早较早Linux 2.5.44(2002)

二、三者底层原理与实现差异

1. select(最古老、最简单)

工作流程

  1. 用户把关心的 fd 集合放入三个 fd_set(读、写、异常)
  2. 调用 select(nfds, &rfds, &wfds, &efds, &timeout)
  3. 内核遍历所有 fd,检查是否就绪 → 修改 fd_set 位图
  4. 返回就绪个数,用户自己遍历 fd_set 找哪些位置为1

致命缺点

  • 每次调用都要把整个 fd 集合从用户态 → 内核态拷贝(O(n))
  • 内核每次都要线性扫描整个集合(O(n))
  • fd 上限 1024(位图大小固定,可改但不推荐)
  • 返回后用户还需再次遍历找就绪 fd(O(n))

2. poll(select 的改进版)

改进点

  • 用 pollfd 数组代替 fd_set:{fd, events, revents}
  • 没有 1024 限制(只受内存限制)
  • 事件和结果分离(events 传入,revents 返回)

仍存在的缺点

  • 每次调用仍需把整个 pollfd 数组拷贝到内核(O(n))
  • 内核仍需遍历整个数组检查(O(n))
  • 返回后用户仍需遍历数组找 revents != 0 的 fd(O(n))

3. epoll(目前 Linux 高并发的事实标准)

革命性设计事件驱动 + 内核维护就绪链表

三大核心函数

// 1. 创建 epoll 实例(红黑树 + 就绪链表)
int epoll_fd = epoll_create1(0);   // 或 epoll_create(size) 已废弃

// 2. 注册/修改/删除 监控的 fd 和事件
int epoll_ctl(epoll_fd, EPOLL_CTL_ADD/DEL/MOD, fd, &event);

// event 示例
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;     // 可读 + 边缘触发
ev.data.fd = client_fd;            // 或 ev.data.ptr = 自定义结构体
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);

// 3. 等待就绪事件(阻塞或超时)
int n = epoll_wait(epoll_fd, events, maxevents, timeout);
// events 数组中就是已经就绪的 fd 和事件

关键优势

  • 红黑树存储所有监控 fd(增删改 O(log n))
  • 内核回调机制:当 fd 就绪时,内核主动把事件加入就绪链表(callback 机制)
  • epoll_wait 只返回就绪的 fd(O(1) 均摊获取就绪数)
  • 支持边缘触发(ET) vs 水平触发(LT)
  • LT(默认):只要缓冲区有数据就一直通知(类似 select/poll)
  • ET:只在状态变化时通知一次(高性能,但需一次性读完)

ET 模式经典写法(非阻塞 + 循环读写):

while (1) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for (int i = 0; i < n; i++) {
        int fd = events[i].data.fd;
        if (events[i].events & EPOLLIN) {
            char buf[1024];
            while (1) {  // 必须循环读到 EAGAIN
                int len = read(fd, buf, sizeof(buf));
                if (len <= 0) {
                    if (errno == EAGAIN || errno == EWOULDBLOCK) break;
                    // 错误处理
                }
                // 处理数据
            }
        }
    }
}

三、实际选型建议(2025-2026)

场景推荐选择理由简述
连接数 < 1000,活跃连接少select / poll实现最简单,跨平台
跨平台需求(Linux + BSD + Windows)poll比 select 更灵活,无 1024 限制
高并发服务器(1w+ 连接)epoll性能碾压,Nginx/Redis 标配
需要边缘触发 + 极致性能epoll ET减少唤醒次数,但代码复杂度高
想跨平台又要高性能libevent / libev / io_uring封装了 epoll/kqueue 等

一句话总结

select 和 poll 已经过时,除非你有跨平台强需求,否则现代 Linux 高并发网络服务一律首选 epoll

如果你想看 epoll 的完整服务器示例代码(C语言)、与 io_uring 的对比、ET vs LT 的详细实验、或者 Nginx 是如何用 epoll 的,都可以直接告诉我,我再给你展开更具体的代码和分析。

文章已创建 4026

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部