当前位置: 首页 > news >正文

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 凭借红黑树存储、内核层集合、边沿触发等特性,成为高并发服务器的首选。

http://www.xdnf.cn/news/1381267.html

相关文章:

  • 工业机器人如何通过Modbus TCP转CanOpen网关高效通信!
  • HTML贪吃蛇游戏实现
  • RAW API 的 TCP 总结2
  • 鸿蒙Harmony-从零开始构建类似于安卓GreenDao的ORM数据库(四)
  • 刷题日记0828
  • 未来模型会转向多模态吗
  • Logstash数据迁移之mysql-to-kafka.conf详细配置
  • 领悟8种常见的设计模式
  • 导入文件允许合并表格
  • HBase Compaction HFile 可见性和并发安全性分析
  • audioMAE模型代码分析
  • 流程控制语句(3)
  • 帕萨特盘式制动器cad+设计说明书
  • 【C语言16天强化训练】从基础入门到进阶:Day 13
  • week5-[一维数组]归并
  • 公共字段自动填充
  • 云计算学习100天-第29天
  • 基于SamOut的音频Token序列生成模型训练指南
  • Linux shell getopts 解析命令行参数
  • 算力沸腾时代,如何保持“冷静”?国鑫液冷SY4108G-G4解锁AI服务器的“绿色空调”!
  • 使用Rag 命中用户feedback提升triage agent 准确率
  • Elasticsearch数据迁移方案深度对比:三种方法的优劣分析
  • linu 网络 :TCP粘包及UDP
  • 【C++】C++11的右值引用和移动语义
  • STAGEWISE实战指南:从集成到使用的完整解决方案
  • vscode pyqt5设置
  • 【ai编辑器】使用cursor-vip获得cursor的pro版 pro plan(mac)
  • uniapp vue3 canvas实现手写签名
  • Flask测试平台开发,登陆重构
  • (二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵