Linux专题十:I/O 复用进阶(LT/ET 模式)同步,异步阻塞,以及 libevent 库核心知识点

一、epoll 的 LT 与 ET 模式(核心区别与实现)

1.核心定义与本质差异

epoll 支持两种事件触发模式,核心差异在于 "事件通知的时机",直接影响高并发场景下的性能和编程复杂度:

触发模式 核心特性 触发逻辑(以可读事件为例)
LT(水平触发) 1. epoll 默认模式,开发简单,无数据漏读风险;2. 支持阻塞 / 非阻塞 socket;3. 适配对性能要求不高、追求开发效率的场景 只要 socket 接收缓冲区有未读数据,每次调用epoll_wait都会持续触发 "可读事件",直到数据全部读完;示例:缓冲区收到 10 字节,仅读 5 字节,下次epoll_wait仍会通知 "可读"
ET(边缘触发) 1. 需手动设置EPOLLET标志启用;2. 仅支持非阻塞 socket,编程复杂;3. 高并发、高性能场景首选(如 Nginx 默认模式) 仅在 socket 状态发生 "突变" 的瞬间触发一次事件;示例:缓冲区从 "空→有数据""满→未满" 时触发一次,后续即使有未读数据,也不再通知,除非有新数据写入

2. 关键技术细节

(1)ET 模式的核心要求(避免数据漏读)
  • 必须将 socket 设为非阻塞 (防止recv()/read()阻塞在无数据时);
  • 事件触发后,需循环读取数据 ,直到recv()返回EAGAINEWOULDBLOCK(表示缓冲区无更多数据),确保一次性读完所有数据。
(2**)模式设置方法(epoll_event 结构体)**
cpp 复制代码
struct epoll_event ev;
ev.data.fd = conn_fd;
ev.events = EPOLLIN | EPOLLET; // 启用ET模式(默认LT,仅需加EPOLLET)
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
(3)ET 模式循环读数据示例(非阻塞 socket)
cpp 复制代码
// 前提:conn_fd已设为非阻塞
void handle_et_read(int conn_fd) {
    char buf[1024] = {0};
    while (1) {
        ssize_t len = recv(conn_fd, buf, sizeof(buf)-1, 0);
        if (len > 0) {
            // 处理读取到的数据
            printf("ET模式读取数据:%s\n", buf);
            memset(buf, 0, sizeof(buf));
        } else if (len == 0) {
            // 客户端关闭连接
            printf("客户端断开连接\n");
            epoll_ctl(epfd, EPOLL_CTL_DEL, conn_fd, NULL);
            close(conn_fd);
            break;
        } else {
            // 区分真错误和"无数据"(EAGAIN/EWOULDBLOCK)
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 缓冲区无更多数据,退出循环
                break;
            } else {
                // 真错误,关闭连接
                perror("recv失败");
                epoll_ctl(epfd, EPOLL_CTL_DEL, conn_fd, NULL);
                close(conn_fd);
                break;
            }
        }
    }
}

3**. LT 与 ET 模式对比总结**

对比维度 LT 模式 ET 模式
通知频率 持续通知(只要有未处理事件) 仅一次通知(状态突变瞬间)
socket 要求 支持阻塞 / 非阻塞 仅支持非阻塞
编程复杂度 低(无需循环读,框架自动提醒) 高(需手动设非阻塞 + 循环读)
性能 较低(无效通知多,系统调用频繁) 高(减少通知次数,降低系统开销)
数据漏读风险 有(未循环读则漏读,需严格按规范实现)
适用场景 中小规模连接、快速开发(如内部工具、简单服务器) 大规模高并发(如互联网服务器、高吞吐场景)

二、select/poll/epoll 底层实现与性能对比

1. 底层核心差异

特性 select poll epoll(Linux)
内核存储结构 位图(fd_set) 数组(struct pollfd) 红黑树(存储监控 FD)+ 就绪链表(存储就绪 FD)
FD 拷贝方式 每次select()调用都需将fd_set拷贝到内核 每次poll()调用都需将pollfd数组拷贝到内核 epoll_ctl()添加 FD 时拷贝一次,后续无需拷贝
内核实现方式 轮询(遍历所有监控 FD,时间复杂度 O (n)) 轮询(遍历所有监控 FD,时间复杂度 O (n)) 事件驱动(注册回调函数,FD 就绪时自动加入就绪链表,时间复杂度 O (1))
就绪 FD 查找 需用户层遍历所有 FD(O (n)) 需用户层遍历所有监控 FD(O (n)) 内核直接返回就绪链表,用户层无需遍历(O (1))
触发模式 仅 LT 模式 仅 LT 模式 支持 LT(默认)和 ET 模式

2. 性能结论

  • 低并发(FD<1024):三者性能差异不大,select/poll 更易跨平台;
  • 高并发(FD>10000):epoll 性能碾压 select/poll,核心优势是 "无 FD 拷贝 + 事件驱动 + O (1) 就绪查找"。

三、同步 / 异步、阻塞 / 非阻塞

一、先明确两个核心维度的定义

同步 / 异步、阻塞 / 非阻塞是操作系统和编程中描述 I/O 操作、任务执行模式的核心概念 ,两者属于不同的维度(同步异步关注任务结果的通知方式 ,阻塞非阻塞关注任务执行时的等待状态),但经常结合使用(如同步阻塞、异步非阻塞)。

关键前提:同步 / 异步与阻塞 / 非阻塞并无直接关联,属于两个完全独立的维度。

1. 阻塞(Blocking)vs 非阻塞(Non-Blocking)

这是描述任务执行时,调用方是否需要等待操作完成的维度,关注的是 **"等待状态"**。

  • 阻塞:调用方发起操作后,必须等待操作完全完成才能继续执行后续代码,等待期间进程 / 线程会被挂起(CPU 不分配时间片,资源释放给其他进程 / 线程)。
  • 非阻塞:调用方发起操作后,无需等待操作完成,立即返回结果(成功 / 失败 / 未完成),等待期间进程 / 线程可正常执行其他任务,不会被挂起。
2. 同步(Synchronous)vs 异步(Asynchronous)

这是描述操作完成后,结果的通知方式的维度,关注的是 **"结果如何返回"**。

  • 同步:调用方发起操作后,需要主动等待 / 查询操作结果(无论是否阻塞),结果的获取由调用方主动发起、主动掌控。
  • 异步:调用方发起操作后,无需主动等待 / 查询结果,操作完成后由操作系统 / 框架 / 第三方通过回调、信号、消息通知等方式主动告知调用方结果,结果的传递由被调用方触发、被动接收。

核心口诀:同步 = 主动要结果,异步 = 被动收通知。

二、四个组合场景的实例解析

同步 / 异步与阻塞 / 非阻塞可组合为四种常见的 I/O 模型,通过 **"去食堂打饭"** 的生活实例(类比程序的 I/O 操作,如读取文件、网络通信、数据库查询)来通俗理解,同时搭配对应程序示例。

1. 同步阻塞(最常见的基础模式)
  • 生活场景:你走到食堂窗口点饭,然后站在窗口前一直等待打饭阿姨做完饭,期间不做任何其他事情,直到拿到饭才离开窗口,继续后续行程(如回教室、去餐厅)。

  • 程序类比 :C 语言的read()/write()系统调用读取普通文件、accept()阻塞等待客户端连接、recv()阻塞等待网络数据,都是典型的同步阻塞 I/O。

    cpp 复制代码
    // 同步阻塞读文件:调用read后,线程被内核挂起,直到数据读取完成才返回
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int main() {
        int fd = open("test.txt", O_RDONLY);
        if (fd == -1) {
            perror("open失败");
            return 1;
        }
    
        char buf[1024] = {0};
        // 阻塞调用:数据未就绪时,线程挂起,CPU释放,直到读取完成或出错
        ssize_t len = read(fd, buf, sizeof(buf)-1);
        if (len > 0) {
            printf("读取完成:%s\n", buf);
        } else {
            perror("read失败");
        }
    
        close(fd);
        return 0;
    }
  • 核心特点:实现简单、开发成本低,无需处理复杂的状态查询和错误判断;但等待期间线程挂起,无法处理其他任务,并发性能差,CPU 资源利用率低,适合简单场景(如小文件读取、低并发服务)。

2. 同步非阻塞(轮询模式)

  • 生活场景:你走到食堂窗口点饭,阿姨说 "饭还没好,你先别在这等",你没有站在窗口前原地等待,而是回到座位刷手机、和同学聊天(执行其他任务),每隔 1 分钟主动去窗口问一次 "我的饭好了吗?",直到阿姨告知饭已做好,拿到饭为止。
  • 程序类比:将文件描述符(FD)设为非阻塞模式后,循环调用read()/recv(),直到读取到有效数据,期间可处理其他任务。
cpp 复制代码
// 同步非阻塞读文件:调用read后立即返回,数据未就绪时循环轮询
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

// 设置文件描述符为非阻塞模式
void setnonblock(int fd) {
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl F_GETFL失败");
        return;
    }
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL失败");
    }
}

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open失败");
        return 1;
    }

    setnonblock(fd); // 转为非阻塞模式
    char buf[1024] = {0};
    while (1) {
        // 非阻塞调用:数据未就绪时立即返回-1,errno设为EAGAIN/EWOULDBLOCK
        ssize_t len = read(fd, buf, sizeof(buf)-1);
        if (len > 0) {
            printf("读取完成:%s\n", buf);
            break;
        } else if (len == 0) {
            printf("文件读取完毕\n");
            break;
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 数据未就绪,执行其他任务(模拟业务处理)
                printf("数据未就绪,先处理其他任务...\n");
                sleep(1); // 模拟轮询间隔,避免频繁查询占用CPU
            } else {
                perror("read失败");
                break;
            }
        }
    }

    close(fd);
    return 0;
}
  • 核心特点:等待期间线程不挂起,可执行其他任务,CPU 资源利用率较同步阻塞更高;但存在轮询开销(频繁查询操作状态),若轮询间隔过短会占用大量 CPU 资源,间隔过长会导致结果获取延迟,适合中等并发、对延迟要求不高的场景。

3. 异步阻塞(逻辑可行,实际极少使用)

  • 生活场景:你让同学帮你去食堂打饭(发起异步操作,由第三方执行),然后你坐在教室座位上一动不动地等待同学回来,期间不刷手机、不学习,什么事情都不做,直到同学把饭送到你手上,才继续后续行动。
  • 程序类比:调用 Linux 异步 I/O(AIO)接口aio_read()后,阻塞调用aio_wait()等待异步操作完成通知,期间线程挂起,无法处理其他任务。
  • 核心特点:操作本身是异步的(由内核 / 第三方执行),但调用方仍阻塞等待结果,失去了异步操作 "解放调用方" 的核心优势,既没有同步阻塞的简单性,也没有异步非阻塞的高性能,实际开发中几乎不使用,仅存在于逻辑组合中。
cpp 复制代码
// libevent实现异步非阻塞读文件:注册事件后无需等待,就绪后回调通知
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <event2/event.h>

// 读事件回调函数(操作完成后,由libevent主动调用,传递结果)
void read_callback(evutil_socket_t fd, short events, void *arg) {
    char buf[1024] = {0};
    ssize_t len = read(fd, buf, sizeof(buf)-1);
    if (len > 0) {
        printf("读取完成:%s\n", buf);
    } else if (len == 0) {
        printf("文件读取完毕\n");
    } else {
        perror("read失败");
    }

    // 退出事件循环
    struct event_base *base = (struct event_base*)arg;
    event_base_loopexit(base, NULL);
}

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open失败");
        return 1;
    }

    // 1. 创建事件反应堆(自动适配底层epoll/select/poll)
    struct event_base *base = event_base_new();
    if (base == NULL) {
        printf("event_base创建失败\n");
        close(fd);
        return 1;
    }

    // 2. 创建非阻塞读事件(注册回调函数,持久化触发)
    struct event *read_ev = event_new(base, fd, EV_READ | EV_PERSIST, read_callback, base);
    if (read_ev == NULL) {
        printf("event创建失败\n");
        event_base_free(base);
        close(fd);
        return 1;
    }

    // 3. 添加事件到反应堆,无需阻塞等待,直接进入事件循环
    event_add(read_ev, NULL);
    printf("事件注册完成,进入事件循环(可处理其他任务)...\n");
    event_base_dispatch(base); // 事件循环,阻塞等待就绪事件,但不阻塞单个I/O操作

    // 4. 释放资源
    event_free(read_ev);
    event_base_free(base);
    close(fd);
    return 0;
}

4. 异步非阻塞(高性能 I/O 的核心模式)

  • 生活场景:你给食堂阿姨留了手机号,点完饭后直接回教室专心学习(执行其他核心任务),无需等待、无需主动询问,阿姨做好饭后主动给你打电话 / 发消息通知你取饭,你收到通知后再去食堂拿饭,全程不耽误自身核心任务的执行。
  • 程序类比:Linux 的epoll+ 异步 I/O、libevent 事件驱动框架、Node.js 回调机制、Java NIO 的 Reactor 模式,都是典型的异步非阻塞 I/O。
cpp 复制代码
// libevent实现异步非阻塞读文件:注册事件后无需等待,就绪后回调通知
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <event2/event.h>

// 读事件回调函数(操作完成后,由libevent主动调用,传递结果)
void read_callback(evutil_socket_t fd, short events, void *arg) {
    char buf[1024] = {0};
    ssize_t len = read(fd, buf, sizeof(buf)-1);
    if (len > 0) {
        printf("读取完成:%s\n", buf);
    } else if (len == 0) {
        printf("文件读取完毕\n");
    } else {
        perror("read失败");
    }

    // 退出事件循环
    struct event_base *base = (struct event_base*)arg;
    event_base_loopexit(base, NULL);
}

int main() {
    int fd = open("test.txt", O_RDONLY);
    if (fd == -1) {
        perror("open失败");
        return 1;
    }

    // 1. 创建事件反应堆(自动适配底层epoll/select/poll)
    struct event_base *base = event_base_new();
    if (base == NULL) {
        printf("event_base创建失败\n");
        close(fd);
        return 1;
    }

    // 2. 创建非阻塞读事件(注册回调函数,持久化触发)
    struct event *read_ev = event_new(base, fd, EV_READ | EV_PERSIST, read_callback, base);
    if (read_ev == NULL) {
        printf("event创建失败\n");
        event_base_free(base);
        close(fd);
        return 1;
    }

    // 3. 添加事件到反应堆,无需阻塞等待,直接进入事件循环
    event_add(read_ev, NULL);
    printf("事件注册完成,进入事件循环(可处理其他任务)...\n");
    event_base_dispatch(base); // 事件循环,阻塞等待就绪事件,但不阻塞单个I/O操作

    // 4. 释放资源
    event_free(read_ev);
    event_base_free(base);
    close(fd);
    return 0;
}
  • 核心特点:调用方发起操作后无需等待、无需主动轮询,可全力执行其他核心任务;操作完成后由内核 / 框架主动回调通知结果,无无效等待和轮询开销,CPU 资源利用率最高,是高并发、高吞吐网络编程的首选模式(如 Nginx、Redis、Node.js 的底层 I/O 模型);缺点是开发复杂度高,需要处理回调嵌套、事件调度、资源管理等问题。

三、核心区别与关键对比表

1. 同步 vs 异步(核心:结果获取方式)
对比项 同步(Synchronous) 异步(Asynchronous)
核心逻辑 调用方主动获取 / 查询结果 被调用方主动通知 / 回调传递结果
等待方式 主动等待 / 轮询 被动接收,无需主动等待
结果掌控方 调用方(主动掌控) 被调用方(内核 / 框架 / 第三方,被动接收)
开发复杂度 低(无需处理回调 / 通知) 高(需处理回调嵌套、事件调度、异常处理)
并发性能 低 - 中(高并发下存在轮询 / 阻塞瓶颈) 高(无无效等待,充分利用 CPU 资源)
典型场景 小文件读取、低并发服务、简单工具类程序 高并发 Web 服务器、音视频传输、大数据处理
2. 阻塞 vs 非阻塞(核心:等待时的线程状态)
对比项 阻塞(Blocking) 非阻塞(Non-Blocking)
核心逻辑 操作未完成时,线程被挂起(暂停执行) 操作未完成时,线程持续运行(立即返回)
CPU 资源占用 等待期间不占用(线程挂起,释放 CPU) 等待期间可占用(线程运行,执行其他任务)
返回结果时机 仅当操作完全完成 / 出错时返回 发起操作后立即返回(无论操作是否完成)
错误处理 简单(仅处理操作本身的错误) 复杂(需区分 "真错误" 和 "操作未就绪")
典型接口 普通read()/accept()/recv() 非阻塞read()/recv()epoll_wait()
适用场景 简单场景、对并发无要求的程序 中等及以上并发、需要高效利用 CPU 的程序
3. 四种组合模型综合对比表
组合模型 核心特点 优点 缺点 典型适用场景
同步阻塞 主动等结果,等待时线程挂起 实现简单、开发成本低、无额外开销 并发性能差、CPU 利用率低、无法处理多任务 小文件读取、低并发服务、内部工具类程序
同步非阻塞 主动查结果,等待时线程可执行其他任务 较同步阻塞并发性能提升、CPU 利用率更高 存在轮询开销、延迟不可控、开发复杂度中等 中等并发、对延迟要求不高的后台服务
异步阻塞 被动收结果,等待时线程挂起 操作异步执行,无需主动轮询 失去异步核心优势、并发性能无提升、极少使用 几乎无实际应用场景,仅存在逻辑组合中
异步非阻塞 被动收结果,等待时线程可执行其他任务 并发性能最高、CPU 利用率最优、无无效等待 开发复杂度高、需处理回调 / 事件调度、调试难 高并发 Web 服务器、音视频传输、大数据处理
4. 维度交叉总结表
概念维度 核心关注点 阻塞(等待时线程挂起) 非阻塞(等待时线程可执行其他任务)
同步 调用方主动获取结果 同步阻塞(食堂窗口原地等饭) 同步非阻塞(食堂窗口轮询问饭)
异步 被调用方主动通知结果 异步阻塞(等同学带饭,原地发呆) 异步非阻塞(等食堂阿姨打电话通知取饭)

四、常见误区纠正

  1. 误区一:"同步就是阻塞,异步就是非阻塞"纠正:两者是完全独立的维度,同步可搭配非阻塞(轮询),异步也可搭配阻塞(极少用),不能直接划等号。

  2. 误区二:"非阻塞一定比阻塞好,异步一定比同步优"纠正:没有绝对的优劣,需结合场景选择。简单场景下(如小文件读取),同步阻塞的实现成本更低,性能差异可忽略;高并发场景下,异步非阻塞才是最优解,避免过度设计。

  3. 误区三 :"异步非阻塞就是多线程"纠正:异步非阻塞是事件驱动模型,可在单线程中实现高并发(如 Node.js、单线程 + epoll);多线程是另一种并发模式,通过线程切换处理多任务,两者实现逻辑不同,可结合使用(如多线程 + epoll),但并非同一概念。

  4. 误区四:"非阻塞 I/O 不会等待"纠正:非阻塞 I/O 只是 "不挂起线程的等待",并非 "无需等待操作完成"。同步非阻塞仍需主动轮询等待操作完成,异步非阻塞只是将等待过程交给内核 / 框架,调用方无需感知而已。

五、核心总结

  1. 两个核心维度:同步 / 异步关注结果如何返回(主动 / 被动),阻塞 / 非阻塞关注等待时线程状态(挂起 / 运行);
  2. 四种组合模型:同步阻塞(简单)、同步非阻塞(轮询)、异步阻塞(极少用)、异步非阻塞(高性能);
  3. 选型原则:简单场景选同步阻塞,中等并发选同步非阻塞,高并发高吞吐选异步非阻塞;
  4. 核心目标:理解不同模型的本质,根据业务场景和性能要求选择合适的 I/O 模型,平衡开发效率和系统性能。

四、errno 与错误处理(系统调用必备)

1. 核心概念

  • errno :全局变量(声明于<errno.h>),用于存储系统调用 / 库函数的错误原因(错误码);
  • 适用场景open()/read()/epoll_wait()等系统调用执行失败时,会自动设置 errno,程序员通过 errno 判断具体错误类型。

2. 常见错误码与含义

错误码宏定义 含义说明 典型场景
EAGAIN/EWOULDBLOCK 非阻塞 I/O 无数据可读写(临时错误,可重试) ET 模式下recv()无更多数据、非阻塞accept()无新连接
EINTR 系统调用被信号中断(如epoll_wait()ctrl+c中断) select()/epoll_wait()阻塞时收到信号
EBADF 无效的文件描述符(FD 未打开或已关闭) 用已关闭的 FD 调用recv()/epoll_ctl()
EINVAL 参数无效(如epoll_create()传入负数、epoll_wait()maxevents=0) 函数参数不符合要求

3. 错误处理示例

cpp 复制代码
int fd = open("test.txt", O_RDONLY);
if (fd == -1) {
    // 根据errno打印具体错误
    if (errno == ENOENT) {
        printf("错误:文件不存在\n");
    } else if (errno == EACCES) {
        printf("错误:权限不足\n");
    } else {
        perror("open失败"); // perror自动结合errno打印错误信息
    }
    exit(1);
}

五、libevent 库详解(I/O 复用封装神器)

1. 核心定位

libevent 是一个跨平台、高性能的事件驱动网络库,封装了 select/poll/epoll(自动适配底层系统),实现了 Reactor(反应堆)模式,让开发者无需关注底层 I/O 复用细节,专注于业务逻辑。

2. Reactor 模式(反应堆模式)

  • 本质:事件多路分发器,核心是 "事件注册→事件监控→事件触发→回调处理" 的闭环;
  • 核心组件
    1. event_base:反应堆核心,管理事件循环、I/O 复用机制、定时器等;
    2. event:事件对象(可对应 I/O 事件、信号事件、定时器事件);
    3. 回调函数:事件触发时执行的业务逻辑函数。

3. 事件的二进制位编码原理

  • 每个事件类型对应一个唯一的二进制位(位掩码),通过 "按位或(|)" 组合多个事件;

  • 示例(libevent 事件宏定义):

    cpp 复制代码
    #define EV_READ  0x01  // 二进制00000001(第0位):可读事件
    #define EV_WRITE 0x02  // 二进制00000010(第1位):可写事件
    #define EV_SIGNAL 0x04 // 二进制00000100(第2位):信号事件
    #define EV_TIMEOUT 0x08 // 二进制00001000(第3位):定时器事件
    #define EV_PERSIST 0x10 // 二进制00010000(第4位):持久化事件(触发后不自动删除)
  • 组合事件:EV_READ | EV_PERSIST(0x11)表示 "持续监控可读事件"。

4. libevent 核心函数(按使用流程)

函数名 功能说明 示例
event_base_new() 创建反应堆核心对象(event_base),自动适配底层 I/O 复用(epoll/select) struct event_base *base = event_base_new();
event_new() 创建事件对象(支持 I/O、信号、定时器事件) `struct event *ev = event_new(base, fd, EV_READ EV_PERSIST, callback, NULL);`
evtimer_new() 快捷创建定时器事件(简化event_new,无需手动设EV_TIMEOUT struct event *timer_ev = evtimer_new(base, timeout_cb, NULL);
event_add() 将事件添加到反应堆,注册到事件循环(可设置超时时间) struct timeval tv={5,0}; event_add(timer_ev, &tv);(5 秒定时器)
event_base_dispatch() 启动事件循环(阻塞监控事件),触发后自动调用回调函数,循环直至事件全部删除或主动退出 event_base_dispatch(base);
event_free() 释放事件对象内存(包含event_del,自动从反应堆移除事件) event_free(ev);
event_base_free() 释放反应堆核心对象内存 event_base_free(base);

5. 回调函数规范

  • 固定格式(返回值void,参数固定 3 个):

    cpp 复制代码
    void callback(int fd, short events, void *arg) {
        // fd:事件对应的文件描述符(信号事件为信号值)
        // events:触发的事件类型(如EV_READ、EV_TIMEOUT)
        // arg:event_new()传入的自定义参数
    }

    6. 完整示例(libevent 实现多事件监控)

    功能:同时监控 "标准输入可读""SIGINT 信号(ctrl+c)""5 秒定时器"
    cpp 复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <event2/event.h>
    #include <signal.h>
    
    // 标准输入可读事件回调
    void stdin_cb(int fd, short events, void *arg) {
        char buf[128] = {0};
        ssize_t len = read(fd, buf, sizeof(buf)-1);
        if (len > 0) {
            buf[len-1] = '\0';
            printf("标准输入:%s\n", buf);
            if (strcmp(buf, "quit") == 0) {
                // 退出事件循环
                event_base_loopexit((struct event_base*)arg, NULL);
            }
        }
    }
    
    // SIGINT信号回调(ctrl+c)
    void sigint_cb(int sig, short events, void *arg) {
        printf("收到信号SIGINT(%d),准备退出\n", sig);
        event_base_loopbreak((struct event_base*)arg);
    }
    
    // 定时器回调(5秒触发一次,持久化)
    void timeout_cb(int fd, short events, void *arg) {
        printf("定时器触发:5秒到了\n");
    }
    
    int main() {
        // 1. 创建反应堆核心
        struct event_base *base = event_base_new();
        if (base == NULL) {
            printf("event_base创建失败\n");
            return 1;
        }
    
        // 2. 创建标准输入事件(FD=0,可读+持久化)
        struct event *stdin_ev = event_new(base, 0, EV_READ | EV_PERSIST, stdin_cb, base);
        event_add(stdin_ev, NULL); // 无超时时间
    
        // 3. 创建SIGINT信号事件(信号2,持久化)
        struct event *sig_ev = event_new(base, SIGINT, EV_SIGNAL | EV_PERSIST, sigint_cb, base);
        event_add(sig_ev, NULL);
    
        // 4. 创建定时器事件(5秒,持久化)
        struct event *timer_ev = evtimer_new(base, timeout_cb, NULL);
        struct timeval tv = {5, 0}; // 5秒
        event_add(timer_ev, &tv);
    
        // 5. 启动事件循环(阻塞)
        printf("事件循环启动,按ctrl+c或输入quit退出\n");
        event_base_dispatch(base);
    
        // 6. 释放资源
        event_free(stdin_ev);
        event_free(sig_ev);
        event_free(timer_ev);
        event_base_free(base);
        return 0;
    }
    编译与运行(需安装 libevent 库)
    cpp 复制代码
    # 安装libevent(Ubuntu)
    sudo apt-get install libevent-dev
    
    # 编译(链接libevent库)
    gcc libevent_demo.c -o libevent_demo -levent
    
    # 运行
    ./libevent_demo

    7. libevent 核心优势

  • 跨平台:自动适配 Linux(epoll)、Windows(select)、BSD(kqueue),无需修改代码;

  • 简化开发:封装底层 I/O 复用、事件循环、定时器,开发者仅需关注回调逻辑;

一句话来说就是封装了这三个i/o复用的工具,然后我们要使用i/o服用的话直接使用libenevt里面的库函数就可以了,不需要自己去思考用哪个工具合适。

六、关键补充:超时时间的统一理解

  • 定义:程序等待事件的最大阻塞时长,类比 "等待闹钟"------ 闹钟响前事件触发则处理,超时无事件则结束等待;
  • 适用场景select()/poll()/epoll_wait()/event_add()均支持设置超时时间;
  • 参数格式
    • 系统调用:struct timeval { long tv_sec; long tv_usec; }(秒 + 微秒);
    • libevent:event_add()直接传入struct timevalevtimer_new()简化为直接传时长。
  • 高性能:底层优先使用 epoll 等高效 I/O 复用机制,性能接近原生实现;
  • 支持多事件类型:统一管理 I/O 事件、信号事件、定时器事件,无需单独处理。
相关推荐
掘根2 小时前
【消息队列项目】服务器实现
运维·服务器
菩提小狗2 小时前
第1天:基础入门-操作系统&名词&文件下载&反弹SHELL&防火墙绕过|小迪安全笔记|网络安全|
网络·笔记·学习·安全·web安全
wniuniu_2 小时前
ceph修改
网络·ceph
想做后端的小C2 小时前
Linux:期末考点
linux·运维·服务器
我可以将你更新哟2 小时前
【linux】配置 Docker 国内镜像源, centos7安装docker-ce,docker相关命令,永久配置 DNS
linux·运维·docker
jimy12 小时前
本地下载vscode server安装包(tar.gz)然后上传至服务器开发机
服务器·ide·vscode
代码游侠2 小时前
复习——网络测试工具
linux·开发语言·网络·笔记·学习·测试工具
qq_406176142 小时前
JavaScript的同步与异步
前端·网络·tcp/ip·ajax·okhttp
colus_SEU2 小时前
【计算机网络笔记】第三章 传输层
网络·笔记·计算机网络