计算机网络 之【高级IO】(IO模型、select\poll\epoll)

目录

[1.五种 I/O 模型](#1.五种 I/O 模型)

[2.非阻塞 I/O 与 fcntl 详解](#2.非阻塞 I/O 与 fcntl 详解)

2.1.fcntl

(4)SetNoBlock

(5)非阻塞错误码

3.select

[3.1.fd_set 与 FD_XXX 宏](#3.1.fd_set 与 FD_XXX 宏)

[3.2.select 参数详解](#3.2.select 参数详解)

3.3.select的优缺点

3.4.简易selectServer实现的细节:

4.poll

4.1.poll参数详解

[poll 和 select 的核心区别](#poll 和 select 的核心区别)

4.2.poll的改进与局限

4.3.简易pollServer实现的细节:

5.epoll

[5.1.epoll 的三个核心接口](#5.1.epoll 的三个核心接口)

5.2.epoll原理

[5.3.LT(水平触发)与 ET(边缘触发)](#5.3.LT(水平触发)与 ET(边缘触发))

[5.4.关于 EPOLLOUT 的按需设置](#5.4.关于 EPOLLOUT 的按需设置)

5.5.简易epollServer实现的细节:


1.五种 I/O 模型

I/O = 等待 + 拷贝,提高 I/O 效率的本质是降低等待的比重

模型 是否同步 等待方式 拷贝参与 效率关键点
阻塞 I/O 同步 进程阻塞等待内核数据就绪 进程参与拷贝 等待时 CPU 无法处理其他任务
非阻塞 I/O 同步 轮询检查数据是否就绪(不阻塞) 进程参与拷贝 轮询浪费 CPU,但可同时处理多路
信号驱动 I/O 同步 数据就绪时内核发信号通知 进程参与拷贝 等待不占用 CPU,但信号处理复杂
多路复用 I/O 同步 select/poll/epoll 统一等待 进程参与拷贝 单线程监听多个 fd,效率高
异步 I/O 异步 内核完成等待 + 拷贝后通知 内核完成全部工作 进程仅发起请求,完全不参与 I/O
  • **同步IO:**进程主动等待或轮询,并参与数据拷贝阶段
  • **异步IO:**进程发起请求后立即返回,内核做完所有事情后再通知进程
  • 阻塞 vs 非阻塞 :等待数据就绪时进程是否挂起(多路复用也是阻塞),数据拷贝阶段 效率一致,等待数据就绪阶段,阻塞IO让出CPU(高效),非阻塞IO忙轮询(低效)

在同步 I/O 范畴内,多路复用(尤其是 epoll)因为可以单执行流管理大量连接且只通知就绪 fd,效率最高。但异步 I/O 理论上更高效,只是编程模型复杂,只有遇到明确性能瓶颈且确定是I/O模型导致的,才值得考虑切换到异步 I/O(了解)

2.非阻塞 I/O 与 fcntl 详解

2.1.fcntl

fcntl (file control) 是 POSIX 系统中用于对已打开的文件描述符进行各种控制操作的系统调用

(1)函数原型

复制代码
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */);
  • 功能:修改已打开文件描述符的属性
  • 常见 cmd:F_GETFL:获取当前文件状态标志(返回值即标志位图,失败返回 -1)F_SETFL:设置文件状态标志(如 O_NONBLOCK、O_APPEND)
  • 返回值:fcntl 的返回值取决于具体的 cmd 命令(位图或者整数)

(2)主要功能分类

  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

(3)文件状态标志

标志 说明 示例
O_RDONLY 0 只读 不可修改
O_WRONLY 1 只写 不可修改
O_RDWR 2 读写 不可修改
O_APPEND 0x2000 追加模式 每次写追加到末尾
O_NONBLOCK 0x4000 非阻塞模式 无数据时立即返回
O_ASYNC 0x2000 (不同系统) 异步 I/O 数据就绪发送信号
O_SYNC 0x1000 同步写 等待数据落盘

(4)SetNoBlock

获取/设置文件状态标记, 将一个文件描述符设置为非阻塞

复制代码
int SetNoBlock(int fd) {
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0) {
        perror("fcntl F_GETFL");
        return -1;
    }
    
    if (fcntl(fd, F_SETFL, fl | O_NONBLOCK) < 0) {
        perror("fcntl F_SETFL");
        return -1;
    }
    
    return 0;
}

(5)非阻塞错误码

设置O_NONBLOCK后,当底层数据未就绪时:

操作 返回值 errno 说明
读(read/recv) -1 EAGAINEWOULDBLOCK(值相同) 无数据可读
写(write/send) -1 EAGAINEWOULDBLOCK(都判断确保可移植性) 发送缓冲区满

这不是真正的错误,而是**"资源临时不可用",需要稍后重试**

复制代码
#include <errno.h>
#include <unistd.h>

ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
        // 数据未就绪,不是错误
        // 等待 epoll/select 通知可读
        return 0;  // 或 -2 表示需要重试
    }
    // 真正的错误
    perror("read error");
    return -1;
}
// 成功读取 n 字节

3.select

3.1.fd_set 与 FD_XXX 宏

fd_set 本质是一个位图,在内核中以 unsigned long 数组实现

**位图大小:**由 FD_SETSIZE 宏定义(通常 1024),因此单个 select 能监听的最大 fd 编号为 1023

  • 操作宏
原型 功能
FD_ZERO FD_ZERO(fd_set *set) 清空集合(所有位清零)
FD_SET FD_SET(int fd, fd_set *set) 将 fd 加入集合(对应位置1)
FD_CLR FD_CLR(int fd, fd_set *set) 将 fd 从集合移除(对应位置0)
FD_ISSET FD_ISSET(int fd, fd_set *set) 检查 fd 是否在集合中(0=不在集合中)

3.2.select 参数详解

select 是 I/O 多路复用函数,用于监视多个文件描述符的状态变化

复制代码
           struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
  • nfds:监听的最大 fd 值 +1,内核据此缩小遍历范围
  • timeout:输入输出型参数,为NULL时阻塞等待;tv_sec = tv_usec = 0时非阻塞轮询;否则就等待指定时间,返回时会被更新为剩余时间,可能需要周期重复设置
  • fd_set 也是输入输出型参数:输入时表示关心的 fd ,返回时表示就绪的 fd因此每次调用前必须重新设置
返回值 含义 说明
> 0 就绪的文件描述符数量 有多少个 fd 发生了事件
0 超时 在指定时间内没有 fd 就绪
-1 错误 调用失败,查看 errno

3.3.select的优缺点

(1)优点:

  • 单进程引入 select 后 ,进程不再被迫在某个慢速连接上傻等,而是由 select 统一监控所有连接,内核会精确唤醒进程并告知哪个 fd 先就绪,程序随即优先处理该事件并立即返回,从而在单线程内达成了并发的效果------即哪个客户端先发数据就先服务谁,完全解除了由于某个连接阻塞导致其他连接饥饿的耦合关系
  • 单进程 select 实现的是I/O 多路复用式并发 ------它通过内核事件通知让单个线程在多个连接之间高效切换,属于并发模型的一种 ,只是它不依赖多线程。而多线程是另一种并发模型,它通过多个执行流来承载并发,在多核 CPU 上还能进一步实现并行

(2)缺点

  • fd 数量有上限(可重新编译内核修改,但效率下降)
  • 每次调用需拷贝 fd 集合到内核,返回时再拷贝回来
  • 内核采用线性扫描所有监听的 fd,效率随 fd 数量线性下降
  • 用户态需要遍历整个数组找出就绪 fd

3.4.简易selectServer实现的细节:

gitee.com/dongfanqi/homework6/commit/3c29645059401652d7bbae1c40b29ccee26a87e2

(1)listenfd 在 selec中监听的是读事件,新连接到来等价于读事件就绪;必须先通过 select 检测到 listenfd 可读后再调用 accept,否则 accept 在无连接时会阻塞(阻塞模式)或返回 EAGAIN(非阻塞模式)

"新连接到达"在语义上等同于"监听 socket 有新的数据/对象可被读取"
(2)select 采用水平触发(LT)模式:只要 fd 上还有未处理的数据/事件,select 就会持续通知,上层可以不处理,下次 select 仍然会返回该 fd,此时调用 read/accept 等操作不会阻塞(因为数据已就绪)
(3)为了让文件描述符能在 select 循环中跨函数传递并保持完整监控列表,必须使用自定义数组来持久化存储所有需要监听的文件描述符。这是因为 select 的 fd_set 是输入输出型参数,每次调用后只会保留就绪的 fd,导致原始监听集合丢失,因此需要借助数组充当"全量备份"------在每轮循环前遍历数组重建 fd_set,调用 select 等待事件,事后再遍历数组找出就绪的 fd 进行处理
(4)在使用 select 实现 I/O 多路复用时,必须遍历自定义数组来找出当前最大的文件描述符值,因为 select 的第一个参数 nfds 要求传入"最大文件描述符 + 1"以限定内核的监听范围。具体做法是先将 max_fd 初始化为监听套接字,然后在将每个有效客户端 fd 加入 fd_set 的同一循环中,逐一比较并更新 max_fd,确保 select 能够覆盖数组中所有需要监控的描述符
(5)每次 accept 获取的新连接 fd 绝不能立即阻塞读写,必须存入自定义数组的空闲槽位完成"注册",随后交由 select 统一托管等待;这是因为新连接刚建立时内核接收缓冲区极可能是空的,若此时直接调用 recv,会导致单执行流服务端瞬间卡死,丧失处理其他并发连接的能力
(6)select 返回后仅能告知文件描述符"可读",但无法区分这种"可读"究竟是监听套接字上的连接就绪(需调用 accept 接纳新客户端)还是普通客户端套接字上的数据就绪(需调用 recv 读取内容),因此必须在主循环中引入一个事件派发器的逻辑:通过判断 遍历自定义数组 与 FD_ISSET判断,决定当前 fd 是否为监听 fd,若是则执行注册逻辑将新连接存入数组,若否则执行读取逻辑

4.poll

4.1.poll参数详解

复制代码
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 参数

    struct pollfd {
    int fd; // 要监控的文件描述符
    short events; // 关心的事件(输入参数)
    short revents; // 实际发生的事件(输出参数,由内核填充)
    };

参数 含义
fds struct pollfd 数组首地址,存放所有要监控的 fd 及事件
nfds 数组有效元素个数
timeout 超时时间(毫秒),-1 表示永久阻塞
  • 事件宏
事件宏 含义
POLLIN 数据可读(包括连接就绪)
POLLOUT 数据可写
POLLERR 发生错误
POLLHUP 连接挂起(对端关闭)

poll 和 select 的核心区别

对比项 select poll
fd 集合存储方式 位图 fd_set(固定大小 1024) 动态数组 struct pollfd[]
最大 fd 数量限制 硬限制 FD_SETSIZE(通常 1024) 无上限,只受内存限制
参数分离 输入输出共用 fd_set,需每次重建 分离设计events 是输入,revents 是输出
遍历复杂度 O(n) 但 n 上限固定 O(n),n 可以是任意大
移植性 几乎所有 Unix 系统 POSIX 标准,Windows 有 WSAPoll

4.2.poll的改进与局限

  • struct pollfd 将事件输入(events) 与事件输出(revents) 分离,无需每次重置
  • 事件类型通过宏位图定义(POLLIN、POLLOUT 等)
  • 突破了 select 的 fd 数量限制(数组大小由用户指定,内存超限属于硬件问题)
  • 缺点:内核仍需要线性扫描所有监听的 fd,数量大时效率低

4.3.简易pollServer实现的细节:

gitee.com/dongfanqi/homework6/commit/3c29645059401652d7bbae1c40b29ccee26a87e2

实现细节 select poll 共同点说明
等待机制 将当前进程/线程挂载到所有监听 fd 的等待队列 完全相同 内核睡眠等待,任意 fd 就绪时唤醒进程
内核遍历方式 线性扫描所有监听的 fd(0 ~ nfds-1 线性扫描用户传入的 pollfd 数组(0 ~ nfds-1 O(n) 时间复杂度,n 为监听 fd 总数
事件就绪检测 调用每个 fd 对应的 poll 方法(file->f_op->poll 完全相同 内核通过 VFS 层调用设备驱动的 poll 函数检查状态
用户态/内核态拷贝 select 调用时拷贝 fd_set 到内核,返回时拷贝回用户态 poll 调用时拷贝 pollfd 数组到内核,返回时拷贝回用户态 每次调用都有 O(n) 的内存拷贝开销
输入输出参数特性 fd_set 在返回时被修改(覆盖为就绪 fd 集合) pollfdrevents 字段被修改(保留 events 不变) 都需要区分"关心的事件"和"就绪的事件"
就绪事件通知 返回就绪 fd 总数,用户需遍历整个 fd_set 找出具体就绪的 fd 返回就绪 fd 总数,用户需遍历整个 pollfd 数组检查 revents 用户态二次遍历不可避免
超时精度 struct timeval(微秒级) int timeout(毫秒级) 底层均转换为内核的 jiffies 或高精度定时器

poll 在接口设计上将输入事件(events)与输出事件(revents)分离,使得用户无需在每次调用前重建监听集合 ,同时通过传入用户自定义大小的数组打破了 select 对文件描述符数量的硬性限制;但其底层内核实现与 select 几乎完全一致------依然是线性扫描所有监听的文件描述符、依然需要在用户态与内核态之间拷贝整个数组、依然需要用户层二次遍历才能定位就绪事件,因此在大量并发场景下性能瓶颈与 select 无异,最终被内核基于事件驱动回调的 epoll 所取代

5.epoll

5.1.epoll 的三个核心接口

  • epoll_create ------ 创建 epoll 实例
项目 说明
函数原型 int epoll_create(int size);
头文件 #include <sys/epoll.h>
参数 size:历史遗留参数,内核已忽略 ,但必须 > 0 (建议填 1
返回值 成功:返回 epoll 实例的文件描述符 (epfd) 失败:返回 -1,并设置 errno
核心作用 在内核中创建一个包含**红黑树(存储所有监听fd)和就绪链表(缓存已触发事件)**的eventpoll对象,并返回一个文件描述符作为后续高效事件管理的操作句柄
资源释放 使用完毕后必须 close(epfd),否则造成内核内存泄漏
现代替代 Linux 2.6.8+ 推荐使用 epoll_create1(0),支持 EPOLL_CLOEXEC 标志,避免 fork 后 fd 泄露
  • epoll_ctl ------ 控制 epoll 事件(增/删/改)
项目 说明
函数原型 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
头文件 #include <sys/epoll.h>
参数详解
epfd epoll_create 返回的 epoll 实例 fd
op 操作类型 ,取值如下: • EPOLL_CTL_ADD ------ 向 epoll 实例注册 新的 fd • EPOLL_CTL_MOD ------ 修改 已注册 fd 的监听事件 • EPOLL_CTL_DEL ------ 移除已注册的 fd
fd 要操作的目标文件描述符(监听 socket 或客户端 socket)
event 指向 struct epoll_event 的指针,指定要监听的事件类型和携带的用户数据
返回值 成功:返回 0 失败:返回 -1,并设置 errno
核心作用 对内核中的 epoll 红黑树执行增、删、改操作,相当于"告诉内核我关心这个 fd 的哪些事件"
常见错误 EEXIST(重复 ADD)、ENOENT(对不存在的 fd 执行 MOD/DEL)、EBADF(fd 无效)
  • struct epoll_event 字段说明:
字段 类型 说明
events uint32_t 监听的事件掩码,常用: • EPOLLIN ------ 数据可读 • EPOLLOUT ------ 数据可写 • EPOLLET ------ 边缘触发模式EPOLLRDHUP ------ 对端半关闭 • EPOLLERR / EPOLLHUP ------ 错误/挂起(内核自动监听)
data epoll_data_t 联合体 ,携带用户自定义数据: • data.fd ------ 存放 fd 本身 • data.ptr ------ 存放自定义结构体指针(更灵活)

epoll_event 结构体的 data 成员是一个联合体,最常用的是 data.fd,用于在事件触发时告知用户是哪个 fd 就绪了

  • epoll_wait ------ 等待事件就绪
项目 说明
函数原型 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
头文件 #include <sys/epoll.h>
参数详解
epfd epoll 实例 fd
events 输出参数 ,指向用户态数组,内核将就绪的事件拷贝到这里
maxevents 数组容量,表示一次最多能接收多少个就绪事件
timeout 超时时间(毫秒): • -1 ------ 永久阻塞,直到有事件 • 0 ------ 立即返回,不阻塞 • >0 ------ 等待指定毫秒数
返回值 >0 ------ 就绪的 fd 数量 • 0 ------ 超时,无事件 • -1 ------ 出错,检查 errno
核心作用 当有事件就绪或超时时返回,内核只将真正就绪的 fd 填入 events 数组,用户态只需遍历返回的 n 个就绪项,而无需遍历全部被监控的 fd
关键优势 内核事件通知机制是 O(1) 的(就绪事件通过回调直接入队),且返回给用户态时只拷贝实际就绪的 n 个事件,而非全部监控的 N 个;整体调用开销与监控总数 N 无关,只与就绪数 n 成正比

若就绪队列中事件数量超过 maxevents,内核只取前 maxevents 个拷贝到用户态,剩余节点保留在就绪队列中,下次 epoll_wait 继续返回

5.2.epoll原理

复制代码
epoll_create()  ──→  创建 epfd(内核红黑树 + 就绪队列)
                         │
                         ↓
epoll_ctl(ADD)   ──→  将 listen_fd 和 client_fd 挂到红黑树上
                         │
                         ↓
epoll_wait()     ──→  按时等待,内核把就绪 fd 从就绪队列拷贝到用户态数组
                         │
                         ↓
                    只遍历就绪的 n 个 fd(而不是全部监控的 fd)
  • 红黑树:存储所有监听的 fd 及其事件(epoll_ctl 增删改操作)
  • 就绪队列:双向链表,存放已就绪 fd 对应的节点
  • 读回调机制:从网卡检测到数据的那一刻起,首先网卡触发硬中断内核在硬中断处理中激活软中断 ,软中断通过回调函数 将数据包读取到内核内存并推入协议栈;随后协议栈进行数据解析处理操作,在确认数据有效负载进入Socket接收队列后,回调 Socket层的默认通知函数 ;该函数遍历Socket等待队列,执行epoll_ctl预先注册的ep_poll_callback回调,将对应的epitem结构体挂入eventpoll的就绪链表,并唤醒阻塞在epoll_wait上的用户进程,事件监测从主动轮询转变为被动通知
  • 写回调机制是发送缓冲区从满到非满的被动反向通知:当网卡完成数据发送、通过硬中断-软中断路径释放内核缓冲区空间后,协议栈触发 ep_poll_callback 将对应 epitem 挂入就绪链表并唤醒进程,从而将"能否写入"的主动轮询转化为"空间腾出即通知"的硬件驱动事件模型。

(1)epoll优点

复制代码
select/poll 模式:
用户进程 --[每次调用拷贝全量fd列表]--> 内核 --[O(N)轮询检查状态]--> 返回

epoll 模式:
用户进程 --[epoll_ctl仅拷贝一次fd]--> 内核红黑树
硬件中断 --[回调直接注入]--> 内核就绪链表
用户进程 --[epoll_wait仅从链表取数据]--> 返回 O(n)//n表示已就绪的fd个数

epoll 优点消除无效遍历 (回调机制) 与重复拷贝 (红黑树存储), 提升 fd 修改效率 (红黑树VS数组)

红黑树常驻免拷贝,链表就绪免轮询,中断回调改 O(N) 为 O(1)

  1. 事件驱动回调:硬件中断与协议栈进行事件监听操作,操作系统无需主动轮询,空闲连接不消耗 CPU
  2. O(1) 就绪检测:epoll_wait 只需检查就绪链表是否为空,时间复杂度极低
  3. O(N) 有效获取:仅需遍历实际发生事件的 N 个就绪 fd,无 select/poll 的全量遍历浪费
  4. 无上限连接:基于红黑树管理全量 fd,大小仅受系统内存限制
  5. 无重复拷贝:fd 事件数据在内核中常驻 (红黑树节点) ,用户态与内核态仅交换就绪事件列表

(2)选择红黑树而不是哈希表的原因

  • 哈希表的 O(1) 假设了完美的哈希函数、无碰撞、且内核愿意承受扩容时的延迟。而服务器 5 万个长连接同时活跃时,一次 rehash 导致的延迟可能触发服务崩溃
  • 红黑树的 O(logN) 虽然理论常数更大,但在 N=10 万时深度不超过 18 层,操作耗时方差极小。更重要的是,红黑树赋予了 epoll "无痛清理" 的能力:进程退出时,内核只需一个中序遍历就能有序、无空洞、无额外分配地销毁所有 epitem(描述 fd 及其事件的数据结构)

红黑树换来了 epoll 在任意负载形态下的"性能可预测性"

5.3.LT(水平触发)与 ET(边缘触发)

模式 通知时机 特点
LT(默认) 只要 fd 对应缓冲区有数据,epoll_wait 就会通知 编程简单,若不处理数据会反复通知,可能导致忙等
ET 仅当 fd 状态发生变化时(如无数据→有数据)通知 高效,要求非阻塞 I/O 并一次性读完数据,否则可能丢失事件
  • ET 与 LT 效率对比 :若在 LT 下也采用非阻塞 I/O 并一次性读完数据,两者效率接近。ET 的优势在于减少事件通知次数,避免无谓的用户态/内核态切换
  • LT 只读一次的情况下,ET 的通知效率更高,ET 的 IO 效率也更高,因为 ET 下程序员会把数据全部取走,tcp 就会给对方通告更大的窗口,从而在概率上让对方一次可以发送更多数据
  • ET 只通知一次,会倒逼程序员在每次通知时把本轮数据都全部取走,循环读取,直到读取出错,fd默认是阻塞的,所以 ET 模式需要将fd设置为非阻塞

边缘触发 (ET) 与 水平触发 (LT) 的底层区别

  • LT :在 epoll_wait 将 fd 返回给用户后,内核不会把该节点从"就绪队列"里彻底摘除,而是留在队列里做一个标记。下一次 epoll_wait 如果发现数据还没读完,这个 fd 依然在队列里,会再次返回。这保证了"只要还有数据,就一直通知"
  • ET :在内核回调 ep_poll_callback 后,数据加入队列。用户 epoll_wait 取走数据时,该节点直接从就绪队列移除 。如果用户没有一次性把缓冲区读空,且后续没有新数据到达 (即没有新的硬件中断触发新的回调),这个 fd再也不会被返回给用户

5.4.关于 EPOLLOUT 的按需设置

写缓冲区通常有空闲空间,若一直监听 EPOLLOUT,epoll_wait 会频繁返回,浪费 CPU

正确做法:

  1. 默认不监听 EPOLLOUT
  2. 当需要发送数据时,直接调用 write/send
  3. 若返回 EAGAIN,说明缓冲区满,此时将数据存入用户态输出缓冲区,并监听 EPOLLOUT
  4. 当 EPOLLOUT 触发,尝试继续发送输出缓冲区数据;若发送完毕,取消监听 EPOLLOUT

读事件必须常设监听以持续感知对端数据到达,而写事件必须按需设置,即仅在应用层有数据待发送且因内核缓冲区满导致 write 返回 EAGAIN 时才临时注册 EPOLLOUT,并在数据全部发出后立即移除,以防止写事件始终就绪引发 epoll_wait 空转与 CPU 满载

5.5.简易epollServer实现的细节:

gitee.com/dongfanqi/homework6/commit/3c29645059401652d7bbae1c40b29ccee26a87e2

(1)编程技巧:禁止拷贝的类,通过继承编译器自动禁止拷贝

(2)使用类封装操作,不同类进行组合,并使用智能指针管理

(3)epoll_ctl ADD 时需要 struct epoll_event 保存用户关心的 fd,便于后续通知用户

(4)epoll_ctl DEL 时确保 fd 合法:先移除,再关闭。若先执行 close(fd),内核虽会异步清理 epoll 节点,但在清理完成前该整数值 fd 可能被操作系统立即回收并分配给新创建的连接,导致 epoll 中残留的旧 epitem 节点错误地将事件注入到不相关的新连接上,造成严重的逻辑串扰或程序崩溃;只有先调用 EPOLL_CTL_DEL 将该 fd 从红黑树和等待队列中彻底摘除,才能确保后续 close 的安全性

(5)直接调用 write/send,发送数据,然后根据返回值判断缓冲区是否已满,进而设置监听

相关推荐
ShineWinsu10 天前
对于Linux:文件操作以及文件IO的解析
linux·c++·面试·笔试·io·shell·文件操作
爱码驱动13 天前
文件操作和IO
java·开发语言·io·文件操作
日拱一卒的小田16 天前
ZYNQ学习笔记1-裸机-PS端中断配置、IO配置及PS/PL AXI交互
io·zynq·中断
咬_咬16 天前
go语言学习(变量定义与输入输出)
开发语言·学习·golang·io·go语言··go变量定义
曼彻斯特的海边1 个月前
BIO、NIO、AIO
io·nio·bio·aio
高铭杰2 个月前
MySQL源码(2)同步io相关模块行为
mysql·io·sio
’长谷深风‘2 个月前
深入理解 Linux 文件 IO、目录操作与时间接口
linux·c语言·io·软件编程
半桔2 个月前
【IO多路转接】高并发服务器实战:Reactor 框架与 Epoll 机制的封装与设计逻辑
linux·运维·服务器·c++·io
c++逐梦人2 个月前
Linux基础IO
linux·操作系统·io