Linux 软件编程(十三)网络编程:TCP 并发服务器模型与 IO 多路复用机制、原理epoll

一、TCP 并发服务器核心模型

(一)多进程模型

  • 原理 :服务端通过 fork() 创建子进程,父进程专注监听新连接,子进程独立处理客户端交互。利用进程的独立性,实现多客户端并行处理。
  • 优缺点
    • 优点:进程资源隔离性强,一个子进程异常通常不会牵连其他进程,安全性高。
    • 缺点:进程创建、切换涉及独立地址空间的分配与销毁,资源开销大,并发量高时性能易受影响。

(二)多线程模型

  • 原理 :借助 pthread_create() 创建子线程,主线程负责 accept 新连接,子线程处理客户端数据收发。线程共享进程地址空间,轻量化实现并发。
  • 优缺点
    • 优点:相较于进程,线程创建、切换成本低,相同资源下可支撑更高并发量。
    • 缺点:线程共享资源,需通过互斥锁等同步机制避免竞争,否则易引发数据混乱,增加开发复杂度。

(三)线程池模型

  • 背景 :多线程 / 多进程模型中,频繁创建、销毁线程 / 进程会产生大量时间消耗。线程池基于 生产者 - 消费者模式任务队列 优化,预先创建固定数量线程,复用线程处理任务,减少资源开销。
  • 流程
    • 生产者(主线程)accept 客户端连接,将任务(如请求处理)放入队列。
    • 消费者(线程池线程):从队列取任务执行,循环复用,无需频繁创建销毁。

二、IO 多路复用

IO 多路复用让 单个进程 / 线程 可同时监测、处理 多个文件描述符(fd) 的读写事件,无需为每个 fd 单独创建进程 / 线程,核心解决阻塞 IO 导致的效率问题。主流实现有 selectpollepoll

(一)select 机制

  • 实现逻辑

    1. 位图(数组) 存储待监测的 fd 集合,最多支持 1024 个 fd (系统限制 )。
    2. 需反复在 应用层与内核层拷贝 fd 集合 ,内核监测事件后,应用层需 遍历集合 找触发事件的 fd 。
    3. 仅支持 水平触发(LT) :只要 fd 有未处理数据,就持续触发事件,易导致重复处理。
  • 关键函数

    复制代码
    // 清空/操作 fd 集合
    void FD_ZERO(fd_set *set);  
    void FD_SET(int fd, fd_set *set);  
    int FD_ISSET(int fd, fd_set *set);  
    
    // 核心监测函数
    int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout);
  • 特点总结:实现简单但效率有限,受限于 fd 数量与反复拷贝、遍历的开销,适合低并发场景。

(二)poll 机制

  • 实现逻辑

    1. 链表 存储 fd 集合,突破 1024 个 fd 的数量限制。
    2. 仍需 应用层与内核层反复拷贝 fd 数据 ,且需遍历链表判断触发事件的 fd 。
    3. 仅支持 水平触发(LT) ,未解决根本性能瓶颈。
  • 关键函数

    复制代码
    struct pollfd {
        int   fd;         // 监测的文件描述符
        short events;     // 关注的事件(如 POLLIN 读事件)
        short revents;    // 实际触发的事件
    };
    
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 特点总结:解决了 fd 数量限制,但核心性能问题(拷贝、遍历)未优化,适合中等并发但对性能要求不极致的场景。

(三)epoll 机制

epoll 是 Linux 下高性能 IO 多路复用方案,专为高并发设计,从存储、数据交互、事件触发全链路优化。

1. 核心优势
  • 存储优化 :用 红黑树(二叉搜索树) 存储 fd 集合,无数量限制,且查找效率高(O (logN) )。
  • 数据交互优化 :fd 集合直接 创建在内核层 ,避免应用层与内核层反复拷贝,降低资源消耗。
  • 事件触发优化 :支持 水平触发(LT)边沿触发(ET)
    • LT(默认):类似 select/poll,有未处理数据持续触发。
    • ET(高速模式):仅在数据状态变化时触发(如新增数据),减少重复事件,提升效率。
  • 结果处理优化epoll_wait 直接返回 触发事件的 fd 列表 ,无需遍历所有 fd ,处理更高效。
2. 关键函数与流程

epoll 使用分三步:创建集合 → 操作集合 → 监测事件

  1. 创建文件描述符集合 : int epoll_create(int size);

  2. 添加关注的文件描述符:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  3. epoll通知内核开始进行事件监测 :int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

  4. epoll返回时,获取到达事件的结果

  5. 根据到达事件做任务处理

  • 创建 epoll 集合

    int epoll_create(int size);

    功能:通知内核创建文件描述符集合

    参数:

    size:监测的文件描述符个数

    返回值:

    成功:文件描述符(代表内核创建的集合)

    失败:-1

  • 操作 fd 集合(添加、修改、删除)

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    功能:对epoll的文件描述符集合进行操作

    参数:

    epfd:创建的epoll集合

    op:对文件描述符集合进行的操作

    EPOLL_CTL_ADD : 添加文件描述符到集合

    EPOLL_CTL_MOD : 修改集合中的文件描述符

    EPOLL_CTL_DEL :删除集合中的文件描述符

    fd:要操作的文件描述符

    event:文件描述符对应的事件

    typedef union epoll_data {

    void *ptr;

    int fd;

    uint32_t u32;

    uint64_t u64;

    } epoll_data_t;

    struct epoll_event {

    uint32_t events; /* Epoll events */

    epoll_data_t data; /* User data variable */

    };

    events:文件描述符的事件:

    EPOLLIN: 读事件

    EOPLLOUT:写事件

    data.fd : 关注的文件描述符

    返回值:

    成功:0

    失败:-1

  • 监测事件

int epoll_wait(int epfd, struct epoll_event *events,

int maxevents, int timeout);

功能:通知内核开始监测文件描述符的事件

参数:

epfd:监测的文件描述符集合

events:保存返回的到达事件的结果(数组)

struct epoll_event evs[MAX_FD_CNT];

evs;

maxevents:最大的事件个数

timeout:监测的超时时间

-1 :不设置超时(一直阻塞)

返回值:

成功:到达的事件的个数

失败:-1

3. 工作模式对比(LT vs ET)
  • 水平触发(LT):只要 fd 有数据未读,就持续触发事件。优点是简单可靠,缺点是可能重复处理,适合对实时性要求不高的场景。
  • 边沿触发(ET):仅在数据状态变化时触发(如从无到有、从少到多)。需一次性读完 fd 数据,否则后续不会触发,效率更高,适合高并发、低延迟场景,但编程时需更严谨处理数据读取。

三、技术选型与应用场景

技术 优点 缺点 适用场景
多进程 安全性高,进程隔离 资源开销大,并发量受限 低并发、对稳定性要求高场景
多线程 轻量,并发量高于多进程 需处理线程同步,易引发资源竞争 中等并发、逻辑简单场景
线程池 优化线程资源,减少创建销毁开销 增加代码复杂度 高并发、任务重复性高场景
select 实现简单,跨平台性好 fd 数量受限,拷贝 / 遍历开销大 低并发、简单网络应用
poll 突破 fd 数量限制 仍需拷贝 / 遍历,效率一般 中等并发、无严格性能要求场景
epoll 高性能,支持高并发、边沿触发 仅支持 Linux 平台,编程稍复杂 高并发服务器(如 Web 服务)

四、总结

TCP 并发服务器的实现,从多进程、多线程的基础模型,到线程池的优化,再到 IO 多路复用的高效处理,本质是在 资源开销并发能力 间寻找平衡。selectpoll 适合简单场景,而 epoll 凭借红黑树存储、内核层集合、边沿触发等特性,成为高并发服务器的首选。