土法炼钢兴趣小组的算法知识备份

文件与管道 (Files & Pipes)

目录

在 Unix 哲学中,“一切皆文件”。但在 I/O 多路复用的世界里,文件和文件是不一样的。Socket、Pipe、TTY 和普通磁盘文件在 Libevent 中的待遇天差地别。

1. 管道 (Pipe) 与 FIFO

管道(匿名管道 pipe 或命名管道 mkfifo)在内核中是有缓冲区的。 * 支持度: 完美支持。 * 机制: 它们表现得就像 Socket 一样。当缓冲区有数据时可读,有空间时可写。 * 用法: 直接使用 event_newbufferevent_socket_new 即可。

int fds[2];
pipe(fds);
// 监听读端
struct event *ev = event_new(base, fds[0], EV_READ | EV_PERSIST, read_cb, NULL);
event_add(ev, NULL);

2. 普通磁盘文件 (Regular Files)

这是新手最容易踩的坑:你不能用 epoll 去监听一个普通文件(如 /var/log/syslog)。

2.1. 为什么 epoll 不支持?

epoll(以及 select, poll)的设计初衷是监控“可能会阻塞”的 I/O。 对于普通磁盘文件,Linux 内核认为它总是就绪的(Always Ready)。 * 读: 即使数据不在 Page Cache 中,内核也会发起磁盘 I/O 并挂起进程,但这被视为“阻塞读”,而不是“未就绪”。 * 写: 只要不超过磁盘配额,写入总是立即成功的(写入 Page Cache)。

如果你把一个文件 fd 加入 epoll,epoll_wait立即返回,导致你的 Event Loop 变成 100% CPU 的死循环(Busy Loop)。

2.2. 解决方案

既然不能用 Reactor 模式处理文件 I/O,我们该怎么办?

方案 A: 阻塞 I/O (简单粗暴)

如果文件读写量很小(如读取配置文件),直接在 Event Loop 中阻塞读取也无伤大雅。但如果是大文件,绝对禁止。

方案 B: 线程池 (Thread Pool)

这是最通用的做法。 1. 主线程将文件读写任务投递给工作线程池。 2. 工作线程阻塞读写文件。 3. 完成后,通过管道或 event_active 通知主线程。

方案 C: 零拷贝发送 (evbuffer_add_file)

如果你只是想把文件发送给网络对端(如 HTTP Server),Libevent 提供了 evbuffer_add_file。 它利用 sendfile 系统调用,在内核层面完成“磁盘 -> 网卡”的数据传输,完全不占用 Event Loop 的 CPU 时间。

3. 文件变更监控

除了读写内容,我们有时还需要监控文件元数据的变化(如文件被修改、删除)。

3.1. Linux: inotify

Linux 提供了 inotify 机制。inotify_init 返回的是一个文件描述符。 惊喜: 这个 fd 是支持 epoll 的! 所以,你可以把 inotify 的 fd 注册到 Libevent 中,当文件发生变化时,回调函数会被触发。

3.2. BSD/macOS: kqueue

kqueue 原生支持 EVFILT_VNODE,可以直接监控文件系统的变化,无需额外的 fd。

4. 总结


上一篇: 03-events/signal.md - 信号处理 下一篇: 04-architecture/threading.md - 线程安全与锁

返回 Libevent 专题索引


By .