文件監視開發技術小結

最近在用java寫監視文件系統的東東,特對C++和Java下的不同實現方法做一小結。

1.Java環境下

很多人都說用文件輪詢HashTable,然後如何如何比較,這種方法效率極爲低下,還會造成無謂的磁盤讀寫。好了JDK 7中提供了java.nio.file大家可以通過 WatchService 來實現對文件的事件的監聽。千萬記得在JDK 7下哈,現在的JDK7的預覽版發佈到 jdk-7-ea-bin-b84-windows-i586-18_feb_2010.exe了。有興趣的哥們可以把這個程序拿下去學習學習。

該段代碼來自於 Sun(準確的該說是Oracle了) http://java.sun.com/docs/books/tutorial/essential/io/notification.html

/*
* Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*   - Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*
*   - Redistributions in binary form must reproduce the above copyright
*     notice, this list of conditions and the following disclaimer in the
*     documentation and/or other materials provided with the distribution.
*
*   - Neither the name of Sun Microsystems nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import java.nio.file.*;
import static java.nio.file.StandardWatchEventKind.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

/**
* Example to watch a directory (or tree) for changes to files.
*/

public class WatchDir {

    private final WatchService watcher;
    private final Map keys;
    private final boolean recursive;
    private boolean trace = false;

    @SuppressWarnings("unchecked")
    static WatchEvent cast(WatchEvent<?> event) {
        return (WatchEvent)event;
    }

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            FileRef prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s/n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s/n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir) {
                try {
                    register(dir);
                } catch (IOException x) {
                    throw new IOError(x);
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Creates a WatchService and registers the given directory
     */
    WatchDir(Path dir, boolean recursive) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap();
        this.recursive = recursive;

        if (recursive) {
            System.out.format("Scanning %s .../n", dir);
            registerAll(dir);
            System.out.println("Done.");
        } else {
            register(dir);
        }

        // enable trace after initial registration
        this.trace = true;
    }

    /**
     * Process all events for keys queued to the watcher
     */
    void processEvents() {
        for (;;) {

            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }

            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("WatchKey not recognized!!");
                continue;
            }

            for (WatchEvent<?> event: key.pollEvents()) {
                WatchEvent.Kind kind = event.kind();

                // TBD - provide example of how OVERFLOW event is handled
                if (kind == OVERFLOW) {
                    continue;
                }

                // Context for directory entry event is the file name of entry
                WatchEvent ev = cast(event);
                Path name = ev.context();
                Path child = dir.resolve(name);

                // print out event
                System.out.format("%s: %s/n", event.kind().name(), child);

                // if directory is created, and watching recursively, then
                // register it and its sub-directories
                if (recursive && (kind == ENTRY_CREATE)) {
                    try {
                        if (Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).isDirectory()) {
                            registerAll(child);
                        }
                    } catch (IOException x) {
                        // ignore to keep sample readbale
                    }
                }
            }

            // reset key and remove from set if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);

                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }

    static void usage() {
        System.err.println("usage: java WatchDir [-r] dir");
        System.exit(-1);
    }

    public static void main(String[] args) throws IOException {
        // parse arguments
        if (args.length == 0 || args.length > 2)
            usage();
        boolean recursive = false;
        int dirArg = 0;
        if (args[0].equals("-r")) {
            if (args.length < 2)
                usage();
            recursive = true;
            dirArg++;
        }

        // register directory and process its events
        Path dir = Paths.get(args[dirArg]);
        new WatchDir(dir, recursive).processEvents();
    }
}
2.C++環境下

其實從操作系統層面能夠監聽到文件操作事件是最好的了。

在windows環境下呢可以通過用拷貝鉤子實現對文件夾的監控。

可參考網頁 http://ccrun.com/article.asp?i=601&d=0z20f4

ICopyHook是一個用於創建拷貝鉤子處理程序COM接口,它決定一個文件夾或者打印機對象是否可以被移動,拷貝,重命名或刪除。Shell在執行這些操作之前,會調用ICopyHook接口的CopyCallback方法對它們進行驗證。CopyCallback返回一個int值指示Shell是否應該繼續執行這個操作。返回值IDYES表示繼續,而返回值IDNO和IDCANCEL則表示終止。

一個文件夾對象可以安裝多個拷貝鉤子處理程序。如果出現這種情況,Shell會依次調用每個處理程序。只有當每個處理程序都返回IDYES時,Shell才真正執行用戶請求的操作。

拷貝鉤子處理程序的作用是在上述四種操作執行前對它們進行驗證,但是Shell並不會把操作的結果通知給拷貝鉤子處理程序。而windows提供的API函數FindFirstChangeNotification和FindNextChangeNotification卻可以實現這個功能。因此,只有把這種兩種方法結合起來,才能對一個文件夾的狀態進行完全的監控。

拷貝鉤子處理程序實現並不困難,首先創建一個作爲進程內組件的COM對象,它只需要暴露一個ICopyHook接口(當然還有IUnknown)。然後用regsrv32.exe註冊這個COM組件。最後一步是向Shell註冊你的這個拷貝鉤子處理程序,方法是在註冊表HKEY_CLASSES_ROOT/Directory/Shellex/CopyHookHandlers下創建一個名稱任意的sub key,在此sub key中創建一個類型爲REG_SZ的項並將你的COM對象的CLSID作爲它的默認值就可以了。

下面就是一個拷貝鉤子的實現程序(注:以下代碼經老妖改動並添加了詳細操作過程,在BCB6中成功編譯並通過測試)

2.1. 從ICopyHook接口創建TCopyHook,從IClassFactory接口創建TClassFactory:

// TCopyHook.h
// TCopyHook類實現了ICopyHook接口,TClassFactory實現了IClassFactory接口
//---------------------------------------------------------------------------
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//---------------------------------------------------------------------------
class TCopyHook: public ICopyHook
{
public:
    TCopyHook():m_refcnt(0) {}
    STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
            LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
            LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
    int m_refcnt;
};
//---------------------------------------------------------------------------
class TClassFactory : public IClassFactory
{
public:
    TClassFactory():m_refcnt(0) {}
    STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
    STDMETHODIMP LockServer(BOOL fLock);
private:
    int m_refcnt;
};

// TCopyHook.cpp
// TCopyHook對象和TClassFactory對象的實現文件
#include
#include "TCopyHook.h"
//---------------------------------------------------------------------------
extern LONG nLocks;          // 對象計數,用於DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
    if(m_refcnt == 0)
        nLocks++;
    m_refcnt++;
    return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TCopyHook::Release()
{
    int nNewCnt = --m_refcnt;
    if(nNewCnt <= 0)
    {
        nLocks--;
        delete this;
    }
    return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
    if(dwIID == IID_IUnknown)
        *ppvObject = static_cast<IUnknown*>(this);
    else
        if(dwIID == IID_IShellCopyHook)
            *ppvObject = static_cast(this);
        else
            return E_NOINTERFACE;
    reinterpret_cast(*ppvObject)->AddRef();
    return S_OK;
}
//---------------------------------------------------------------------------
// 這就是CopyCallback方法,拷貝鉤子的所有功能由它實現。參數的具體值參看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
        LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
        LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
    char szMessage[MAX_PATH+14];
    sprintf(szMessage, "對%s進行的操作,是否繼續?", pszSrcFile);
    return MessageBox(NULL, szMessage, "確認", MB_YESNO | MB_ICONEXCLAMATION);
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::AddRef()
{
    if(m_refcnt==0)
        nLocks++;
    m_refcnt++;
    return m_refcnt;
}
//---------------------------------------------------------------------------
ULONG __stdcall TClassFactory::Release()
{

    int nNewCnt = --m_refcnt;

    if(nNewCnt <= 0)
    {
        nLocks--;
        delete this;
    }
    return nNewCnt;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
    if(dwIID == IID_IUnknown)
        *ppvObject = static_cast<IUnknown*>(this);
    else
        if(dwIID == IID_IClassFactory)
            *ppvObject = static_cast(this);
        else
            return E_NOINTERFACE;
    reinterpret_cast(*ppvObject)->AddRef();
    return S_OK;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
        REFIID riid, void** ppvObj)
{
    if(pUnkownOuter != NULL)
        return CLASS_E_NOAGGREGATION;
    TCopyHook *pObj = new TCopyHook;
    pObj->AddRef();
    HRESULT hr = pObj->QueryInterface(riid, ppvObj);
    pObj->Release();
    return hr;
}
//---------------------------------------------------------------------------
HRESULT __stdcall TClassFactory::LockServer(BOOL fLock)
{
    if(fLock)
        nLocks++;
    else
        nLocks--;
    return S_OK;
}

2.2. 在BCB中New-->ActiveX-->ActiveX Library,然後添加相應代碼。

   //----------------------------------------------------------------
   // 原作: webber84
// 本文轉自 C++Builder研究 - http://www.ccrun.com/article.asp?i=601&d=0z20f4
   // 修改: ccrun(老妖)(www.ccrun.com)
   // 歡迎訪問C++ Builder 研究:http://www.ccrun.com
   // 爲防不負責任的轉載者,此處加上原作者及修改者信息,請見諒。
   //----------------------------------------------------------------
   以下是修改後的Project1.cpp,大家可以直接copy過去。:
//$$---- axlib proj source ---- (stAXLibProjectSource)
#define NO_WIN32_LEAN_AND_MEAN
#include
#pragma hdrstop
#include
#include
#include
#include "TCopyHook.h"

#pragma package(smart_init)
TComModule  Project1Module;
TComModule &_Module = Project1Module;

// 這是要添加到註冊表中的項,注意如果你要使用這段代碼,應該用UUIDGEN.exe生成一
// 個新的CLSID。
const char* szRegTable[][3]=
{
    {"CLSID//{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}", 0, "CopyHook"},
    {"CLSID//{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}//InProcServer32", 0, (const char*)-1},
    {"CLSID//{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}//InProcServer32", "ThreadingModel", "Apartment"},
    {"CLSID//{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}//ProgID", 0, "webber84.CopyHook.1"},
    {"webber84.CopyHook.1", 0, "CopyHook"},
    {"webber84.CopyHook.1//CLSID", 0, "{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}"}
};

HMODULE hInstance = NULL;
LONG nLocks = 0;
//---------------------------------------------------------------------------
// The ATL Object map holds an array of _ATL_OBJMAP_ENTRY structures that
// described the objects of your OLE server. The MAP is handed to your
// project's CComModule-derived _Module object via the Init method.
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
//---------------------------------------------------------------------------
// Entry point of your Server invoked by Windows for processes or threads are
// initialized or terminated.
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
    if(reason == DLL_PROCESS_ATTACH)
        hInstance = (HMODULE)hinst;
    return TRUE;
}
//---------------------------------------------------------------------------
// _Module.Term is typically invoked from the DLL_PROCESS_DETACH of your
// DllEntryPoint. However, this may result in an incorrect shutdown sequence.
// Instead an Exit routine is setup to invoke the cleanup routine
// CComModule::Term.
void ModuleTerm(void)
{
    _Module.Term();
}
#pragma exit ModuleTerm 63
//---------------------------------------------------------------------------
// Entry point of your Server invoked to inquire whether the DLL is no
// longer in use and should be unloaded.
STDAPI __export DllCanUnloadNow(void)
{
    return nLocks == 0? S_OK: S_FALSE;
}
//---------------------------------------------------------------------------
// Entry point of your Server allowing OLE to retrieve a class object from
// your Server
STDAPI __export DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    HRESULT hr = E_OUTOFMEMORY;
    *ppv = NULL;
    TClassFactory *pClassFactory = new TClassFactory;
    if(pClassFactory != NULL)
        hr = pClassFactory->QueryInterface(riid, ppv);
    return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to create
// registry entries for all classes supported by the module
STDAPI __export DllRegisterServer(void)
{
    HRESULT hr = S_OK;
    int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
    char szDllPath[MAX_PATH];
    GetModuleFileName(hInstance, szDllPath, MAX_PATH);
    for(int i=0; i    {
        const char *szKeyName = szRegTable[i][0];
        const char *szValueName = szRegTable[i][1];
        const char *szValue = szRegTable[i][2];
        if(szValue == (const char*) - 1)
            szValue = szDllPath;
        HKEY hKey;
        LONG lReturn = RegCreateKey(HKEY_CLASSES_ROOT, szKeyName, &hKey);
        if(lReturn == ERROR_SUCCESS)
        {
            RegSetValueEx(hKey, szValueName, 0, REG_SZ,
                    (const BYTE*)szValue, strlen(szValue)+1);
            RegCloseKey(hKey);
        }
        if(lReturn != ERROR_SUCCESS)
        {
            hr = SELFREG_E_CLASS;
            DllUnregisterServer();
        }
    }
    return hr;
}
//---------------------------------------------------------------------------
// Entry point of your Server invoked to instruct the server to remove
// all registry entries created through DllRegisterServer.
STDAPI __export DllUnregisterServer(void)
{
    HRESULT hr = S_OK;
    LONG lReturn = 0;
    int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
    for(int i=nItems-1; i>=0; i--)
    {
        const char *szKeyName = szRegTable[i][0];
        if((i == nItems-1) || stricmp(szRegTable[i+1][0], szKeyName) != 0)
            lReturn = RegDeleteKey(HKEY_CLASSES_ROOT, szKeyName);
        if(lReturn != ERROR_SUCCESS)
            hr = SELFREG_E_CLASS;
    }
    return hr;
}
//---------------------------------------------------------------------------

2.3. 在BCB的IDE環境中,選擇菜單的Project-->Add to Project-->找到剛纔創建的TCopyHook.cpp-->OK
   編譯工程。如果沒有錯誤,將生成Project1.dll。

2.4. 修改註冊表:
   在HKEY_CLASSES_ROOT/Directory/shellex/CopyHookHandlers/下新建一個項,命名爲Test,更改其默認值爲{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735},老妖注:這裏的默認值應該和project1.cpp中的相同註冊項目相同。
2.5. 註冊COM組件:
   運行regsvr32.exe 路徑/project.dll,點擊確定後不要理會再彈出的錯誤窗口。重新啓動計算機,試着copy一個文件夾,當paste的時候,效果出來了,嘿嘿。自己試試吧。

源代碼下載:
http://www.ccrun.com/cctools/MyCopyHook.rar

在Linux環境下呢可以通過用inotify 監視文件系統的變化。

可參考網頁 http://blog.csdn.net/rstevens/archive/2008/05/12/2438567.aspx

可以用 Linux 2.6 kenel 提供的 inotify 機制來監控文件系統的變化,例如增加文件,刪除文件,修改文件等

下面是一個例子程序,可用來監控一個目錄,如果在此目錄下進行創建文件、刪除文件、修改文件的操作,會有提示信息打印到屏幕上

#include
#include
#include
#include
#include
#include

#define BUF_LEN   4096

int main(int argc, char **argv)
...{

    if(argc < 2) ...{
        printf("Usage: %s [directory] ", argv[0]);
        return -1;
    }

    int fd;

    fd = inotify_init();

    if(fd == -1) ...{
        printf("failed in inotify_init, %d ", errno);
        return -1;
    }

    int wd1, wd2;   
ADD_AGAIN:

    wd1 = inotify_add_watch(fd, argv[1], IN_DELETE|IN_MODIFY|IN_CREATE|IN_IGNORED);
    if(wd1 == -1) ...{
        perror("inotify_add_watch");
        return -1;
    } else
        printf("new watch fd %d ", wd1);
/**//*
    wd2 = inotify_add_watch(fd, "/etc", IN_ACCESS|IN_MODIFY);
    if(wd2 == -1) {
        perror("inotify_add_watch");
        return -1;
    }
*/

    char buf[BUF_LEN] __attribute__ ((aligned(4)));
    ssize_t len, i = 0;

    unsigned int queue_len;
    int ret;

while (1) ...{
    i = 0;
    ret = ioctl(fd, FIONREAD, &queue_len);
    if(ret < 0) ...{
        perror("ioctl");
    }

    len = read(fd, buf, BUF_LEN);

    while(i < len) ...{
        struct inotify_event *event = (struct inotify_event*)&buf[i];
        printf("wd=%d mask=%d cookie=%d len=%d dir=%s ",
            event->wd, event->mask, event->cookie, event->len, (event->mask & IN_ISDIR) ? "yes" : "no");
        if(event->len)
            printf("name=%s ", event->name);
        if(event->mask & IN_IGNORED) ...{
            printf("ignored, add again ");
            if(0 != inotify_rm_watch(fd, event->wd))
                perror("inotify_rm_watch");
            goto ADD_AGAIN;
        }
        i += sizeof(struct inotify_event) + event->len;
    }
}

    return 0;
}

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/cnhome/archive/2010/03/03/5342920.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章