最轻量方案是Linux用inotify(内核原生,无需第三方库)、Windows用ReadDirectoryChangesW(避免轮询);需注意事件覆盖(如IN_MOVED_TO)、绝对路径、缓冲区大小、跨平台语义差异及调试验证。

Linux 下用 inotify 监控文件夹增删事件最轻量
inotify 是内核原生支持的机制,不依赖第三方库,inotify_init() + inotify_add_watch() 就能监听 IN_CREATE 和 IN_DELETE 两类事件。glibc 已封装好,C++ 直接 #include 即可,不用引入 Boost 或 Qt。
常见错误是只监听 IN_CREATE 却漏掉 IN_MOVED_TO:很多编辑器(如 VS Code、vim)保存文件时用的是“写临时文件 + rename”方式,触发的是移动事件而非创建事件。
- 必须同时监听
IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM才能覆盖真实场景 -
inotify_add_watch()的第二个参数是路径,不能是相对路径(会静默失败),必须用realpath()转成绝对路径 - 单次
read()可能返回多个事件,需循环解析struct inotify_event,不能假设一次只一个 - 事件缓冲区大小默认很小(
8192字节),高频操作下容易丢事件;建议malloc(64 * 1024)并检查read()返回值是否为 -1 且errno == EAGAIN
Windows 上用 ReadDirectoryChangesW 避免轮询开销
Windows 没有 inotify 对等物,ReadDirectoryChangesW() 是唯一靠谱的异步通知方案。它需要 HANDLE、重叠 I/O 和单独线程处理完成包,比 Linux 复杂,但比每秒 FindFirstFile 轮询强十倍。
典型坑是把 FILE_NOTIFY_CHANGE_FILE_NAME 和 FILE_NOTIFY_CHANGE_DIR_NAME 混用:前者只报文件名变化(新建/删除文件),后者才报子目录变动(新建/删除子目录)。监控文件夹内容,两个标志要 | 运算一起传。
- 回调函数里拿到的
FILE_NOTIFY_INFORMATION是变长结构,NextEntryOffset为 0 表示末尾,不能硬写 for 循环次数 - 必须用
GetOverlappedResult()或 IOCP 获取结果,直接WaitForSingleObject()容易假死 - 路径是宽字符,
FileNameLength单位是字节而非字符数,转std::string前得先用WideCharToMultiByte() - 该 API 不递归监听子目录,要监控整个树,得对每个子目录单独调用
CreateFile()+ReadDirectoryChangesW()
跨平台封装要注意事件语义差异
Linux inotify 和 Windows ReadDirectoryChangesW 对“删除”的定义不同:inotify 在 unlink() 后立刻发 IN_DELETE,而 Windows 在文件句柄全关闭后才通知——如果某个程序正打开着被删的文件,事件会延迟甚至丢失。
另一个关键差异是“新增”事件的粒度:IN_CREATE 和 FILE_ACTION_ADDED 都只报告文件名出现,不保证内容已写完。实际业务中常需配合 stat() 或 GetFileSize() 判断文件是否稳定(比如大小不再增长)。
- 不要在事件回调里直接读新文件——可能读到截断或乱码,加个
usleep(10000)或用inotifywait --format '%w%f' -e moved_to,create做简单验证 - inotify 的 watch 数量受
/proc/sys/fs/inotify/max_user_watches限制,默认 8192,监控深目录树前记得echo 524288 > /proc/sys/fs/inotify/max_user_watches - Windows 的
ReadDirectoryChangesW()缓冲区太小会返回ERROR_INSUFFICIENT_BUFFER,需动态扩容重试,不能忽略
调试时快速验证事件是否发出
别急着写 C++ 代码,先用系统工具确认内核/OS 层事件确实产生,省去 70% 白忙活。
Linux 下跑 inotifywait -m -e create,delete,move ./target_dir(来自 inotify-tools 包),然后 touch a.txt; rm a.txt,看终端是否实时打印。Windows 下用 PowerShell 的 Get-ChildItem -Recurse | ForEach-Object { $_.FullName } 对比前后快照虽土但有效。
- 如果
inotifywait不输出,检查目标路径是否挂载为 noatime 或 tmpfs(部分嵌入式环境不支持) - Windows 上若 PowerShell 脚本发现不了变化,先运行
dir /s /b看是否真有文件,再确认 Explorer 是否卡在刷新状态(它会独占某些句柄) - 所有测试务必在目标用户权限下进行:root 创建的文件,普通用户进程可能收不到 inotify 事件;管理员启动的 PowerShell,非管理员 C++ 程序也看不到其触发的变更
事件监听本身不难,难的是边界情况——文件正在被写入、杀进程没关句柄、符号链接绕过监控、容器挂载点行为异常。这些地方没日志、不复现、难定位,得靠提前埋点和对比系统工具输出来卡住问题源头。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











