windows环境下C++实现的多种目录监控和优劣解析
FileSystemWatcher使用原因:
最近在项目中需要在windows平台,实时的监控一个目录下各级子目录中被FTP下载到本地的文件的生成,所以研究了windows的各种监控目录变化信息的API。
1.FindFirstChangeNotification()函数
功能:创建一个改变通知句柄,并设置通知条件,即发生某种修改时,该通知句柄将被促发生效。该函数仅仅负责对文件修改作出通知,并不记录和反馈修改信息。
返回值:成功时返回指向改变通知的句柄,失败时返回INVALID_HANDLE_VALUE,可以用GetLastError()函数得到。
示例代码:
#define _CRT_SECURE_NO_WARNINGS#include<windows.h>
#include<stdio.h>
#include<conio.h>
void main()
{//监视C盘根目录下文件名改变的例子,其中while循环一直监控,直到程序关闭HANDLE hChangeHandle = FindFirstChangeNotification("C:\\", false, FILE_NOTIFY_CHANGE_FILE_NAME);//判断是否设置成功if (hChangeHandle == INVALID_HANDLE_VALUE) {printf("Find First Change Notification failed\n");system("pause");return;}//等待通知printf("These change a file name\n");//循环等待while (true)if (FindNextChangeNotification(hChangeHandle)) {printf("These change a file name\n");//结束监视程序调用FindCloseChangeNotification关闭句柄FindCloseChangeNotification(hChangeHandle);getchar(); //按键退出程序}system("pause");}system("pause");
}
优点: 这种方式监控目录很简单,API很好掌握,返回值也很简单
缺点: 太TM简单了,无法获取是哪一个文件发生了改变。
应用途径:可以作为简单的警报模块,当一个固定的文件发生了很细微的(比较单调,或者已经知道类型的)变化的时候,可以及时的通知和反馈。
ps:下面是转载自http://blog.csdn.net/woshinia/article/details/8162983的一段代码,略作修改以便于直接使用。
#include <Windows.h>
#include <tchar.h>
#include <time.h>//使用FindFirstChangeNotification()实现了警报
void WatchDirectory(LPTSTR lpDir)
{DWORD dwWaitStatus;HANDLE dwChangeHandles[3];TCHAR lpDrive[4];//存储磁盘符TCHAR lpFile[_MAX_FNAME];//用于存储文件名TCHAR lpExt[_MAX_EXT];//用于存储对应文件的后缀_tsplitpath(lpDir, lpDrive, NULL, lpFile, lpExt);lpDrive[2] = (TCHAR)'\\';lpDrive[3] = (TCHAR)'\0';//分别监控文件名,路径名,文件内容的修改 dwChangeHandles[0] = FindFirstChangeNotification(lpDir,TRUE,FILE_NOTIFY_CHANGE_FILE_NAME); //文件名 if (dwChangeHandles[0] == INVALID_HANDLE_VALUE)ExitProcess(GetLastError());dwChangeHandles[1] = FindFirstChangeNotification(lpDrive,TRUE,FILE_NOTIFY_CHANGE_DIR_NAME); //路径名 if (dwChangeHandles[1] == INVALID_HANDLE_VALUE)ExitProcess(GetLastError());dwChangeHandles[2] = FindFirstChangeNotification(lpDir,TRUE,FILE_NOTIFY_CHANGE_LAST_WRITE); //文件内容/或者说最后保存时间 if (dwChangeHandles[2] == INVALID_HANDLE_VALUE)ExitProcess(GetLastError());//改变通知已经设置完成,现在只需等待这些通知被触发,然后做相应处理while (TRUE){dwWaitStatus = WaitForMultipleObjects(3, dwChangeHandles, FALSE, INFINITE);//把等待的时间设置为无限// time(&ChangeTime);switch (dwWaitStatus){//分别监控文件名,路径名,文件内容的修改 case WAIT_OBJECT_0: if (FindNextChangeNotification(dwChangeHandles[0]) == FALSE)ExitProcess(GetLastError());break;case WAIT_OBJECT_0 + 1:if (FindNextChangeNotification(dwChangeHandles[1]) == FALSE)ExitProcess(GetLastError());break;case WAIT_OBJECT_0 + 2:if (FindNextChangeNotification(dwChangeHandles[2]) == FALSE)ExitProcess(GetLastError());break;default:ExitProcess(GetLastError());}}
}
2.ReadDirectoryChangesW()函数
功能:监控文件修改,并记录文件修改的相关信息,如修改的文件名,何种类型的修改等。
返回值:如果函数成功,返回值就是非0。对于同步调用,这意味着操作成功,对于异步调用,这意味着操作成功地排队。如果函数失败,返回值是0。如果操作目录或文件系统不支持这个操作,函数将返回ERROR_INVALID_FUNCTION,可以使用GetLastError()函数获取。
注意,FindFirstChangeNotification 和 ReadDirectoryChangesW 是互斥的,不能同时使用。
ps: 这个API其实我在一开始的时候是最感兴趣的,因为它机会包含了我一切想要的功能,而且看起来也不是很复杂,但是在实际的操作中总会出现一些奇怪的现象——比如同时在需要monitor的path下change一些file,总会奇怪的pass掉一部分数据,而在我的项目中这是不能容忍的,为此我找了很多的资料,幸好我还是找到了一个大牛解释这个问题:
Jim Beveridge:
使用 ReadDirectoryChangesW 的最大挑战在于,在IO模式,处理信号,等待方式,以及线程模型这几个问题的整合上,存在数百种可能性。如果你不是 Win32 I/O 方面的专家,即使最简单的场景,你也很难搞定。
- A. I/O模式:
- 阻塞同步(Blocking synchronous)
- 触发式同步(Signaled synchronous)
- 重叠异步(Overlapped asynchronous)
- 完成例程(Completion Routine) (又名 Asynchronous Procedure Call or APC)
- B. 当调用 WaitForXxx 函数的时候:
- 等待目录句柄
- 等待 OVERLAPPED 结构体里的 Event 对象
- 什么都不等 (APCs)
- C. 处理通知:
- 阻塞
- WaitForSingleObject
- WaitForMultipleObjects
- WaitForMultipleObjectsEx
- MsgWaitForMultipleObjectsEx
- IO完成端口(I/O Completion Ports)
- D. 线程模型:
- 每个工作线程调用一次 ReadDirectoryChangesW.
- 每个工作线程调用多次 ReadDirectoryChangesW.
- 在主线程上调用多次 ReadDirectoryChangesW.
- 多个线程进行多个调用. (I/O Completion Ports)
最后,当调用 ReadDirectoryChangesW 的时候,你可以通过 flags 选择你要监控的内容,包括文件创建,内容变更,属性变更等等。你可以多次调用,每次一个 flag,也可以在一次调用中使用多个 flag。多个 flag 总是正确的解决方案。但如果你为了调试方便,需要一个 flag 一个 flag 的调用的话,那就需要从 ReadDirectoryChangesW 返回的通知缓冲区中读取更多的数据。
如果你的脑子正在囧的话,那么你就能够明白为什么那么多人都没法搞定这件事了。
解决方案:
简单 - A2C3D1 - 在单独的线程中调用 ReadDirectoryChangesW,然后通过 PostMessage 发送给主线程。对于性能要求不高的 GUI 程序最合适。在 CodeProject 上的 CDirectoryChangeWatcher 就是使用的这个策略。微软的 FWATCH 例子 也是使用的这个策略。
性能 - A4C6D4 - 性能最好的解决方案是使用I/O完成端口,但是,这个激进的多线程方案实在太过复杂,应当仅限在服务器上使用。对任何 GUI 程序来说,这个方案似乎都是不必要的。如果你不是一个多线程专家,请远离这个策略。
平衡 - A4C5D3 - 通过完成例程(Completion Routines),在一个线程中完成所有工作。你可以发起尽可能多的 ReadDirectoryChangesW 调用,由于完成例程是自动分派的,所有不需要等待任何句柄。你可以通过回调传递对象的指针,以便跟踪原始的数据结构。
起初我曾经认为 GUI 程序可以通过 MsgWaitForMultipleObjectsEx 将变更通知混入到窗口消息中。但由于对话框有自己的消息循环,当对话框显示的时候,通知便无法处理了。于是这个好主意被现实无情的碾碎了。
-------------------------------------------------------------------------------------------------------------------------------------------------------
但是不管问题有多大,我还是介绍一下这个API以便于在单线程下奋斗的小伙子们使用:
BOOL WINAPI ReadDirectoryChangesW(__in HANDLE hDirectory,//指向监控目录的句柄,可以用CreatFile生成__in_out LPVOID lpBuffer,//存储修改信息的首地址__in DWORD nBufferLength,//分配的存储修改信息的空间的长度__in BOOL bWatchSubtree,//TRUE则监控子目录,FALSE则不监控__in DWORD dwNotifyFilter,//通知条件__out LPDWORD lpBytesReturned,//该函数返回信息的字节数,也就是存到lpBuffer中的内容的字节数__in LPOVERLAPPED lpOverlapped,//一个指向OVERLAPPED结构的指针,他在同步调用时提供数据供使用,否则他就为NULL__in LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//当操作结束或被取消或者线程进入等待状态时的一个指向将被调用操作的指针
);
返回值:如果函数成功,返回值就是非0。对于同步调用,这意味着操作成功,对于异步调用,这意味着操作成功地排队。如果函数失败,返回值是0。如果操作目录或文件系统不支持这个操作,函数将返回ERROR_INVALID_FUNCTION,可以使用GetLastError()函数获取。
PS:下面是一个关于ReadDirectoryChangesW()函数的使用例子.添加主函数可以使用,效果一般,不建议直接在项目中使用。
#include <Windows.h>
#include <tchar.h>
#include <time.h>
#include <thread>
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
//ThreadParameter结构体的定义
/*
*整合参数为一个结构体传给子线程的原因在于创建线程时
*指定的线程入口函数只接受一个LPVOID类型的指针,具体内容可以参考msdn,
*实际上向子线程传递参数还有一种方法,全局变量,
*例如后面程序中的WriteLog就是一个文件输入流对象,我就是在程序首部定义的全局变量。
*/
typedef struct ThreadParameter
{LPTSTR in_directory;//监控的路径FILE_NOTIFY_INFORMATION *in_out_notification;//存储监控函数返回信息地址DWORD in_MemorySize;//传递存储返回信息的内存的字节数DWORD *in_out_BytesReturned;//存储监控函数返回信息的字节数DWORD *in_out_version;//返回版本信息FILE_NOTIFY_INFORMATION *temp_notification;//备用的一个参数
}ThreadParameter;// 函数: WatchChanges(LPVOID lpParameter)
//
// 目的: 监控目录的程序
//
// 注释:主函数创建线程时制定了这个函数的入口
// 届时该子程序将自动启动执行。
// 备注:因为代码不全,看下面的代码时,主要参考红色的字体部分DWORD WINAPI WatchChanges(LPVOID lpParameter)//返回版本信息
{ThreadParameter *parameter = (ThreadParameter*)lpParameter;LPCTSTR WatchDirectory = parameter->in_directory;////创建一个目录句柄HANDLE handle_directory = CreateFile(WatchDirectory,FILE_LIST_DIRECTORY,FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);if (handle_directory == INVALID_HANDLE_VALUE){DWORD ERROR_DIR = GetLastError();MessageBox(NULL, TEXT("打开目录错误!"), TEXT("文件监控"), 0);}BOOL watch_state;while (TRUE){watch_state = ReadDirectoryChangesW(handle_directory,(LPVOID)parameter->in_out_notification,parameter->in_MemorySize,TRUE,FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE,(LPDWORD)parameter->in_out_BytesReturned,NULL,NULL);//printf("########%d########\n",watch_state);//检测结果为1,表明函数已成功执行/// time(&ChangeTime);//记录修改时间if (GetLastError() == ERROR_INVALID_FUNCTION){MessageBox(NULL, TEXT("系统不支持!"), TEXT("文件监控"), 0);}else if (watch_state == 0){MessageBox(NULL, TEXT("监控失败!"), TEXT("文件监控"), 0);}else if (GetLastError() == ERROR_NOTIFY_ENUM_DIR){MessageBox(NULL, TEXT("内存溢出!"), TEXT("文件监控"), 0);}else{//将宽字符类型的FileName变量转换成string,便于写入log文件,否则写不进去正确的文件名string file_name;DWORD length = WideCharToMultiByte(0, 0, parameter->in_out_notification->FileName, -1, NULL, 0, NULL, NULL);PSTR ps = new CHAR[length];if (length >= 0){WideCharToMultiByte(0, 0, parameter->in_out_notification->FileName, -1, ps, length, NULL, NULL);file_name = string(ps);delete[] ps;}//这里主要就是检测返回的信息,需要注意FILE_NOTIFY_INFORMATION结构体的定义,以便正确调用返回信息if (parameter->in_out_notification->Action == FILE_ACTION_ADDED){cout<< "新增文件 : " << file_name << "\n" << flush;}if (parameter->in_out_notification->Action == FILE_ACTION_REMOVED){cout << "删除文件 : " << file_name << "\n" << flush;}//对于下面两种情况,Action本身也是文件名(可能是old_name也可能是new_name)if (parameter->in_out_notification->Action == FILE_ACTION_RENAMED_OLD_NAME){cout << "重命名\"" << file_name << "\"文件\n" << flush;}if (parameter->in_out_notification->Action == FILE_ACTION_RENAMED_NEW_NAME){cout << "重命名\"" << file_name << "\"文件为\"" << parameter->in_out_notification->Action << "\"\n" << flush;}(*(parameter->in_out_version))++;memset(parameter->in_out_notification, '\0', 1024);}Sleep(500);}return 0;
}
3.FileSystemWatcher
其实关于这个类,我最开始听说是在一个搞网络的朋友那里,他前段时间有做一个监控档案系统的修改,是多线程并发的监控到某一个目录下档案信息的改变。然后这个是使用公共语言运行时CLR.net的一个产品,我就花了一些时间在这个上面,主要是参看用MSDN手册,在里面有很详细的解释——https://msdn.microsoft.com/zh-cn/library/system.io.filesystemwatcher(v=vs.110).aspx
1.常用的几个基本属性:
(1) Path :设置要监视的目录的路径。
(2) IncludeSubdirectories :设置是否级联监视指定路径中的子目录。
(3) Filter :设置筛选字符串,用于确定在目录中监视哪些类型的文件。
(4) NotifyFilter :设置文件的哪些属性的变动会触发Changed事件,同时监控多个属性变动可以按“或”组合。(默认值为 NotifyFilter.LastWrite | NotifyFilter.FileName | NotifyFilter.DirectoryName 组合)
子项: Attributes -- 文件或文件夹的属性。
CreationTime -- 文件或文件夹的创建时间。
DirectoryName -- 目录名。(常用)
FileName -- 文件名。 (常用)
LastAccess -- 文件或文件夹上一次打开的日期。
LastWrite -- 上一次向文件或文件夹写入内容的日期。
Security -- 文件或文件夹的安全设置。
Size -- 文件或文件夹的大小。 (常用)
(5) EnableRaisingEvents :设置是否开始监控。(默认为false)
2.常用事件:(1) Changed :当更改文件和目录时发生,可以通过NotifyFilter属性设置触发该事件的需要文件更改的属性。
(2) Created : 当创建文件和目录时发生。
(3) Deleted : 删除文件或目录时发生。
(4) Renamed :重命名文件或目录时发生。
(5) FileSystemEventArgs 对象:
PS:下面是我具体实现的代码:
using namespace System;
using namespace msclr::interop;
using namespace System::IO;
using namespace System::Security::Permissions;
using namespace System::Runtime::InteropServices;
ref class FSEventHandler
{
private:void OnChanged(Object^ source, FileSystemEventArgs^ e){char* path = (char*)(Marshal::StringToHGlobalAnsi(e->FullPath)).ToPointer();std::cout<<path<<endl;}public:/*启动检测*/int static run1(String ^ MonitorPath){//创建一个FileSystemWatcher并设置它的属性.FileSystemWatcher^ fsWatcher = gcnew FileSystemWatcher();fsWatcher->Path = MonitorPath;//设置缓冲区大小1M,根据需求设置fsWatcher->InternalBufferSize = 1048576;//监听文件、目录、文件大小的改变fsWatcher->NotifyFilter = NotifyFilters::FileName | NotifyFilters::DirectoryName | NotifyFilters::Size;//监听子目录fsWatcher->IncludeSubdirectories = true;//添加事件处理程序FSEventHandler^ handler = gcnew FSEventHandler();fsWatcher->Changed += gcnew FileSystemEventHandler(handler, &FSEventHandler::OnChanged);//开始监听fsWatcher->EnableRaisingEvents = true;system("pause");//把父进程阻塞起来,防止负责检测的子进程死掉return 0;}
};
但是,这样做同样出现了很多问题:
1.首先是在做类似拷贝这样的操作的时候,监控事件create会监控到多次
2.监控FTP的下载产物的时候会把tmp文件也录入进去
限于篇幅,在下一篇博客中写我的解决办法。
windows环境下C++实现的多种目录监控和优劣解析相关推荐
- windows环境下cmd切换不同盘目录
windows系统下,如果是在本盘切换目录,直接用cd 找到对应的本盘目录直接即可切换. 但是如果切换到不同的盘的,比如E盘,这个时候我用cd就管用了,后来发现,直接输入"E:"即 ...
- Windows环境下打开cmd进入到指定目录的两种方法
文章目录 一.cmd是什么 二.在指定的目录地址栏中输入cmd敲回车即可 三."Windows+r"键打开运行对话框,在框中输入cmd敲回车,然后再输入相应的命令 四.cmd常用指 ...
- Windows环境下如何打开cmd进入到指定目录
Windows环境下如何打开cmd进入到指定目录,可以有两种方式:一种是直接在指定的目录地址栏中输入cmd敲回车即可:第二种可以里面"Windows+r"键打开运行对话框,在框中输 ...
- java 内存映射文件进程间通讯_[转]Windows环境下利用“共享内存”实现进程间通信的C/C++代码---利用CreateFileMapping和MapViewOfFile...
进程间的通信方式有很多种, 上次我们说了最傻瓜的"共享外存/文件"的方法. 那么, 在本文中, 我们即将学习"共享内存"的方式实现进程间的通信, 这是IPC最快 ...
- windows环境下安装RabbitMQ(超详细)
windows环境下安装RabbitMQ(超详细) 记录RabbitMQ安装过程,欢迎大家和我交流.(安装过程中遇到的问题也有总结哈,请查看) RabbitMq简介 安装准备工具 安装步骤(图文) 安 ...
- Windows环境下文件夹自动同步的实现
一.Winscp介绍 Winscp是开源的FTP.SCP客服端. WinSCP 是一个 Windows 环境下使用的 SSH 的开源图形化 SFTP 客户端.同时支持 SCP 协议.它的主要功能是在本 ...
- 第一部分:使用iReport制作报表的详细过程(Windows环境下)
第一部分:使用iReport制作报表的详细过程(Windows环境下) 提示:在有些板块,文中的图片看不到,建议到我的blog浏览文章:http://blog.csdn.net/jemlee2002/ ...
- 第二课 如何在WINDOWS环境下搭建以太坊开发环境
[本文目标] 根据本文指导,可以在WINDOUWS环境下完成以太坊智能合约部署前的比较环境准备. [技术收获] 如何在WINDOW(笔者以WINDOWS 10为例)环境搭建以太坊开发环境,按照以下安装 ...
- resin-pro-4.0.34 服務器在windows环境下的配置
resin-pro-4.0.34 服務器在windows环境下的配置 (轉載请注明作者:icelong) 到caucho網站上http://www.caucho.com/download/ 下載 re ...
最新文章
- Bitcoin 中的挖矿算法(5) 难度值举例说明
- mysql语句orderby_mysql中的orderby_MySQL
- 轻松学习,高效学习,那些年大学玩命的学习方法
- android 聊天功能实现,Android聊天背景功能实现
- JavaScript中子类调用父类方法的实现
- html5json转换为数组,将JSON数组转换为jQuery中的HTML表
- Android fastboot下载模式或reboot流程解析
- 2018年深圳杯论文_2018.5.21/建模日记/深圳杯
- 基于jqUI的日期选择(‘yy-mm-dd’)
- Python+Dlib库实现人脸合成
- Win10更新失败的解决办法
- 7-12 分解质因数 (10分)
- Android产品研发(十六)--开发者选项
- Android audio 三 AudioRecord 分析下
- Linux 系统使用 git 提交代码-- git 的安装及使用(简明教学指南)
- 解决显卡驱动错误43
- 一起动才够嗨!Android CoordinatorLayout 自定义 Behavior
- python报错ValueError: Found input variables with inconsistent numbers of samples: [x,y]
- zookeepr 简介
- nodejs项目云端部署初试
热门文章
- 一个具有多个组件的在线平面图设计 平面图 在线制图
- CuraEngine VS2017
- 阿里云服务器地域暂时无法办理经营性ICP许可证什么意思?
- 银盒子受邀出席银联云闪付大会,获“最佳行业合作”奖
- 解决方案:Zotero+坚果云+Zotfile完成多平台多设备的文献同步
- 你是外包,麻烦不要偷吃零食。。。网友:...
- Encoder-Decoder
- 11111122266666
- 干碳纤维 湿碳纤维_纤维状网站故障不是一种选择
- linux环境下java输出乱码,linux java 输出乱码问题