inotify是linux内核原生的轻量级文件监控机制,需手动递归添加子目录watch、按event->len解析事件、避免缓冲区溢出,并用inotify_init1(in_cloexec)防fd泄漏。

Linux 下用 inotify 实现文件变更实时通知
inotify 是 Linux 内核原生支持的轻量级文件系统事件监控机制,比轮询高效得多,且能精确捕获 IN_MODIFY、IN_CREATE、IN_DELETE 等细粒度事件。它不依赖第三方库,只需系统头文件和标准 C++(C++11 足够)。
常见错误是直接监听目录却忽略子目录递归——inotify_add_watch 默认不递归,需对每个子目录单独添加 watch;另一个坑是读取 inotify_event 时未按 len 字段截断,导致读到后续乱码或崩溃。
- 使用
inotify_init1(IN_CLOEXEC)创建实例,避免 fork 后 fd 泄漏 - 对目标路径调用
inotify_add_watch(fd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_TO),注意返回值为 watch descriptor(wd),非 0 才有效 - 用
read()一次性读满缓冲区(建议 4096 字节),再用reinterpret_cast<const inotify_event>(buf)</const>遍历解析,每次偏移 +=sizeof(inotify_event) + event->len - 若需监控整个目录树,先用
std::filesystem::recursive_directory_iterator(C++17)遍历所有子目录,逐个inotify_add_watch
Windows 上用 ReadDirectoryChangesW 避免句柄泄漏
Windows 没有 inotify 对等物,ReadDirectoryChangesW 是唯一可靠方案,但它基于异步 I/O,容易因忘记调用 CancelIoEx 或重复 CloseHandle 导致句柄泄露甚至蓝屏(尤其在服务进程里)。
它不返回具体修改了哪行内容,只告诉你“某个文件被写入”或“重命名”,且必须搭配 OVERLAPPED 和事件/完成端口使用——同步模式(NULL overlapped)会阻塞线程,不适合长期运行程序。
- 创建监听句柄时用
CreateFileW(path.c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr) - 分配固定大小缓冲区(如 8192 字节),每次调用前重置
OVERLAPPED.hEvent,并用WaitForSingleObject等待事件触发 - 解析返回数据时,必须用
FILE_NOTIFY_INFORMATION结构体链式遍历:从缓冲区起始地址开始,每次跳转NextEntryOffset,直到该字段为 0 - 每次成功读取后,必须重新发起一次
ReadDirectoryChangesW调用,否则监听会中断
跨平台封装要注意事件丢失与缓冲区溢出
inotify 和 ReadDirectoryChangesW 都可能丢事件:inotify 缓冲区满时内核会丢弃新事件并设置 IN_Q_OVERFLOW;Windows 的缓冲区溢出则直接失败并返回 ERROR_NOTIFY_ENUM_DIR。两者都不保证 100% 可靠,尤其是高频率小文件写入场景(如日志轮转、编译输出)。
不要试图在回调里做耗时操作(比如解析 JSON 或上传网络),应把事件推入队列由工作线程处理;更关键的是,必须定期检查是否发生溢出,并触发全量扫描兜底——比如记录上次扫描时间戳,一旦发现溢出就用 std::filesystem::last_write_time 对比所有文件。
- inotify 建议设缓冲区大小为
fcntl(fd, F_SETPIPE_SZ, 1048576)(1MB),但需 root 权限才能突破默认限制 - Windows 缓冲区至少 4KB,但频繁小变更建议 64KB 起步,否则极易溢出
- 所有事件结构体中的文件名都是宽字符(Windows)或原始字节(Linux),不可直接当 C 字符串用,需显式加
\0截断 - 监控符号链接时,inotify 默认跟踪目标文件;Windows 则默认跟踪链接本身,需用
FILE_FLAG_OPEN_REPARSE_POINT控制
C++17 std::filesystem 不适合实时监控
std::filesystem 没有监听能力,它只是封装了 stat/fstat 等系统调用,任何“用 last_write_time 定时轮询”的做法都属于伪实时——延迟取决于轮询间隔,CPU 占用高,且无法区分 chmod 和 write 这类语义差异。
有人试图用 std::filesystem::directory_iterator 配合 std::this_thread::sleep_for 实现“轻量监控”,这在开发机上可能凑合,但在容器环境或高负载服务器上,两次轮询间可能错过数十次变更,且无法响应 IN_MOVED_FROM 这类原子操作事件。
- 轮询最小间隔别低于 100ms,否则大量
stat系统调用会拖慢整个存储栈 - 若真要用轮询,至少缓存 inode+size+mtime 三元组,避免仅靠时间戳误判(NFS/VM 共享盘时间不同步)
- 绝对不要在主线程里轮询并 sleep,应放到独立
std::thread并设std::this_thread::yield()防止抢占式调度失衡
unlink 后依然存在,但 wd 不再有效;Windows 句柄在目录被删除后立即失效,且不会自动重建。监控逻辑必须能感知路径消失并主动重注册,而不是静默失败。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











