【大話QT之五】Windows與Linux下文件操作監控的實現

一、需求分析:

        隨着渲染業務的不斷進行,數據傳輸漸漸成爲影響業務時間最大的因素。究其原因就是因爲數據傳輸耗費較長的時間。於是,依託於渲染業務的網盤開發逐漸成爲迫切需要解決的需求。該網盤的實現和當前市場上網盤實現有一些的不同,主要在客戶端與服務器端的操作需要雙向進行,即:用戶在客戶端的操作需要及時同步到服務器端;在服務器端作業渲染生成的文件要及時同步到客戶端。即:用戶不在需要單獨的下載數據,而是在作業運行的同時,渲染就過就會自動同步到客戶端,大大縮短了等待時間。當然,無論是在客戶端還是在服務端都面臨着一個問題,即:實現對文件操作的監控,這裏的文件操作包括:文件(夾)創建、文件(夾)刪除、文件(夾)重命名、文件(夾)移動等操作。除此之外還要能夠同步客戶端文件的修改操作,即:當用戶退出網盤後,修改了原有同步目錄中的文件,當用戶再次啓動網盤時通過一次掃描與md5值的比較能缺確定出哪些文件發生了改動,並將改動後的操作及時同步到服務端。這裏,先將Windows(客戶端實現需要)與Linux(服務端實現需要)下文件監控的實現方法簡要概述。

二、文件監控實現方法分析

      1> Windows下文件監控的實現

            Windows下實現文件監控的原理是利用SHChangeNotifyRegister把指定的窗口添加到系統的消息監視鏈中,從而註冊窗口就可以接收到來自文件系統或Shell的通知了。在繼續向下說明之前,需要解釋一下Windows外殼名字空間(Shell Name Space)的概念。

            外殼名字空間是Windows下的標準文件系統,它大大擴展了Dos文件系統,形成了以”桌面“爲根的單一文件系統樹,原有的C、D盤等目錄樹變成了“我的電腦”這一外殼名字控件子樹的下一級子樹,而像“控制面板”、“回收站”、“網上鄰居”等應用程序以及“打印機”等設備也被虛擬成了外殼名字空間中的節點。爲了區別於Dos中“目錄”的概念,Windows引入了“文件夾”的概念。“文件夾”一般是指外殼名字空間樹中的非葉幾點,既可以是DOS下的目錄,也可以是“控制面板”、“回收站”這類虛擬的目錄。

            新的“路徑”PIDL:外殼對象標識符列表。PIDL是一個元素類型爲ITEMIDLIST結構的數組,數組中元素的個數是未知的,但緊接着數組的末尾的必是一個雙字節的零。每個數組元素代表了外殼名字空間樹中的一層(即一個文件夾或文件),數組中的前一元素代表的是後一元素的父文件夾。由此可見,PIDL實際上就是指向一塊由若干個順序排列的ITEMIDLIST結構組成、並在最後有一個雙字節零的空間的指針。所以PIDL的類型就被Windows定義爲ITEMIDLIST結構的指針。

            DOS中的路徑是一個字符串,但PIDL是一種二進制結構,所以我們不能直接從PIDL中獲知它所代表的到底是哪個文件夾或文件,而必須調用相應的函數把它轉換爲代表路徑的字符串。如果某絕對PIDL是文件系統的一部分,則調用SHGetPathFromIDList函數即可;但如不是,就無法獲得路徑字符串了,因爲DOS中根本就不存在這種路徑。

       總體來說:實現Windows下文件監控的基本流程如下:

              

       上圖是在windows下利用C++實現文件監控的一種方式(根據需要靈活改動),這裏簡要敘述一下:1) 利用DialogBoxParam創建一個模態對話框,進入窗口過程函數,在該窗口函數中根據各種消息來完成我們的操作。這裏模態對話框與非模態對話框的區別之一是因爲它有一套自己的消息泵機制,不需要我們再手動寫消息的接收了(非模態對話框要自己接收消息)。攔截用戶消息,根據各個不同的階段可以加入我們自己的操作,比如初始化等。2) 獲取指定路徑的PIDL:即目標路徑的外殼對象標識符,有了它才能繼續後續的處理,這裏的獲取有兩種方式,一種是利用IFileDialog打開對話框讓用戶選擇(IFileDialog *pfd),從而通過GetResult(IShellItem *psi ; pfs->GetResult(&psi))獲取IShellItem對象;然後利用QueryInterface(IShellItem2 *_psiDrop ; psi->QueryInterface(&_psiDrop))獲取IShellItem2對象;最後利用它就可以獲得PIDL了(利用SHGetIDListFromObject)。3) 最後利用SHChangeNotifyRegister完成最終目標窗體的掛載,從而將一個目錄加入到系統的消息鏈中,從而可以獲取文件系統或Shell中關於文件操作的相關信息。最後將信息解析出來就可以了。

       還有一種方式即直接提供目標文件夾的絕對路徑,由該路徑獲取到PIDL,從而將窗體掛載到系統消息鏈中,注意:如果是在QT中實現的話,可以很輕鬆的獲取到QWidget的窗口句柄。關鍵代碼如下:

void houqd::RegisterWindow()
{
	char absoluteFolderPath[] = "C:\\openssl";


	//! 由文件夾的絕對路徑獲取PIDL:外殼對象標識符列表,即在windows 外殼名字空間 "Shell Name Space"中的表示方法。
	LPITEMIDLIST myFolderPIDL = ParsePidlFromPath(absoluteFolderPath);

	HRESULT res ;
	IShellItem *psi = NULL;

	//! 創建一個IShellItem(interface)對象,IShellItem interface提供了查找一個關於Shell Item相關信息的方法。
	//! IShell Item接口都繼承自IUnknown interface
	res = SHCreateShellItem(NULL, NULL, myFolderPIDL, &psi);
	
	IShellItem2 *ppsi ;

	//! 檢索一個對象上支持的接口的指針
	psi->QueryInterface(&ppsi);

	
	//! =======================================註冊文件監控=============================================
	PIDLIST_ABSOLUTE pidlWatch;
	HRESULT hr = SHGetIDListFromObject(ppsi, &pidlWatch);

	if(SUCCEEDED(hr))
	{
		SHChangeNotifyEntry const entries[] = { pidlWatch, true };

		int const nSources = SHCNRF_ShellLevel | SHCNRF_InterruptLevel | SHCNRF_NewDelivery;

		//! 註冊窗口主函數
		_ulRegister = SHChangeNotifyRegister(_hdlg, nSources, SHCNE_ALLEVENTS, c_notifyMessage, ARRAYSIZE(entries), entries);
		hr = _ulRegister != 0 ? S_OK : E_FAIL;
	}

	//ShowWindow(SW_HIDE);
	//! ====================================================================================
}
ParsePidFromPath 的具體實現如下:

LPITEMIDLIST houqd::ParsePidlFromPath(LPCSTR lpszPath)
{
	//存放以Unicode內碼錶示的路徑字符串的緩衝區
	 OLECHAR szOleChar[MAX_PATH];

	 //“桌面“的IshellFolder接口指針
	 LPSHELLFOLDER lpsfDeskTop;

	 //返回的PIDL
	 LPITEMIDLIST lpifq;

	 ULONG ulEaten, ulAttribs;
	 HRESULT hres;

	 //得到“桌面”的IshellFolderr 接口指針
	 SHGetDesktopFolder(&lpsfDeskTop);

	 //將Ansi字符集的路徑字符串轉換成Unicode字符串,存入szOleChar
	 MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, lpszPath, -1, szOleChar, sizeof(szOleChar));

	 //將szOleChar,中的路徑徑字符串翻譯成相應的PIDL,存入lpifq
	 hres = lpsfDeskTop->ParseDisplayName(NULL, NULL, szOleChar, &ulEaten, &lpifq, &ulAttribs);

	 hres = lpsfDeskTop->Release();

	 //如果翻譯失敗,則返回NULL

	 if(FAILED(hres))
	  return NULL;

	 return lpifq;
}

      2> Linux下文件監控的實現

        Linux下主要是通過inotify實現文件監控。它是一個內核用戶通知用戶空間程序文件系統變化的機制。在用戶狀,inotify通過三個系統調用和在返回的文件描述符上的文件I/O操作來使用.

        1) 使用inotify的第一步是創建inotify的實例:int fd = inotify_init() ; 每一個inotify實例對應一個獨立的排序的隊列。

        2) int wd = inotify_add_watch(fd , file_dir_path , mask);添加一個目錄的監控。

        3) 刪除一個監控:inotify_rm_watch(fd , wd);

       關鍵代碼如下:

if (m_InotifyFd != LHFSMC_FD_UNCREATED_STATE)
        close(m_InotifyFd);

    //! inotify_init()
    if ((m_InotifyFd = inotify_init()) < 0)
    {
        qDebug() << "[error] LHFileSystemMonitor::Start: inotify_init failure.";
        return 0;
    }

    //! 爲m_CreatedDirList中所有保存的目錄創建監控
    if(!CreateWatcherForEachDir(m_CreatedDirList))
    {
        qDebug() << "[error] LHFileSystemMonitor::Start: CreateNotifierForEachDir failure.";
        return 0;
    }
CreateWatcherForEachDir的實現:

int LHFileSystemMonitor::CreateWatcherForEachDir(QStringList &dirLocationList)
{
#ifndef WIN32
    for (QStringList::const_iterator iter = dirLocationList.begin();
         iter != dirLocationList.end();
         ++iter)
    {
        int watchDescriptor;

        if ((watchDescriptor = CreateWatcher(iter->toStdString().c_str())) > 0)
            m_MonitoredObjectList.push_back(LHFSMonitorData(*iter, watchDescriptor));
        else
            qDebug() << "[error] LHFileSystemMonitor::CreateWatcherForEachDir: CreateWatcher for"
                     << *iter << "failure";
    }
#endif
    return 1;
}
CreateWatcher的實現:

int LHFileSystemMonitor::CreateWatcher(const char *fileLocation) const
{
    if (fileLocation == NULL)
        return 0;

    QDir dir(fileLocation);

    if (!dir.exists())
    {
        qDebug() << "[error] LHFileSystemMonitor::CreateNotifier:" << fileLocation << "is not exist!";
        return 0;
    }

    return ((m_InotifyFd != LHFSMC_FD_UNCREATED_STATE) ? inotify_add_watch(m_InotifyFd, fileLocation, LHFSMC_MONITOR_EVENT) : -1);
}
總結:

        以上就是Windows以及Linux下文件監控系統實現的相關思路及代碼,僅僅作爲一個引入,利用這種方式均可以實現對應的功能。

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