文件系统事件监听

文件系统事件和网络IO事件一样,也可以通过epoll或者IOCP 事件管理器统一调度,当所监控的文件或文件夹发生了增删改的事件时,就会触发事件回调,进行事件处理。很常见的应用,如配置文件立即生效功能,就可以通过这种方式触发软件内部进行配置更新。也可以用来监控日志文件的更新,拉取最新日志(类似notepad++打开日志文件,如果日志文件更新了,就会提示reload)。

下面分别给出了linux和windows系统下文件系统事件监听的示意代码,仅供参考。

cpp 复制代码
//file: FileMonitor.h

#ifndef INOTIFY_FILEMONITOR_H
#define INOTIFY_FILEMONITOR_H

#include <string>
#include <map>

/**
 * 文件监视器。
 * 用于监控文件系统中文件相关的事件,如创建、删除等操作。
 */
class FileMonitor {

public:

    FileMonitor() = default;

    ~FileMonitor() = default;

    void startWatch(const std::string &path);

    void stopWatch();

    bool addWatch(const std::string &path);

private:

    // inotify 句柄
    int inotify_fd{0};

    // inotify_add_watch返回值 -> 要监控的文件名 的映射
    std::map<int, std::string> watchDesc2Name;

    // 是否停止监控
    bool isStopped{false};
};


#endif //INOTIFY_FILEMONITOR_H


// file: FileMonitor.cpp

#include <sys/inotify.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <thread>
#include <chrono>
#include <cstdio>
#include <iostream>
#include "FileMonitor.h"

#define log(format, ...) { \
    auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());   \
    char buf[64];                                                                        \
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now));               \
    printf("%s | %llu | ", buf, std::this_thread::get_id());                             \
    printf(format, ##__VA_ARGS__);                                                       \
    printf("\n"); }

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

void FileMonitor::startWatch(const std::string &path) {
    log("Ready to watch...");

    // inotify 初始化
    inotify_fd = inotify_init();
    if (!addWatch(path)) {
        return;
    }

    // 创建一个 epoll 句柄
    log("epoll_create");
    int epfd = epoll_create(256);
    struct epoll_event ev;
    ev.data.fd = inotify_fd;        // 设置要处理的事件相关的文件描述符
    ev.events = EPOLLIN | EPOLLET;  // 设置要处理的事件类型

    // 注册 epoll 事件
    log("epoll_ctl");
    epoll_ctl(epfd, EPOLL_CTL_ADD, inotify_fd, &ev);

    // 循环监听事件
    char buffer[BUF_LEN];
    struct epoll_event events[20];  // 存储从内核得到的事件集合
    while (!isStopped) {
        log("epoll_wait...");
        // 等待事件发生。返回需要处理的事件数目
        int nfds = epoll_wait(epfd, events, 20, 500);
        log("Event count: %d", nfds);
        for (int i = 0; i < nfds; ++i) {
            /**
             * epoll_wait 会一直阻塞直到下面2种情况:
             *   1. 一个文件描述符触发了事件。
             *   2. 被一个信号处理函数打断,或者 timeout 超时。
             * 所以下面需要对 fd 进行过滤,判断是否是我们需要的 fd 产生了事件
             */
            if (events[i].data.fd != inotify_fd) {
                log("Other event, fd=%d", events[i].data.fd);
                continue;
            }
            long length = read(inotify_fd, buffer, BUF_LEN);
            if (length < 0) {
                perror("Read failed");
            }
            log("Read length: %ld", length);
            int pos = 0;
            int count = 0;
            while (pos < length) {
                struct inotify_event *event = (struct inotify_event *) &buffer[pos];
                log("[%d]event->len=%u, event->mask=%#X, EVENT_SIZE=%lu", ++count, event->len, event->mask, EVENT_SIZE);
                if (event->len) {
                    if (event->mask & IN_CREATE) {
                        log("CREATE: %s", event->name);
                    } else if (event->mask & IN_DELETE) {
                        log("DELETE: %s", event->name);
                    } else if (event->mask & IN_MODIFY) {
                        log("MODIFY: %s", event->name);
                    } else if (event->mask & IN_OPEN) {
                        log("OPEN: %s", event->name);
                    } else if (event->mask & IN_CLOSE) {
                        log("CLOSE: %s", event->name);
                    } else {
                        log("Unknown event, mask=%u", event->mask);
                    }
                }
                pos += EVENT_SIZE + event->len;
            }
        }
    }
    log("Stop inotify");
    for (auto &ele: watchDesc2Name) {
        inotify_rm_watch(inotify_fd, ele.first);
    }
    close(epfd);
    close(inotify_fd);
}

void FileMonitor::stopWatch() {
    isStopped = true;
}

bool FileMonitor::addWatch(const std::string &path) {
    // TODO 需要对文件(夹)是否存在进行判断
    // 监听指定目录下的修改、创建、删除事件
    int wd = inotify_add_watch(inotify_fd, path.c_str(), IN_MODIFY | IN_CREATE | IN_DELETE);
//    int wd = inotify_add_watch(inotify_fd, path.c_str(), IN_ALL_EVENTS);
    if (wd < 0) {
        log("inotify_add_watch failed: %s", path.c_str());
        return false;
    }
    log("Add watch: %s", path.c_str());
    watchDesc2Name[wd] = path;
    return true;
}


// main: main.cpp
#include <iostream>
#include "FileMonitor.h"

int main() {
    std::string dirPath = "/home/nvidia/logs";
    FileMonitor fileMonitor;
    fileMonitor.startWatch(dirPath);
    return 0;
}

// https://github.com/BornToDeath/cpp/blob/main/test/case11_inotify/main.cpp
cpp 复制代码
#include <iostream>
#include <map>
#include <windows.h>
#define DIRECTORY_PATH      L"C://TestFolder"

#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                  (PCHAR)(address) - \
                                                  (ULONG_PTR)(&((type *)0)->field)))

typedef struct directory_info
{
    HANDLE    hDir;
    CHAR          szDirName[MAX_PATH];
    CHAR          szBuffer[4096];
    DWORD      dwBufLength;
    OVERLAPPED Overlapped;  // 此处需要定义overlapp结构
}DIRECTORY_INFO, * LPDIRECTORY_INFO;

class cFileUpdateChecker
{
public:
    cFileUpdateChecker(const wchar_t* root) :hThread(NULL), dwThread(0), hComp(NULL), pdir(NULL) { wsprintf(dirPath,L"%s",root); }
    ~cFileUpdateChecker()
    {
        if (hComp) 
        {
            PostQueuedCompletionStatus(hComp, 0, NULL, NULL);
            CloseHandle(hComp);
        }
        if (hThread)        
            CloseHandle(hThread);
        if (pdir)
        {
            CloseHandle(pdir->hDir);
            HeapFree(GetProcessHeap(), 0, pdir);
        }
        for(auto it = opened_files.begin();it!=opened_files.end();it++)
        {
            if(it->second != NULL)
                CloseHandle(it->second);
        }
    }
    // 定义IOCP的工作线程
    static DWORD WINAPI  ThreadFunc(void* p)
    {
        cFileUpdateChecker* pHost = (cFileUpdateChecker*)p;
        LPDIRECTORY_INFO lpdir = NULL;
        DWORD nbytes, errCode;
        LPOVERLAPPED lpOverlapped = NULL;
        PFILE_NOTIFY_INFORMATION fni = NULL;
        ULONG_PTR completion_key;
        DWORD wait = 1000;
        while (true)
        {
            BOOL bRet = GetQueuedCompletionStatus(pHost->hComp, &nbytes, &completion_key, &lpOverlapped, wait);
            if (FALSE == bRet )
            { 
                errCode = GetLastError();
                if (errCode == WAIT_TIMEOUT)
                {
                    wait = INFINITE;
                    continue;
                }
                else
                    break;
            }
            if (lpOverlapped == NULL)
            {
                break;
            }
            else
            {
                lpdir = CONTAINING_RECORD(lpOverlapped, DIRECTORY_INFO, Overlapped);
                if (lpdir)
                {
                    fni = (PFILE_NOTIFY_INFORMATION)lpdir->szBuffer;
                    int filename_length =fni->FileNameLength;
                    wchar_t* root_dir = (wchar_t*)lpdir->szDirName;
                    wchar_t* filename = &fni->FileName[0];
                    switch (fni->Action)
                    {
                    case FILE_ACTION_ADDED:
                        wprintf(L"The file %s/%s was added to the directory.\r\n", root_dir, filename);
                        break;
                    case FILE_ACTION_REMOVED:
                        wprintf(L"The file  %s/%s  was removed from the directory.\r\n", root_dir, filename);
                        break;
                    case FILE_ACTION_MODIFIED:
                        {
                            wchar_t file_path_buffer[MAX_PATH]={};
                            wsprintf(file_path_buffer,L"%s\\%s",root_dir, filename);
                            wprintf(L"The file %s was modified. This can be a change in the time stamp or attributes.\r\n", file_path_buffer);
                            auto it = pHost->opened_files.find(file_path_buffer);
                            if(it == pHost->opened_files.end())
                            {
                                HANDLE hFile = CreateFile(file_path_buffer,
                                                       GENERIC_READ,
                                                       FILE_SHARE_READ|FILE_SHARE_WRITE,
                                                       NULL,OPEN_EXISTING,
                                                       FILE_ATTRIBUTE_NORMAL,NULL);
                                if(hFile != NULL)
                                {
                                    DWORD dwFilePointer = SetFilePointer(hFile,0,NULL,FILE_END);
                                    if(dwFilePointer != INVALID_SET_FILE_POINTER)
                                        pHost->opened_files[file_path_buffer]=hFile;
                                    else
                                        CloseHandle(hFile);
                                }
                            }
                            else
                            {
                                CHAR szReadText[50] = {};
                                DWORD dwRead = 0;
                                //需要注意的是,FILE_ACTION_MODIFIED信号仅表示文件系统中的该文件节点对象的写状态更新了,至于是否完成落盘,还需要看修改文件的进程是否完成了刷盘。由于实时性问题,刷盘动作可能存在一定的滞后,如果每次进入Modify事件都要增量读取更新的文件内容。可以增加30~50ms的延时
                                bRet = ReadFile(it->second,szReadText,32,&dwRead,NULL);
                                if(bRet == FALSE)
                                {
                                    CloseHandle(it->second);
                                    pHost->opened_files.erase(it);
                                }
                                else
                                {
                                    printf("Additional file content: %s.\r\n",szReadText);
                                    SetFilePointer(it->second,0,NULL,FILE_END);
                                }
                            }
                            
                        }
                        break;
                    case FILE_ACTION_RENAMED_OLD_NAME:
                        {
                            const wchar_t* new_filename = L"";
                            int new_filename_length = 0;
                            if (fni->NextEntryOffset);
                            {
                                PFILE_NOTIFY_INFORMATION new_fni = (PFILE_NOTIFY_INFORMATION)(lpdir->szBuffer + fni->NextEntryOffset);
                                new_filename = &new_fni->FileName[0];
                                new_filename_length = new_fni->FileNameLength;
                            }
                            wprintf(L"The file  %s/%s  was renamed, and this is the new name %s.\r\n", root_dir, filename, new_filename);
                        }
                        break;
                    case FILE_ACTION_RENAMED_NEW_NAME:
                        wprintf(L"The file  %s/%s  was renamed and this is the new name.\r\n", root_dir,  filename);
                        break;
                    default:
                        break;
                    }
                    // re post event
                    ZeroMemory(&(pHost->pdir->Overlapped), sizeof(OVERLAPPED));
                    ZeroMemory(pHost->pdir->szBuffer, 4096);
                    ReadDirectoryChangesW(pHost->pdir->hDir,
                        pHost->pdir->szBuffer,
                        sizeof(pHost->pdir->szBuffer),
                        TRUE,
                        FILE_NOTIFY_CHANGE_FILE_NAME |
                        FILE_NOTIFY_CHANGE_DIR_NAME |
                        FILE_NOTIFY_CHANGE_ATTRIBUTES |
                        FILE_NOTIFY_CHANGE_SIZE |
                        FILE_NOTIFY_CHANGE_LAST_ACCESS |
                        FILE_NOTIFY_CHANGE_CREATION |
                        FILE_NOTIFY_CHANGE_SECURITY |
                        FILE_NOTIFY_CHANGE_LAST_WRITE,
                        &(pHost->pdir->dwBufLength),
                        (OVERLAPPED*)(&(pHost->pdir->Overlapped)),
                        NULL
                    );
                }
            }
        }
        return 0;
    }

    bool StartChecker()
    {
        hComp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
        pdir = (LPDIRECTORY_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DIRECTORY_INFO));
        if (NULL == pdir)
        {
            return false;
        }

        pdir->hDir = CreateFile(dirPath,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
        if (pdir->hDir == INVALID_HANDLE_VALUE)
        {
            return false;
        }
        // 启动工作线程
        hThread = CreateThread(NULL, 0, ThreadFunc, this, 0, &dwThread);
        if (NULL == hThread)
        {
            return false;
        }
        ZeroMemory(&(pdir->Overlapped), sizeof(OVERLAPPED));
        ZeroMemory(pdir->szBuffer, 4096);
        ZeroMemory(pdir->szDirName, MAX_PATH);
        wsprintf((wchar_t*)pdir->szDirName,L"%s", dirPath);
        // 投递I/O请求到完成端口
        HANDLE hRet = CreateIoCompletionPort(pdir->hDir, hComp,NULL, 0);
        if (NULL == hComp)
        {
            return false;
        }
        BOOL bRet = ReadDirectoryChangesW(pdir->hDir,
            pdir->szBuffer,
            4096,
            TRUE,
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES |
            FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_SECURITY |
            FILE_NOTIFY_CHANGE_LAST_WRITE,
            &pdir->dwBufLength,
            &(pdir->Overlapped),
            NULL
        );
        if (FALSE == bRet)
        {
            return false;
        }
        return true;
    }
private:
    HANDLE hThread;
    DWORD dwThread;
    HANDLE  hComp;
    LPDIRECTORY_INFO pdir;
    WCHAR dirPath[MAX_PATH];
    std::map<std::wstring,HANDLE> opened_files;
};

int main()
{
    cFileUpdateChecker* ckr = new cFileUpdateChecker(DIRECTORY_PATH);
    std::cout << "Supervise "; std::wcout << DIRECTORY_PATH << " Begin..." << std::endl;
    ckr->StartChecker();
    getchar();
    delete ckr;
    getchar();
}

// https://blog.csdn.net/nhn_devlab/article/details/6034055
相关推荐
林开落L17 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
Prejudices30 分钟前
C++如何调用Python脚本
开发语言·c++·python
单音GG33 分钟前
推荐一个基于协程的C++(lua)游戏服务器
服务器·c++·游戏·lua
qing_0406031 小时前
C++——多态
开发语言·c++·多态
孙同学_1 小时前
【C++】—掌握STL vector 类:“Vector简介:动态数组的高效应用”
开发语言·c++
Shepherd06191 小时前
【Jenkins实战】Windows安装服务启动失败
运维·jenkins
charlie1145141911 小时前
Qt Event事件系统小探2
c++·qt·拖放·事件系统
iiiiiankor1 小时前
C/C++内存管理 | new的机制 | 重载自己的operator new
java·c语言·c++
小辛学西嘎嘎1 小时前
C/C++精品项目之图床共享云存储(3):网络缓冲区类和main
c语言·开发语言·c++
Biomamba生信基地2 小时前
Linux也有百度云喔~
linux·运维·服务器·百度云