zoukankan      html  css  js  c++  java
  • Windows&Linux文件目录监控

    Windows

    Windows提供了几种方式对文件和目录进行监控,包括:FindFirstChangeNotification、ReadDirectoryChangesW、变更日志(Change Journal)等。

    (1)FindFirstChangeNotification函数,可以监控到目标目录及其子目录中所有文件的变化,但不能监控到具体是哪一个文件发生改变。

    (2)ReadDirectoryChangesW 能监控到目标目录下某一文件发生改变,并且可以知道发生变化的是哪一个文件。

    注意,FindFirstChangeNotification 和 ReadDirectoryChangesW 是互斥的,不能同时使用。

    (3)变更日志(Change Journal)可以跟踪每一个变更的细节,即使你的软件没有运行。很帅的技术,但也相当难用。

    本文只对ReadDirectoryChangesW 进行说明。

    该函数定义为:

     BOOL WINAPI ReadDirectoryChangesW(
    
            HANDLE hDirectory,   // 对目录进行监视的句柄
            LPVOID lpBuffer,     // 一个指向FILE_NOTIFY_INFORMATION结构体的缓冲区,其中可以将获取的数据结果将其返回。
            DWORD nBufferLength, // 指lpBuffer的缓冲区的大小值,以字节为单位。
            BOOL bWatchSubtree, // 是否监视子目录. 
            DWORD dwNotifyFilter, // 对文件过滤的方式和标准
           LPDWORD lpBytesReturned, // 将接收的字节数转入lpBuffer参数
           LPOVERLAPPED lpOverlapped, // 一般选择 NULL
          LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 一般选择 NULL
    
     );

    注意点:

    1)ReadDirectoryChangesW 数据缓冲区中使用的都是宽字节Unicode,字符串不是 NULL 结尾的,所以不能使用 wcscpy。
    2)ReadDirectoryChangesW放入while循环中。目的就是要在每次监测到一次变化后,重新发起新的 ReadDirectoryChangesW 调用。(适用情况:被监控的目录会被修改)
    3)如果很多文件在短时间内发生变更,则有可能会丢失部分通知。
    4)如果缓冲区溢出,整个缓冲区的内容都会被丢弃,BytesReturned会返回0。 5)在MSDN中,FILE_NOTIFY_INFORMATION的文档有一个关键的描述:如果文件既有长文件名,又有短文件名,那么文件会返回其中的一个名字,但不确定是返回哪一个。
    大多数时候,在短文件名和长文件名之间转换都很容易,但是如果文件被删除,情况就不一样了。最好的方法是维护一个跟踪文件的列表,同时跟踪长文件名和短文件名。(这种情况目前还没有遇到过)

    函数说明参考链接:
    https://blog.csdn.net/dropme/article/details/6036777
    https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_information

    ReadDirectoryChangesW可以监视的文件系统事件包括:
    FILE_ACTION_ADDED,  新增文件
    FILE_ACTION_REMOVED, 删除文件
    FILE_ACTION_MODIFIED, 修改文件
    FILE_ACTION_RENAMED_OLD_NAME, 重命名
    FILE_ACTION_RENAMED_NEW_NAME, 重命名
    void WatchDirectory(const std::string& watchedDir) {
        //#if defined(OS_WIN)
        std::wstring wstrWatchDir = Zeus::CharsetUtils::UTF8ToUnicode(watchedDir);
        HANDLE dirHandle = CreateFileW(wstrWatchDir.c_str(),
                                       GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
                                       FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                       NULL,
                                       OPEN_EXISTING,
                                       FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                       NULL);
        if (dirHandle == INVALID_HANDLE_VALUE) {
            LOG_ERROR << "Failed to get handle of directory.path:" << watchedDir
                << " errCode:" << GetLastError();
            return;
        }
     
        TCHAR notify[1024];
        memset(notify, 0, sizeof(notify));
        FILE_NOTIFY_INFORMATION *pNotification = (FILE_NOTIFY_INFORMATION *)notify;
        DWORD BytesReturned = 0;
     
        while (TRUE) {
            ZeroMemory(pNotification, sizeof(notify));
            auto watch_state = ReadDirectoryChangesW(dirHandle,
                                                     ¬ify,
                                                     sizeof(notify),
                                                     TRUE,   //监控子目录
                                                     FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_CREATION,
                                                     (LPDWORD)&BytesReturned,
                                                     NULL,
                                                     NULL);
     
            if (GetLastError() == ERROR_INVALID_FUNCTION) {
                LOG_WARN << u8"文件监控,系统不支持! path:" << watchedDir;
                break;
            } else if (watch_state == FALSE) {
                LOG_WARN << u8"文件监控,监控失败! path:" << watchedDir
                    << " errCode:" << GetLastError();
                break;
            } else if (GetLastError() == ERROR_NOTIFY_ENUM_DIR) {
                LOG_INFO << u8"文件监控,内存溢出! path:" << watchedDir;
                continue;
            } else {
                //这里主要就是检测返回的信息,(FILE_NOTIFY_INFORMATION)
                std::wstring fileName(pNotification->FileName, pNotification->FileNameLength / sizeof(wchar_t));
                switch (pNotification->Action) {
                case FILE_ACTION_ADDED:
                {
                    // to do ...
                }
                break;
                case FILE_ACTION_REMOVED:
                {
                    // to do ...
                }
                break;
                case FILE_ACTION_MODIFIED:
                {
                    // to do ...
                }
                break;
                default:
                    break;
                }
            }
        }
        CloseHandle(dirHandle);
        //#endif //OS_WIN
    }



    Linux

    自内核2.6.13起,Linux开始提供inotify机制,它是一个内核用于通知用户空间程序文件系统变化的机制,以允许应用程序监控文件事件。

    类似的监控服务:

    • Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理。
    • udev 动态地维护 /dev 下的设备文件。
    • inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知。

    注意点:

    1)Inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
    2)Inotify 既可以监视文件,也可以监视目录。当监控目录时,与路径自身及其所含文件相关的事件都会通知给应用程序。
    3)Inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
    4)Inotify 使用文件描述符作为接口,因而可以使用通常的文件 I / O 操作select 和 poll 来监视文件系统的变化。
    5)Inotify 监控机制为非递归。若应用程序有意监控整个目录子树内的事件,则需对该树中的每个目录发起 inotify_add_watch() 调用。

    Inotify 可以监视的文件系统事件包括:

    IN_ACCESS,即文件被访问
    IN_MODIFY,文件被 write
    IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等
    IN_CLOSE_WRITE,可写文件被 close
    IN_CLOSE_NOWRITE,不可写文件被 close
    IN_OPEN,文件被 open
    IN_MOVED_FROM,文件被移走,如 mv
    IN_MOVED_TO,文件被移来,如 mv、cp
    IN_CREATE,创建新文件
    IN_DELETE,文件被删除,如 rm
    IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己
    IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己
    IN_UNMOUNT,宿主文件系统被 umount
    IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
    IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)

    inotify API

    #include <sys/inotify.h>
     
    //Returns file descriptor on success, or -1 on error
    int inotify_init(void);
                                     
    //Returns watch descriptor on success, or -1 on error
    int inotify_add_watch(int fd, const char* pathname, uint32_t mask);
                                     
    //Returns 0 on success, or -1 on error
    int inotify_rm_watch(int fd, uint32_t wd);

    使用步骤:

    1、应用程序使用 inotify_init() 来创建一个inotify实例,该系统调用所返回的文件描述符用于在后续操作中指代该实例。   

    int fd = inotify_init ();

    2、应用程序使用 inotify_add_watch() 向inotify实例的监控列表添加条目,藉此告诉内核哪些文件是自己的兴趣所在。每个监控项都包含一个路径名以及相关的位掩码。位掩码针对路径名指明了所要监控的事件集合。作为函数结果,inotify_add_watch()将返回一监控描述符,用于在后续操作中指代该监控项(系统调用 inotify_rm_watch() 执行其逆向操作,将之前添加入 inotify 实例的监控项移除)。

    int wd = inotify_add_watch (fd, path, mask);

    3、为获得事件通知,应用程序需针对 inotify 文件描述符执行 read() 操作。每次对 read() 的成功调用,都会返回一个或多个 inotify_event 结构,其中各自记录了处于 inotify 实例监控之下的某一路径名所发生的事件。

    length = read(fd, buffer, EVENT_BUF_LEN);

    struct inotify_event* event = (struct inotify_event*) & buffer[i];

    注意:

    1) read()函数。读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

    参考链接:https://blog.csdn.net/u012317833/article/details/39343915

    2)inotify_event结构体

    struct inotify_event {
        __s32           wd;             /* 被监视目标的 watch 描述符 */
        __u32           mask;           /* 事件掩码 */
        __u32           cookie;         /* cookie to synchronize two events */
        __u32           len;            /* name字符串的长度 */
        char            name[0];        /* 被监视目标的路径名 */
    };

    inotify-tools工具链接:https://developer.aliyun.com/article/611376

    4、应用程序在结束监控时会关闭 inotify 文件描述符。这会自动清除与 inotify 实例相关的所有监控项。

    int ret = inotify_rm_watch (fd, wd);
    using namespace std;
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/inotify.h>
     
    #include <atomic>
     
    atomic<bool> g_IsExit{ false };
     
    #define EVENT_SIZE  ( sizeof (struct inotify_event) )
    #define EVENT_BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )
     
    int main() {
        int length, i = 0;
        int fd;
        int wd;
        char buffer[EVENT_BUF_LEN];
     
        /*creating the INOTIFY instance*/
        fd = inotify_init();
     
        /*checking for error*/
        if (fd < 0) {
            perror("inotify_init");
        }
     
     
        std::string watchBasePath = "/tmp";
        std::string watchPath = "/tmp/dir1";
     
        /*adding the “/tmp” directory into watch list. Here, the suggestion is to validate the existence of the directory before adding into monitoring list.*/
        wd = inotify_add_watch(fd, watchBasePath.data(), IN_ONESHOT);
     
        //添加了一个子目录进行监听,默认是不会监听子目录的
        inotify_add_watch(fd, watchPath.data(), IN_CREATE| IN_DELETE); 
        /*read to determine the event change happens on “/tmp” directory. Actually this read blocks until the change event occurs*/
     
        // 在外面加了一个循环,防止监听完一个事件后就退出了
        while (1) 
        {
            i = 0;
            length = read(fd, buffer, EVENT_BUF_LEN);
     
            /*checking for error*/
            if (length < 0) {
                perror("error to read file.");
                break;
            }
     
            if (g_IsExit) {
                break;
            }
     
            /*actually read return the list of change events happens. Here, read the change event one by one and process it accordingly.*/
            while (i < length) {
                struct inotify_event* event = (struct inotify_event*) & buffer[i];
                if (event->len) {
                    if (event->mask & IN_CREATE) {
                        if (event->mask & IN_ISDIR) {
                            printf("New directory %s created.
    ", event->name);
                        } else {
                            auto name = event->name;
                            printf("New file %s created.
    ", event->name);
                            break;
                        }
                    } else if (event->mask & IN_DELETE) {
                        if (event->mask & IN_ISDIR) {
                            printf("Directory %s deleted.
    ", event->name);
                        } else {
                            auto name = event->name;
                            printf("File %s deleted.
    ", event->name);
                        }
                    }
                }
                i += EVENT_SIZE + event->len;
            }
     
        }
        /*removing the “/tmp” directory from the watch list.*/
        inotify_rm_watch(fd, wd);
        /*closing the INOTIFY instance*/
        close(fd);
        return 0;
    }

     






  • 相关阅读:
    转载: Ubuntu 在命令下,安装中文环境的方法。
    java复制文件范例代码
    Cesium-entiy闪烁范例
    转载:贝塞尔曲线计算公式
    转载: utm坐标和经纬度相互转换
    arcgis 地图如何转到supermap平台
    Linux查看修改文件句柄数
    转载:Linux目录文件的权限查看与修改
    欧拉系统-登陆 SSH 出现 Access Denied 错误
    关于 nodejs sequelize 事务批量拆分
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/15028054.html
Copyright © 2011-2022 走看看