监视文件修改
今天在改进文件监视程序时写的框架代码,监视文件修改(采用完成端口和ReadDirectoryChangesW同时在一个线程中监视多个目录,并且能够判断文件是否完全复制完毕)
#define STRICT
#define WINVER 0x0500
#define _WIN32_WINNT 0x0500
#define _WIN32_IE 0x0501
#define _RICHEDIT_VER 0x0200
#define _WIN32_DCOM
#include <CTL/CTL_BASE.HPP>
class P2PFileShare
{
typedef struct
{
OVERLAPPED ov;
BYTE buff[1024];
LPTSTR path;
DWORD flag;
HANDLE handle;
}PATH_OV, *LPPATH_OV;
typedef struct
{
LPTSTR name; // 文件名称
DWORD time; // 通知时间
}FILE_NOTIFY;
public:
P2PFileShare()
: mh_IOCP(NULL)
, mn_OVPtr(0)
, mp_OVPtr(NULL)
, mn_Notify(0)
, mp_Notify(NULL)
{
}
virtual~P2PFileShare()
{
Close(TRUE);
}
private:
// 创建工作线程
HRESULT _CreateWorkerThread();
// 工作线程
#ifndef _WIN32_WCE
static UINT WINAPI _WorkerThreadProc(IN LPVOID pData);
#else
static DWORD WINAPI _WorkerThreadProc(IN LPVOID pData);
#endif // #ifndef _WIN32_WCE
HRESULT _WorkerThreadProc();
public:
HRESULT Start();
VRESULT Close(IN CONST BOOL bWait = FALSE);
public:
// 监视指定目录
HRESULT MonitorPath(IN LPCTSTR sFileName);
// 文件变化通知
LPTSTR GetNotify();
private:
HANDLE mh_IOCP;
MLONG mn_OVPtr;
LPPATH_OV* mp_OVPtr;
MLONG mn_Notify;
FILE_NOTIFY* mp_Notify;
public:
INLINE VRESULT EnterLock() {mo_cs.EnterLock();}
INLINE VRESULT LeaveLock() {mo_cs.LeaveLock();}
private:
MTCSObject mo_cs;
};
// 创建工作线程(根据 CPU 的数量,创建相应数量的工作线程)
HRESULT P2PFileShare::_CreateWorkerThread()
{
HRESULT hr = E_FAIL;
HANDLE hThread;
#ifndef _WIN32_WCE
if((hThread = (HANDLE)_beginthreadex(NULL, 0
, _WorkerThreadProc
, (LPVOID)this, 0, NULL)) == 0)
{
return _doserrno;
}
#else
if((hThread = (HANDLE)::CreateThread(NULL, 0
, _WorkerThreadProc
, (LPVOID)this, 0, &NULL)) == 0)
{
return ::GetLastError();
}
#endif
::CloseHandle(hThread); // 关闭句柄避免资源泄漏
hr = S_OK;
return hr;
}
// 工作线程
#ifndef _WIN32_WCE
UINT P2PFileShare::_WorkerThreadProc(IN LPVOID pData)
#else
DWORD P2PFileShare::_WorkerThreadProc(IN LPVOID pData)
#endif // #ifndef _WIN32_WCE
{
((P2PFileShare*)pData)->_WorkerThreadProc();
#ifndef _WIN32_WCE
_endthreadex(0);
#else
ExitThread(0);
#endif
return 0;
}
// 数据处理线程函数
HRESULT P2PFileShare::_WorkerThreadProc()
{
// 注意: 调用 GetQueuedCompletionStatus 的线程都将被放到完成端口的等待线程队列中
// 完成操作循环
BOOL bSucceed;
DWORD dwBytes;
LPDWORD pCT;
PATH_OV* pOV;
for(;;)
{
bSucceed = ::GetQueuedCompletionStatus(mh_IOCP
, &dwBytes
, (LPDWORD)&pCT
, (LPOVERLAPPED*)&pOV
, INFINITE
);
if(bSucceed)
{
if(NULL == pOV) break; // 退出工作线程
FILE_NOTIFY_INFORMATION * pfiNotifyInfo = (FILE_NOTIFY_INFORMATION*)pOV->buff;
DWORD dwNextEntryOffset;
TCHAR sFileName[1024];
do
{
dwNextEntryOffset = pfiNotifyInfo->NextEntryOffset;
DWORD dwAction = pfiNotifyInfo->Action;
DWORD dwFileNameLength = pfiNotifyInfo->FileNameLength;
CPY_W2T(sFileName, pfiNotifyInfo->FileName, dwFileNameLength/sizeof(WCHAR));
switch(dwAction)
{
case FILE_ACTION_REMOVED: // 文件删除
{
LPTSTR sFullName = new TCHAR[LPTSTRLen(pOV->path) + LPTSTRLen(sFileName) + 1];
if(NULL != sFullName)
{
LPTSTRCpy(sFullName, pOV->path);
LPTSTRCat(sFullName, sFileName);
LPTSTRPrintf(__T("Del %s"n"), sFullName);
delete[] sFullName;
}
}
break;
case FILE_ACTION_ADDED: // 文件替换
{
// 替换文件时只会触发 FILE_ACTION_ADDED, 因此需要手工触发 FILE_ACTION_MODIFIED
LPTSTRPrintf(__T("Add %s"n"), sFileName);
}
case FILE_ACTION_MODIFIED: // 文件修改
{
// 测试文件是否关闭
LPTSTR sFullName = new TCHAR[LPTSTRLen(pOV->path) + LPTSTRLen(sFileName) + 1];
if(NULL != sFullName)
{
LPTSTRCpy(sFullName, pOV->path);
LPTSTRCat(sFullName, sFileName);
HANDLE hFile = ::CreateFile(sFullName
, GENERIC_WRITE
, FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
if(INVALID_HANDLE_VALUE == hFile)
{
HRESULT hr = ::GetLastError();
LPTSTRPrintf(__T("Locked %s %d"n"), sFileName, hr);
}
else
{
::CloseHandle(hFile);
LPTSTRPrintf(__T("Modify %s"n"), sFileName);
LONG i;
EnterLock();
for(i=0;i<mn_Notify;i++)
{
if(LPTSTRCompare(mp_Notify[i].name, sFullName) == 0)
{
mp_Notify[i].time = ::GetTickCount();
break;
}
}
if(i >= mn_Notify)
{
FILE_NOTIFY* pNotify = new FILE_NOTIFY[mn_Notify + 1];
if(NULL != pNotify)
{
if(mn_Notify > 0)
{
::CopyMemory(pNotify, mp_Notify, sizeof(FILE_NOTIFY)*mn_Notify);
delete[] mp_Notify;
}
pNotify[mn_Notify].name = sFullName; sFullName = NULL;
pNotify[mn_Notify].time = ::GetTickCount();
mp_Notify = pNotify;
++mn_Notify;
}
}
LeaveLock();
}
if(NULL != sFullName) delete[] sFullName;
}
}
break;
case FILE_ACTION_RENAMED_OLD_NAME: // 文件改名
{
if(dwNextEntryOffset != 0)
{
pfiNotifyInfo= (FILE_NOTIFY_INFORMATION*)((BYTE*)pfiNotifyInfo + dwNextEntryOffset);
}
dwNextEntryOffset = pfiNotifyInfo->NextEntryOffset;
DWORD dwAction = pfiNotifyInfo->Action;
DWORD dwFileNameLength = pfiNotifyInfo->FileNameLength;
if(dwAction == FILE_ACTION_RENAMED_NEW_NAME)
{
TCHAR sNewName[1024];
CPY_W2T(sNewName, pfiNotifyInfo->FileName, dwFileNameLength/sizeof(WCHAR));
LPTSTRPrintf(__T("Rename %s -> %s"n"), sFileName, sNewName);
}
else
{
continue;
}
}
break;
}
if(dwNextEntryOffset != 0)
{
pfiNotifyInfo= (FILE_NOTIFY_INFORMATION*)((BYTE*)pfiNotifyInfo + dwNextEntryOffset);
}
}while (dwNextEntryOffset != 0);
// 投递目录监视
::ZeroMemory(pOV->buff, 1024);
::ReadDirectoryChangesW( pOV->handle
, pOV->buff
, 1024
, FALSE
, pOV->flag
, NULL
, (OVERLAPPED*)pOV
, NULL
);
}
}
EnterLock();
while(mn_Notify > 0)
{
--mn_Notify;
delete[] mp_Notify[mn_Notify].name;
}
delete[] mp_Notify;
mp_Notify = NULL;
while(mn_OVPtr > 0)
{
::InterlockedDecrement(&mn_OVPtr);
pOV = mp_OVPtr[mn_OVPtr];
::CloseHandle(pOV->handle);
delete[] pOV->path;
delete pOV;
}
delete[] mp_OVPtr;
mp_OVPtr = NULL;
::CloseHandle(mh_IOCP);
mh_IOCP = NULL;
LeaveLock();
return S_OK;
}
HRESULT P2PFileShare::Start()
{
HRESULT hr;
EnterLock();
USP_ASSERT(mh_IOCP == NULL);
// 创建完成端口
mh_IOCP = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
if(NULL == mh_IOCP)
{
hr = ::GetLastError();
}
else
{
hr = _CreateWorkerThread();
if(S_OK != hr)
{
::CloseHandle(mh_IOCP);
mh_IOCP = NULL;
}
}
LeaveLock();
return hr;
}
VRESULT P2PFileShare::Close(IN CONST BOOL bWait)
{
::PostQueuedCompletionStatus(mh_IOCP, 0, NULL, NULL); // 通知工作线程关闭
EnterLock();
// 先取消所有的 IO 操作,否则有可能会有内存问题
PATH_OV* pOV;
for(LONG i=0;i<mn_OVPtr;i++)
{
pOV = mp_OVPtr[i];
::CancelIo(pOV->handle);
}
LeaveLock();
if(bWait)
{
while(mn_OVPtr > 0) ::Sleep(10);
}
}
// 监视共享目录
HRESULT P2PFileShare::MonitorPath(IN LPCTSTR sPath)
{
USP_ASSERT(mh_IOCP != NULL);
if(NULL == mh_IOCP) return E_FAIL;
PATH_OV*pOV = new PATH_OV;
if(NULL == pOV) return E_OUTOFMEMORY;
::ZeroMemory(pOV, sizeof(PATH_OV));
pOV->path = NEW_T2T(sPath);
if(NULL == pOV->path)
{
delete pOV;
}
// 创建目录句柄
pOV->handle = ::CreateFile( pOV->path
, FILE_LIST_DIRECTORY
, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE
, NULL
, OPEN_EXISTING
, FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED
, NULL
);
if(INVALID_HANDLE_VALUE == pOV->handle)
{
delete[] pOV->path;
delete pOV;
return ::GetLastError();
}
// 帮定目录句柄
if(NULL == ::CreateIoCompletionPort(pOV->handle, mh_IOCP, NULL, 0))
{
::CloseHandle(pOV->handle);
delete[] pOV->path;
delete pOV;
return ::GetLastError();
}
// 提交目录监视
pOV->flag = FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME;
BOOL bSucceed = ::ReadDirectoryChangesW( pOV->handle
, pOV->buff
, 1024
, FALSE
, pOV->flag
, NULL
, (OVERLAPPED*)pOV
, NULL
);
if(!bSucceed)
{
::CloseHandle(pOV->handle);
delete[] pOV->path;
delete pOV;
return ::GetLastError();
}
HRESULT hr = S_OK;
LONG i;
EnterLock();
for(i=0;i<mn_OVPtr;i++)
{
if(LPTSTRCompare(mp_OVPtr[i]->path, pOV->path) == 0)
{
hr = S_FALSE;
break;
}
}
if(i >= mn_OVPtr)
{
LPPATH_OV* pOVPtr = new LPPATH_OV[mn_OVPtr + 1];
if(NULL == pOVPtr)
{
hr = E_OUTOFMEMORY;
}
else
{
if(mp_OVPtr != NULL)
{
::CopyMemory(pOVPtr, mp_OVPtr, sizeof(LPPATH_OV)*mn_OVPtr);
delete[] mp_OVPtr;
}
pOVPtr[mn_OVPtr] = pOV;
mp_OVPtr = pOVPtr;
::InterlockedIncrement(&mn_OVPtr);
}
}
LeaveLock();
if(S_OK != hr)
{
::CloseHandle(pOV->handle);
delete[] pOV->path;
delete pOV;
return hr;
}
return S_OK;
}
// 文件变化通知
LPTSTR P2PFileShare::GetNotify()
{
LPTSTR sFileName = NULL;
DWORD nTime = ::GetTickCount();
EnterLock();
for(LONG i=0;i<mn_Notify;i++)
{
if(nTime - mp_Notify[i].time >= 1*1000)
{
sFileName = mp_Notify[i].name;
if(mn_Notify - i > 1)
{
::CopyMemory(mp_Notify + i, mp_Notify + i + 1, (mn_Notify - i - 1)*sizeof(FILE_NOTIFY));
}
--mn_Notify;
if(mn_Notify == 0)
{
delete[] mp_Notify;
mp_Notify = NULL;
}
break;
}
}
LeaveLock();
return sFileName;
}
int _tmain(IN INT nArgc, IN LPCTSTR* psArgv)
{
UNREFERENCED_PARAMETER(nArgc);
UNREFERENCED_PARAMETER(psArgv);
#ifdef UNICODE
CRTSetLocale(); // 设置本地化开关,保证在 UniCode 下可以输出汉字
#endif // UNICODE
ConsoleInit();
HRESULT hr;
P2PFileShare oShare;
hr = oShare.Start();
if(S_OK == hr)
{
hr = oShare.MonitorPath(__T("E:""EPServer""bin""incoming"""));
hr = oShare.MonitorPath(__T("E:""EPServer""bin""incomtmp"""));
AFSP sInput;
for(;;)
{
LPTSTR sFileName = oShare.GetNotify();
if(NULL != sFileName)
{
// 有一个文件已经完全复制完毕
LPTSTRPrintf(__T("Hashing %s"n"), sFileName);
// ...
delete[] sFileName;
}
::Sleep(1000);
// sInput.Attach(ConsoleGetStringNoEcho());
// if(0 == LPTSTRICompare(sInput, __T("exit"))) break;
// else
// if(0 == LPTSTRICompare(sInput, __T("quit"))) break;
}
oShare.Close();
}
ConsoleTerm();
return S_OK == hr ? 0 : -1;
}
#include <CTL/CTL_IMPL.HPP>
/*
Monitor File Change Infomation
---Completion Port Module
Monitor Example 1.0 适用于监控一个目录
Author: yaliao [lya_118@163.com]
*/
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#define MAX_BUFFER 4096
//
//自定义结构,即“完成键”(单句柄数据)
//
typedef struct _PER_IO_CONTEXT
{
OVERLAPPED ol;
HANDLE hDir;
CHAR lpBuffer[MAX_BUFFER];
_PER_IO_CONTEXT* pNext;
}PER_IO_CONTEXT, *PPER_IO_CONTEXT;
PPER_IO_CONTEXT g_pIContext;
typedef BOOL (WINAPI *lpReadDirectoryChangesW)( HANDLE,
LPVOID,
DWORD,
BOOL,
DWORD,
LPDWORD,
LPOVERLAPPED,
LPOVERLAPPED_COMPLETION_ROUTINE);
lpReadDirectoryChangesW ReadDirectoryChangesW = NULL;
HANDLE g_hIocp;
HANDLE g_hDir;
DWORD WINAPI CompletionRoutine(LPVOID lpParam);
void FileActionRoutine(PFILE_NOTIFY_INFORMATION pfi);
TCHAR szDirectory[] = "c:\\";
void main()
{
HANDLE hRet;
HANDLE hThread;
g_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == g_hIocp)
{
cout <<"CreateIoCompletionPort Failed: " <<GetLastError() <<endl;
return;
}
cout <<"monitor directory is: " <<szDirectory <<endl;
g_hDir = CreateFile( szDirectory,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ|
FILE_SHARE_WRITE|
FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS|
FILE_FLAG_OVERLAPPED,
NULL);
if (NULL == g_hDir)
{
cout <<"CreateFile Failed: " <<GetLastError() <<endl;
CloseHandle(g_hIocp);
return;
}
HINSTANCE hInst = LoadLibrary("Kernel32.DLL");
if(!hInst)
{
cout <<"LoadLibrary Failed: " <<GetLastError() <<endl;
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
return;
}
ReadDirectoryChangesW = (lpReadDirectoryChangesW)GetProcAddress(hInst, "ReadDirectoryChangesW");//获取DLL函数入口
FreeLibrary(hInst);
SYSTEM_INFO SysInfo;
GetSystemInfo(&SysInfo);
for (int i=0; i<(int)SysInfo.dwNumberOfProcessors; i++)
{
hThread = CreateThread(NULL, 0, CompletionRoutine, NULL, 0, NULL);
if(NULL == hThread)
{
cout <<"CreateThread Failed: " <<GetLastError() <<endl;
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
return;
}
CloseHandle(hThread);
}
g_pIContext = (PPER_IO_CONTEXT)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PER_IO_CONTEXT));
if(NULL == g_pIContext)
{
cout <<"HeapAlloc Failed: " <<GetLastError() <<endl;
PostQueuedCompletionStatus(g_hIocp, 0, NULL, NULL);
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
return;
}
g_pIContext->hDir = g_hDir;
ZeroMemory(&(g_pIContext->ol), sizeof(OVERLAPPED));
ZeroMemory(g_pIContext->lpBuffer, MAX_BUFFER);
g_pIContext->pNext = NULL;
hRet = CreateIoCompletionPort(g_hDir, g_hIocp, (ULONG_PTR)g_pIContext, 0);
if(NULL == hRet)
{
cout <<"CreateIoCompletionPort Failed: " <<GetLastError() <<endl;
PostQueuedCompletionStatus(g_hIocp, 0, NULL, NULL);
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
HeapFree(GetProcessHeap(), 0, g_pIContext);
return;
}
DWORD nBytes = 0;
BOOL bRet = FALSE;
bRet = ReadDirectoryChangesW(g_pIContext->hDir,
g_pIContext->lpBuffer,
MAX_BUFFER,
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,
&nBytes,
&(g_pIContext->ol),
NULL);
if(!bRet)
{
cout <<"ReadDirectoryChangesW Failed: " <<GetLastError() <<endl;
PostQueuedCompletionStatus(g_hIocp, 0, NULL, NULL);
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
HeapFree(GetProcessHeap(), 0, g_pIContext);
return;
}
cout <<"Enter q to exit: " <<endl;
while(getch() != 'q');
PostQueuedCompletionStatus(g_hIocp, 0, NULL, NULL);
CloseHandle(g_hIocp);
CloseHandle(g_hDir);
HeapFree(GetProcessHeap(), 0, g_pIContext);
cout <<"Main thread will exit " <<endl;
getch();
}
DWORD WINAPI CompletionRoutine(LPVOID lpParam)
{
DWORD dwBytes;
PPER_IO_CONTEXT pIContext = NULL;
LPOVERLAPPED pOL = NULL;
PFILE_NOTIFY_INFORMATION pfi = NULL;
BOOL bRet;
DWORD cbOffset;
DWORD nBytes;
while(true)
{
bRet = GetQueuedCompletionStatus(g_hIocp, &dwBytes,(PULONG_PTR )&pIContext, &pOL, INFINITE);
if(bRet)
{
if(NULL == pIContext)
{
cout <<"CompletionRoutine thread exit " <<endl;
return 0;
}
pfi = (PFILE_NOTIFY_INFORMATION)pIContext->lpBuffer;
do
{
try
{
switch(pfi->Action)
{
case FILE_ACTION_ADDED: //The file was added to the directory.
cout <<"FILE_ACTION_ADDED: ";
FileActionRoutine(pfi);
break;
case FILE_ACTION_REMOVED: //The file was removed from the directory.
cout <<"FILE_ACTION_REMOVED: ";
FileActionRoutine(pfi);
break;
case FILE_ACTION_MODIFIED: //The file was modified. This can be a change in the time stamp or attributes.
cout <<"FILE_ACTION_MODIFIED: ";
FileActionRoutine(pfi);
break;
case FILE_ACTION_RENAMED_OLD_NAME: //The file was renamed and this is the old name.
cout <<"FILE_ACTION_RENAMED_OLD_NAME: ";
FileActionRoutine(pfi);
break;
case FILE_ACTION_RENAMED_NEW_NAME: //The file was renamed and this is the new name.
cout <<"FILE_ACTION_RENAMED_NEW_NAME: ";
FileActionRoutine(pfi);
break;
default:
break;
}
cbOffset = pfi->NextEntryOffset;//一次消息中包含了多个文件变化的信息吗?
pfi = (PFILE_NOTIFY_INFORMATION)((LPBYTE) pfi + cbOffset);
}
catch(...)
{
LPVOID lpMsgBuf;
FormatMessage( //把错误消息格式化
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(), //错误消息
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf, //格式化后的错误消息
0,
NULL
);
cout <<"CompletionRoutine Exception = " <<lpMsgBuf <<endl;
LocalFree(lpMsgBuf);
}
}while(cbOffset);
}
g_pIContext->hDir = g_hDir;
ZeroMemory(&(g_pIContext->ol), sizeof(OVERLAPPED));
ZeroMemory(g_pIContext->lpBuffer, MAX_BUFFER);
g_pIContext->pNext = NULL;
nBytes = 0;
bRet = FALSE;
bRet = ReadDirectoryChangesW(g_pIContext->hDir,
g_pIContext->lpBuffer,
MAX_BUFFER,
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,
&nBytes,
&(g_pIContext->ol),
NULL);
if(!bRet)
{
cout <<"ReadDirectoryChangesW Failed: " <<GetLastError() <<endl;
return 0;
}
}
}
void FileActionRoutine(PFILE_NOTIFY_INFORMATION pfi)
{
TCHAR szFileName[MAX_PATH];
memset(szFileName,'\0',sizeof(szFileName));
WideCharToMultiByte(CP_ACP, //ANSI码
0,
pfi->FileName, //待转换的字符串
pfi->FileNameLength/2,
szFileName, //转换后的字符串
MAX_PATH,
NULL,
NULL);
cout <<szFileName <<endl;
}