Linux用inotify可高效捕获目录变更,需注意递归监控、事件可靠性(IN_MOVED_TO优于IN_CREATE)、缓冲区溢出及IN_IGNORED重监听;Windows用ReadDirectoryChangesW需手动解析链表、处理重命名配对、避免静默失败;备份前须校验文件稳定性与打开权限,增量同步依赖inode/mtime等元数据持久化,冲突需人工介入,耗时操作须异步处理。

Linux 下用 inotify 实现目录变更捕获
inotify 是 Linux 内核提供的轻量级文件系统事件通知机制,比轮询高效得多,且能精确到 IN_CREATE、IN_MOVED_TO、IN_DELETE 等细粒度事件。直接调用 inotify_init1() 创建实例,再用 inotify_add_watch() 监控目标路径(注意:只监控单层目录,子目录需递归添加)。
常见坑:IN_MOVED_TO 和 IN_CREATE 都可能触发新文件,但前者更可靠(避免编辑器先写临时文件再 rename 的漏报);IN_IGNORED 事件说明 watch 被自动移除(比如被监控目录被 mv 或 rm),必须重新 add_watch;缓冲区大小默认是 8KB,大量并发小文件操作时容易丢事件,建议用 IN_CLOEXEC | IN_NONBLOCK 初始化,并循环读 read() 直到 EAGAIN。
示例关键片段:
int fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); int wd = inotify_add_watch(fd, "/path/to/monitored", IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM);
Windows 下用 ReadDirectoryChangesW 替代方案
Windows 没有 inotify,得用 ReadDirectoryChangesW() + I/O Completion Port(IOCP)或重叠 I/O。它不支持递归监控,必须为每个子目录单独创建 FindFirstChangeNotification() 或调用 ReadDirectoryChangesW() 并传入 TRUE 的 bWatchSubtree 参数(注意:该参数在远程 SMB 路径上会被忽略)。
容易踩的坑:ReadDirectoryChangesW() 返回的缓冲区内容是变长结构体 FILE_NOTIFY_INFORMATION 链表,必须手动遍历(靠 NextEntryOffset 字段跳转),不能当普通数组处理;事件类型如 FILE_ACTION_ADDED、FILE_ACTION_RENAMED_NEW_NAME 需和旧名配对才能还原完整 rename 行为;调用前必须确保目录句柄以 FILE_LIST_DIRECTORY 权限打开,否则静默失败。
跨平台封装时,别硬套 inotify 的“wd”概念——Windows 没有 watch descriptor,只有单个句柄对应一个监控流。
备份动作要避开“正在写入”的文件
直接 copy() 或 std::filesystem::copy_file() 正在被写入的文件,可能导致备份损坏(尤其是数据库日志、大型视频剪辑等)。正确做法是:收到事件后,先用 stat() / GetFileAttributesEx() 检查 mtime 是否稳定(连续两次间隔 100ms 的检查结果一致),再尝试以只读+共享读方式打开文件(Linux 用 open(..., O_RDONLY | O_NOFOLLOW),Windows 用 CreateFile(..., FILE_SHARE_READ | FILE_SHARE_WRITE))。
若打开失败(EBUSY / ERROR_SHARING_VIOLATION),说明文件正被独占写入,应跳过本次备份,等待下一次事件;对于频繁追加的日志文件,可考虑用 lseek() + read() 读取末尾固定字节数做增量快照,而非全量拷贝。
不要依赖文件扩展名判断是否可备份——.tmp 文件可能是编辑器临时写入,也可能是合法中间产物;真正可靠的依据是文件是否可被只读打开且无写锁。
增量同步与冲突处理的实际约束
实时备份不是“复制即完成”,必须维护本地状态(比如用 SQLite 存每文件的 inode + mtime + size 或 Windows 的 FILE_ID_INFO),否则无法区分“文件 A 被删后重建”和“文件 A 被修改”。SQLite 表结构建议至少含:path TEXT PRIMARY KEY, ino INTEGER, mtime INTEGER, size INTEGER, hash BLOB(小文件可存 SHA256 前 8 字节作快速比对)。
冲突场景真实存在:用户在监控目录中手动 mv a.txt b.txt,同时备份进程刚把 a.txt 同步走,此时 b.txt 是全新 inode;又或者两个进程同时写同一文件(如日志轮转脚本 vs 应用自身)。应对策略不是“智能合并”,而是记录冲突并暂停该路径同步,人工介入。日志里必须明确写出冲突路径、事件类型、当前 inode 和上次记录 inode。
最后提醒:不要在 inotify/ReadDirectoryChangesW 回调里直接做耗时备份操作(如压缩、加密、网络上传),必须扔进线程池或消息队列;否则事件积压会导致内核缓冲区溢出,永久丢失变更。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











