C++ `inotify` / `ReadDirectoryChangesW`:高效文件系统事件监控

哈喽,各位好!今天咱们来聊聊C++里文件系统事件监控这事儿,听起来高大上,其实也没那么神秘。咱们主要讲两种方法:inotify(Linux)和 ReadDirectoryChangesW(Windows)。这俩货都是为了让你写的程序能像个好奇宝宝一样,时刻盯着文件系统的动静,一有风吹草动就立马知道。

为什么要监控文件系统?

你可能会问,好端端的,为啥要盯着文件系统?理由可多了:

  • 实时同步: 比如网盘,文件一改动,立马同步到云端。
  • 构建系统:make这种工具,需要知道哪些源文件变动了,才能重新编译。
  • 安全监控: 监测恶意软件是否在偷偷修改系统文件。
  • 日志分析: 实时监控日志文件,发现异常情况立即报警。
  • 缓存失效: 缓存系统需要知道底层文件是否被修改,及时更新缓存。

总之,只要你的程序需要对文件系统的变化做出反应,那文件系统事件监控就是个必备技能。

Linux 下的 inotify

inotify 是 Linux 内核提供的一个文件系统事件通知接口。它允许你监控文件或目录,当文件或目录发生变化时,内核会通知你的程序。

inotify 的基本概念:

  • inotify 实例: 就像一个监视器,你需要先创建一个 inotify 实例才能开始监控。
  • watch 描述符 (wd): 每个被监控的文件或目录都有一个唯一的 watch 描述符,你可以通过它来标识监控对象。
  • 事件掩码: 指定你感兴趣的事件类型,比如文件被创建、删除、修改等等。

inotify 的使用步骤:

  1. 创建 inotify 实例: 使用 inotify_init() 函数创建。

    #include <iostream>
    #include <sys/inotify.h>
    #include <unistd.h> // close()
    #include <cstring>   // memset
    #include <cerrno>    // errno
    
    int main() {
        int fd = inotify_init();
        if (fd == -1) {
            std::cerr << "inotify_init failed: " << strerror(errno) << std::endl;
            return 1;
        }
    
        std::cout << "inotify instance created successfully." << std::endl;
    
        // ... 后面添加监控和处理事件的代码
    
        close(fd); // 记得关闭
        return 0;
    }
  2. 添加 watch: 使用 inotify_add_watch() 函数添加要监控的文件或目录。

    #include <iostream>
    #include <sys/inotify.h>
    #include <unistd.h> // close()
    #include <cstring>   // memset
    #include <cerrno>    // errno
    
    int main() {
        int fd = inotify_init();
        if (fd == -1) {
            std::cerr << "inotify_init failed: " << strerror(errno) << std::endl;
            return 1;
        }
    
        std::cout << "inotify instance created successfully." << std::endl;
    
        const char *path = "/tmp/watched_directory"; // 你要监控的目录
        int wd = inotify_add_watch(fd, path, IN_MODIFY | IN_CREATE | IN_DELETE);
        if (wd == -1) {
            std::cerr << "inotify_add_watch failed: " << strerror(errno) << std::endl;
            close(fd);
            return 1;
        }
    
        std::cout << "Watching " << path << " (wd = " << wd << ")" << std::endl;
    
        // ... 后面添加读取和处理事件的代码
    
        close(fd);
        return 0;
    }

    这里的 IN_MODIFY | IN_CREATE | IN_DELETE 是事件掩码,表示我们对文件修改、创建和删除事件感兴趣。 常用的事件掩码如下表:

    事件掩码 含义
    IN_ACCESS 文件被访问
    IN_MODIFY 文件被修改
    IN_ATTRIB 文件属性被修改
    IN_CLOSE_WRITE 文件被关闭(以可写方式打开)
    IN_CLOSE_NOWRITE 文件被关闭(以只读方式打开)
    IN_OPEN 文件被打开
    IN_CREATE 文件/目录被创建
    IN_DELETE 文件/目录被删除
    IN_DELETE_SELF 监控的文件/目录自身被删除
    IN_MOVE_SELF 监控的文件/目录自身被移动
    IN_MOVE_FROM 文件从监控目录移出
    IN_MOVE_TO 文件移入监控目录
    IN_ALL_EVENTS 所有事件
    IN_ONLYDIR 只监控目录
    IN_DONT_FOLLOW 不跟踪符号链接
    IN_MASK_ADD 将事件添加到现有的掩码中,而不是替换
    IN_ONESHOT 只监控一次事件,然后自动移除 watch
  3. 读取事件: 使用 read() 函数从 inotify 实例的文件描述符中读取事件。

    #include <iostream>
    #include <sys/inotify.h>
    #include <unistd.h> // close(), read()
    #include <cstring>   // memset
    #include <cerrno>    // errno
    #include <limits.h> // PATH_MAX
    
    int main() {
        int fd = inotify_init();
        if (fd == -1) {
            std::cerr << "inotify_init failed: " << strerror(errno) << std::endl;
            return 1;
        }
    
        std::cout << "inotify instance created successfully." << std::endl;
    
        const char *path = "/tmp/watched_directory"; // 你要监控的目录
        int wd = inotify_add_watch(fd, path, IN_MODIFY | IN_CREATE | IN_DELETE);
        if (wd == -1) {
            std::cerr << "inotify_add_watch failed: " << strerror(errno) << std::endl;
            close(fd);
            return 1;
        }
    
        std::cout << "Watching " << path << " (wd = " << wd << ")" << std::endl;
    
        char buffer[4096]; // 用于读取事件的缓冲区
        while (true) {
            ssize_t len = read(fd, buffer, sizeof(buffer));
            if (len == -1) {
                std::cerr << "read failed: " << strerror(errno) << std::endl;
                break;
            }
    
            // 处理读取到的事件
            char *ptr = buffer;
            while (ptr < buffer + len) {
                struct inotify_event *event = (struct inotify_event *)ptr;
    
                if (event->mask & IN_CREATE) {
                    if (event->mask & IN_ISDIR) {
                        std::cout << "Directory created: " << event->name << std::endl;
                    } else {
                        std::cout << "File created: " << event->name << std::endl;
                    }
                }
                if (event->mask & IN_DELETE) {
                    if (event->mask & IN_ISDIR) {
                        std::cout << "Directory deleted: " << event->name << std::endl;
                    } else {
                        std::cout << "File deleted: " << event->name << std::endl;
                    }
                }
                if (event->mask & IN_MODIFY) {
                    std::cout << "File modified: " << event->name << std::endl;
                }
    
                ptr += sizeof(struct inotify_event) + event->len;
            }
        }
    
        close(fd);
        return 0;
    }

    这段代码会一直循环读取事件,直到发生错误。 注意,read() 函数是阻塞的,也就是说,如果没有事件发生,它会一直等待。

  4. 处理事件: 读取到的事件信息都保存在 inotify_event 结构体中。

    struct inotify_event {
        int      wd;       /* Watch descriptor */
        uint32_t mask;     /* Mask of events */
        uint32_t cookie;   /* Cookie to synchronize two events */
        uint32_t len;      /* Length of name field */
        char     name[];    /* Optional null-terminated name */
    };
    • wd:发生事件的 watch 描述符。
    • mask:发生的事件类型。
    • name:发生事件的文件或目录名(如果事件与文件或目录关联)。
  5. 移除 watch: 使用 inotify_rm_watch() 函数移除不再需要监控的文件或目录。

    inotify_rm_watch(fd, wd);
  6. 关闭 inotify 实例: 使用 close() 函数关闭 inotify 实例。

    close(fd);

完整示例:

#include <iostream>
#include <sys/inotify.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <limits.h>

int main() {
    int fd = inotify_init();
    if (fd == -1) {
        std::cerr << "inotify_init failed: " << strerror(errno) << std::endl;
        return 1;
    }

    const char *path = "/tmp/watched_directory";
    int wd = inotify_add_watch(fd, path, IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVE);
    if (wd == -1) {
        std::cerr << "inotify_add_watch failed: " << strerror(errno) << std::endl;
        close(fd);
        return 1;
    }

    char buffer[4096];
    while (true) {
        ssize_t len = read(fd, buffer, sizeof(buffer));
        if (len == -1) {
            std::cerr << "read failed: " << strerror(errno) << std::endl;
            break;
        }

        char *ptr = buffer;
        while (ptr < buffer + len) {
            struct inotify_event *event = (struct inotify_event *)ptr;

            if (event->mask & IN_CREATE) {
                if (event->mask & IN_ISDIR) {
                    std::cout << "Directory created: " << event->name << std::endl;
                } else {
                    std::cout << "File created: " << event->name << std::endl;
                }
            }
            if (event->mask & IN_DELETE) {
                if (event->mask & IN_ISDIR) {
                    std::cout << "Directory deleted: " << event->name << std::endl;
                } else {
                    std::cout << "File deleted: " << event->name << std::endl;
                }
            }
            if (event->mask & IN_MODIFY) {
                std::cout << "File modified: " << event->name << std::endl;
            }
            if (event->mask & IN_MOVE_SELF) {
                std::cout << "Watched directory was moved or renamed." << std::endl;
                break; // 需要重新设置watch,因为wd可能已经失效
            }
            if (event->mask & IN_MOVED_FROM) {
                std::cout << "File/directory moved out: " << event->name << std::endl;
            }
            if (event->mask & IN_MOVED_TO) {
                std::cout << "File/directory moved in: " << event->name << std::endl;
            }

            ptr += sizeof(struct inotify_event) + event->len;
        }
    }

    inotify_rm_watch(fd, wd);
    close(fd);
    return 0;
}

编译和运行:

  1. 创建一个目录 /tmp/watched_directory
  2. 将代码保存为 inotify_example.cpp
  3. 使用以下命令编译:g++ inotify_example.cpp -o inotify_example
  4. 运行程序:./inotify_example

然后,你可以尝试在 /tmp/watched_directory 目录下创建、删除或修改文件,看看程序是否能正确地检测到这些事件。

inotify 的限制:

  • 最大 watch 数量: inotify 实例可以监控的最大文件和目录数量是有限制的,可以通过 /proc/sys/fs/inotify/max_user_watches 文件查看和修改。 如果watch数量超过限制,inotify_add_watch会返回ENOSPC错误。
  • 事件丢失: 如果事件发生得太快,或者程序处理事件的速度不够快,可能会导致事件丢失。
  • 递归监控: inotify 本身不支持递归监控目录,你需要自己实现递归遍历目录并添加 watch。不过,有些库(比如 Boost.Filesystem)可以简化这个过程。

Windows 下的 ReadDirectoryChangesW

在 Windows 系统上,我们可以使用 ReadDirectoryChangesW 函数来监控目录的变化。 这个函数的功能和 inotify 类似,但使用方式略有不同。

ReadDirectoryChangesW 的基本概念:

  • 目录句柄: 你需要先打开一个目录的句柄才能开始监控。
  • 缓冲区: ReadDirectoryChangesW 函数会将事件信息写入一个缓冲区,你需要自己解析这个缓冲区。
  • Overlapped I/O: ReadDirectoryChangesW 函数可以使用 Overlapped I/O 模式,实现异步监控。

ReadDirectoryChangesW 的使用步骤:

  1. 打开目录句柄: 使用 CreateFileW 函数打开要监控的目录。

    #include <iostream>
    #include <windows.h>
    
    int main() {
        LPCWSTR directoryPath = L"\\?\C:\watched_directory"; // 你要监控的目录,注意使用宽字符
    
        HANDLE hDirectory = CreateFileW(
            directoryPath,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // 需要FILE_FLAG_BACKUP_SEMANTICS才能打开目录
            NULL
        );
    
        if (hDirectory == INVALID_HANDLE_VALUE) {
            std::cerr << "CreateFileW failed: " << GetLastError() << std::endl;
            return 1;
        }
    
        std::cout << "Directory handle created successfully." << std::endl;
    
        // ... 后面添加监控和处理事件的代码
    
        CloseHandle(hDirectory); // 记得关闭句柄
        return 0;
    }

    注意:

    • CreateFileW 函数需要使用宽字符(LPCWSTR)。
    • 打开目录时需要指定 FILE_FLAG_BACKUP_SEMANTICS 标志。
    • 为了实现异步监控,需要指定 FILE_FLAG_OVERLAPPED 标志。
    • UNC 路径需要添加\\?\前缀才能正常工作,例如\\?\C:\watched_directory
  2. 调用 ReadDirectoryChangesW 使用 ReadDirectoryChangesW 函数开始监控目录。

    #include <iostream>
    #include <windows.h>
    #include <vector>
    
    int main() {
        LPCWSTR directoryPath = L"\\?\C:\watched_directory"; // 你要监控的目录,注意使用宽字符
    
        HANDLE hDirectory = CreateFileW(
            directoryPath,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // 需要FILE_FLAG_BACKUP_SEMANTICS才能打开目录
            NULL
        );
    
        if (hDirectory == INVALID_HANDLE_VALUE) {
            std::cerr << "CreateFileW failed: " << GetLastError() << std::endl;
            return 1;
        }
    
        std::cout << "Directory handle created successfully." << std::endl;
    
        DWORD bufferSize = 4096;
        std::vector<BYTE> buffer(bufferSize);
        DWORD bytesReturned;
        OVERLAPPED overlapped;
        memset(&overlapped, 0, sizeof(overlapped));
        overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建事件对象
    
        if (overlapped.hEvent == NULL) {
            std::cerr << "CreateEvent failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            return 1;
        }
    
        BOOL success = ReadDirectoryChangesW(
            hDirectory,
            buffer.data(),
            bufferSize,
            TRUE, // 是否监控子目录
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES |
            FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE |
            FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_SECURITY,
            &bytesReturned,
            &overlapped,
            NULL
        );
    
        if (!success) {
            std::cerr << "ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            CloseHandle(overlapped.hEvent);
            return 1;
        }
    
        std::cout << "Monitoring directory changes..." << std::endl;
    
        // ... 后面添加等待和处理事件的代码
    
        CloseHandle(hDirectory);
        CloseHandle(overlapped.hEvent);
        return 0;
    }
    • FILE_NOTIFY_CHANGE_FILE_NAME 等是事件掩码,表示我们对文件名称、目录名称、属性等变化感兴趣。 常用的事件掩码如下表:

      事件掩码 含义
      FILE_NOTIFY_CHANGE_FILE_NAME 文件名发生变化
      FILE_NOTIFY_CHANGE_DIR_NAME 目录名发生变化
      FILE_NOTIFY_CHANGE_ATTRIBUTES 文件属性发生变化
      FILE_NOTIFY_CHANGE_SIZE 文件大小发生变化
      FILE_NOTIFY_CHANGE_LAST_WRITE 文件最后写入时间发生变化
      FILE_NOTIFY_CHANGE_LAST_ACCESS 文件最后访问时间发生变化
      FILE_NOTIFY_CHANGE_CREATION 文件创建时间发生变化
      FILE_NOTIFY_CHANGE_SECURITY 文件的安全描述符 (ACL) 发生变化
    • TRUE 表示递归监控子目录,FALSE 表示只监控当前目录。

  3. 等待事件: 由于我们使用了 Overlapped I/O,ReadDirectoryChangesW 函数会立即返回,我们需要使用 GetOverlappedResult 或者 WaitForSingleObject 函数来等待事件发生。

    #include <iostream>
    #include <windows.h>
    #include <vector>
    
    int main() {
        LPCWSTR directoryPath = L"\\?\C:\watched_directory"; // 你要监控的目录,注意使用宽字符
    
        HANDLE hDirectory = CreateFileW(
            directoryPath,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // 需要FILE_FLAG_BACKUP_SEMANTICS才能打开目录
            NULL
        );
    
        if (hDirectory == INVALID_HANDLE_VALUE) {
            std::cerr << "CreateFileW failed: " << GetLastError() << std::endl;
            return 1;
        }
    
        std::cout << "Directory handle created successfully." << std::endl;
    
        DWORD bufferSize = 4096;
        std::vector<BYTE> buffer(bufferSize);
        DWORD bytesReturned;
        OVERLAPPED overlapped;
        memset(&overlapped, 0, sizeof(overlapped));
        overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建事件对象
    
        if (overlapped.hEvent == NULL) {
            std::cerr << "CreateEvent failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            return 1;
        }
    
        BOOL success = ReadDirectoryChangesW(
            hDirectory,
            buffer.data(),
            bufferSize,
            TRUE, // 是否监控子目录
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES |
            FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE |
            FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_SECURITY,
            &bytesReturned,
            &overlapped,
            NULL
        );
    
        if (!success) {
            std::cerr << "ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            CloseHandle(overlapped.hEvent);
            return 1;
        }
    
        std::cout << "Monitoring directory changes..." << std::endl;
    
        while (true) {
            DWORD waitResult = WaitForSingleObject(overlapped.hEvent, INFINITE); // 等待事件发生
    
            if (waitResult == WAIT_OBJECT_0) {
                GetOverlappedResult(hDirectory, &overlapped, &bytesReturned, FALSE); // 获取结果
    
                // 处理事件 (稍后添加)
    
                // 重新调用 ReadDirectoryChangesW 准备下一次监控
                success = ReadDirectoryChangesW(
                    hDirectory,
                    buffer.data(),
                    bufferSize,
                    TRUE,
                    FILE_NOTIFY_CHANGE_FILE_NAME |
                    FILE_NOTIFY_CHANGE_DIR_NAME |
                    FILE_NOTIFY_CHANGE_ATTRIBUTES |
                    FILE_NOTIFY_CHANGE_SIZE |
                    FILE_NOTIFY_CHANGE_LAST_WRITE |
                    FILE_NOTIFY_CHANGE_LAST_ACCESS |
                    FILE_NOTIFY_CHANGE_CREATION |
                    FILE_NOTIFY_CHANGE_SECURITY,
                    &bytesReturned,
                    &overlapped,
                    NULL
                );
    
                if (!success) {
                    std::cerr << "ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
                    break;
                }
            } else {
                std::cerr << "WaitForSingleObject failed: " << GetLastError() << std::endl;
                break;
            }
        }
    
        CloseHandle(hDirectory);
        CloseHandle(overlapped.hEvent);
        return 0;
    }

    这里我们使用 WaitForSingleObject 函数来等待 overlapped.hEvent 事件发生。 INFINITE 表示无限等待。

  4. 处理事件: 当事件发生时,我们需要解析缓冲区中的事件信息。

    #include <iostream>
    #include <windows.h>
    #include <vector>
    #include <string>
    #include <locale>
    #include <codecvt>
    
    // 辅助函数:将宽字符串转换为窄字符串
    std::string WStringToString(const std::wstring& wstr) {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
        return converter.to_bytes(wstr);
    }
    
    int main() {
        LPCWSTR directoryPath = L"\\?\C:\watched_directory"; // 你要监控的目录,注意使用宽字符
    
        HANDLE hDirectory = CreateFileW(
            directoryPath,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, // 需要FILE_FLAG_BACKUP_SEMANTICS才能打开目录
            NULL
        );
    
        if (hDirectory == INVALID_HANDLE_VALUE) {
            std::cerr << "CreateFileW failed: " << GetLastError() << std::endl;
            return 1;
        }
    
        std::cout << "Directory handle created successfully." << std::endl;
    
        DWORD bufferSize = 4096;
        std::vector<BYTE> buffer(bufferSize);
        DWORD bytesReturned;
        OVERLAPPED overlapped;
        memset(&overlapped, 0, sizeof(overlapped));
        overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 创建事件对象
    
        if (overlapped.hEvent == NULL) {
            std::cerr << "CreateEvent failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            return 1;
        }
    
        BOOL success = ReadDirectoryChangesW(
            hDirectory,
            buffer.data(),
            bufferSize,
            TRUE, // 是否监控子目录
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES |
            FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE |
            FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_SECURITY,
            &bytesReturned,
            &overlapped,
            NULL
        );
    
        if (!success) {
            std::cerr << "ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
            CloseHandle(hDirectory);
            CloseHandle(overlapped.hEvent);
            return 1;
        }
    
        std::cout << "Monitoring directory changes..." << std::endl;
    
        while (true) {
            DWORD waitResult = WaitForSingleObject(overlapped.hEvent, INFINITE); // 等待事件发生
    
            if (waitResult == WAIT_OBJECT_0) {
                GetOverlappedResult(hDirectory, &overlapped, &bytesReturned, FALSE); // 获取结果
    
                // 处理事件
                DWORD offset = 0;
                while (offset < bytesReturned) {
                    FILE_NOTIFY_INFORMATION* pNotifyInfo = (FILE_NOTIFY_INFORMATION*)(buffer.data() + offset);
    
                    std::wstring fileNameW(pNotifyInfo->FileName, pNotifyInfo->FileNameLength / sizeof(wchar_t));
                    std::string fileName = WStringToString(fileNameW);
    
                    switch (pNotifyInfo->Action) {
                        case FILE_ACTION_ADDED:
                            std::cout << "File added: " << fileName << std::endl;
                            break;
                        case FILE_ACTION_REMOVED:
                            std::cout << "File removed: " << fileName << std::endl;
                            break;
                        case FILE_ACTION_MODIFIED:
                            std::cout << "File modified: " << fileName << std::endl;
                            break;
                        case FILE_ACTION_RENAMED_OLD_NAME:
                            std::cout << "File renamed (old name): " << fileName << std::endl;
                            break;
                        case FILE_ACTION_RENAMED_NEW_NAME:
                            std::cout << "File renamed (new name): " << fileName << std::endl;
                            break;
                        default:
                            std::cout << "Unknown action: " << pNotifyInfo->Action << std::endl;
                            break;
                    }
    
                    if (pNotifyInfo->NextEntryOffset == 0) {
                        break;
                    }
                    offset += pNotifyInfo->NextEntryOffset;
                }
    
                // 重新调用 ReadDirectoryChangesW 准备下一次监控
                success = ReadDirectoryChangesW(
                    hDirectory,
                    buffer.data(),
                    bufferSize,
                    TRUE,
                    FILE_NOTIFY_CHANGE_FILE_NAME |
                    FILE_NOTIFY_CHANGE_DIR_NAME |
                    FILE_NOTIFY_CHANGE_ATTRIBUTES |
                    FILE_NOTIFY_CHANGE_SIZE |
                    FILE_NOTIFY_CHANGE_LAST_WRITE |
                    FILE_NOTIFY_CHANGE_LAST_ACCESS |
                    FILE_NOTIFY_CHANGE_CREATION |
                    FILE_NOTIFY_CHANGE_SECURITY,
                    &bytesReturned,
                    &overlapped,
                    NULL
                );
    
                if (!success) {
                    std::cerr << "ReadDirectoryChangesW failed: " << GetLastError() << std::endl;
                    break;
                }
            } else {
                std::cerr << "WaitForSingleObject failed: " << GetLastError() << std::endl;
                break;
            }
        }
    
        CloseHandle(hDirectory);
        CloseHandle(overlapped.hEvent);
        return 0;
    }

    缓冲区中的事件信息以 FILE_NOTIFY_INFORMATION 结构体数组的形式存在。

    typedef struct _FILE_NOTIFY_INFORMATION {
      DWORD         NextEntryOffset;
      DWORD         Action;
      DWORD         FileNameLength;
      WCHAR         FileName[1]; // 变长数组
    } FILE_NOTIFY_INFORMATION, *PFILE_NOTIFY_INFORMATION;
    • NextEntryOffset:指向下一个 FILE_NOTIFY_INFORMATION 结构体的偏移量,如果为 0,表示这是最后一个事件。

    • Action:发生的事件类型,比如 FILE_ACTION_ADDEDFILE_ACTION_REMOVED 等。常用的 Action 如下表:

      Action 含义
      FILE_ACTION_ADDED 文件被添加
      FILE_ACTION_REMOVED 文件被删除
      FILE_ACTION_MODIFIED 文件被修改
      FILE_ACTION_RENAMED_OLD_NAME 文件被重命名 (旧名称)
      FILE_ACTION_RENAMED_NEW_NAME 文件被重命名 (新名称)
    • FileNameLength:文件名长度(以字节为单位)。

    • FileName:文件名(宽字符)。

  5. 关闭句柄: 使用 CloseHandle 函数关闭目录句柄和事件句柄。

    CloseHandle(hDirectory);
    CloseHandle(overlapped.hEvent);

完整示例:

上面的代码已经是一个完整的示例了。

编译和运行:

  1. 创建一个目录 C:watched_directory
  2. 将代码保存为 ReadDirectoryChangesW_example.cpp
  3. 使用 Visual Studio 或者其他 C++ 编译器编译。
  4. 运行程序。

然后,你可以尝试在 C:watched_directory 目录下创建、删除或修改文件,看看程序是否能正确地检测到这些事件。

ReadDirectoryChangesW 的限制:

  • 缓冲区大小: 缓冲区的大小会影响可以接收的事件数量。如果缓冲区太小,可能会导致事件丢失。
  • Overlapped I/O: 使用 Overlapped I/O 需要小心处理同步问题。
  • 性能: 递归监控大量目录可能会影响性能。

总结

inotifyReadDirectoryChangesW 都是强大的文件系统事件监控工具。 选择哪个取决于你的操作系统和具体需求。希望今天的讲解能帮助你更好地理解和使用这两个工具,让你的程序更加“耳聪目明”!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注