文件系统事件监听

文件系统事件和网络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
相关推荐
_wyt0011 小时前
洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
c++·gesp
大树883 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5204 小时前
Linux 11 动态监控指令top
linux
Inhand陈工4 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
玖玥拾4 小时前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
酣大智5 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩5 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言