8.5 一个文件切割系统的实现
讀寫文件是編程過程中一個很重要的環節。為了消除長時間的讀寫操作對線程造成的阻塞,一般都要在輔助線程中讀寫文件。這就涉及到線程間通信和線程同步的問題。本節將通過實現一個文件切割系統來說明如何將線程封裝到類中,提供線程安全的接口。
讀寫文件是編程過程中一個很重要的環節。為了消除長時間的讀寫操作對線程造成的阻塞,一般都要在輔助線程中讀寫文件。這就涉及到線程間通信和線程同步的問題。本節將通過實現一個文件切割系統來說明如何將線程封裝到類中,提供線程安全的接口。
8.5.1 通信機制
線程間通信的方法很多,如果是工作線程有事情要通知主窗口線程,最好的方法就是發送消息。文件切割系統除了要向主窗口發送基本的“開始工作”和“結束工作”通知消息外,還應該把當前的工作進度告訴主窗口,以便顯示給用戶。為此,CFileCutter類定義了以下3個消息。
// CFileCutter類發給主窗口的通知消息 // FileCutter.h文件 #define WM_CUTTERSTART WM_USER + 100 // wParam == nTatolCount #define WM_CUTTERSTOP WM_USER + 101 // wParam == nExitCode, lParam == nCompletedCount #define WM_CUTTERSTATUS WM_USER + 102 // lParam == nCompletedCount剛開始進行分割(或合并)時,工作線程向主窗口發送WM_CUTTERSTART消息,wParam參數的值為分割以后的文件數量(或待合并的文件數量)。主窗口接收到這個消息后可以設置進度條的取值范圍,提示用戶正在工作等。
工作線程在分割(或合并)文件的過程中,每處理完一個文件就向主窗口發送一個WM_CUTTERSTATUS消息,lParam參數的值為已經完成的文件的總數量。主窗口接收到這個消息后可以更新進度條的位置,向用戶顯示狀態信息等。
停止工作以后,工作線程向主窗口發送WM_CUTTERSTOP消息,lParam參數的值還是已經完成的文件的總數量,其wParam參數的值為工作退出代碼,說明了工作退出的原因。工作退出代碼的定義如下。
class CFileCutter // FileCutter.h文件 { public: // 工作退出代碼 enum ExitCode{ exitSuccess, // 成功完成任務 exitUserForce, // 用戶終止 exitSourceErr, // 源文件出錯 exitDestErr // 目標文件出錯 }; //…………其他成員 };主窗口可以根據不同的退出代碼向用戶顯示是否成功完成任務,或者是出錯的原因。
8.5.2 分割合并機制
分割文件是指將文件分割成指定的大小,并保存到指定的文件夾;合并文件恰好相反,它將文件夾里符合規定的文件讀出來,寫到指定的文件中。
比如,要分割的文件的文件名是film.iso,可以將分割后的文件依次命名為1__film.iso、2__film.iso、3__film.iso等。合并文件的時候則是在用戶提供的文件夾里尋找有“1__”、“2__”、“3__”……標志的文件。
分割文件需要的參數有待分割的文件的名稱、分割以后保存文件的目錄和分割后文件的大小。合并文件需要的參數有待合并的文件所在的文件夾、合并以后保存文件的文件夾。這些參數可以用下面4個變量表示。
// 參數信息 CString m_strSource; CString m_strDest; UINT m_uFileSize; BOOL m_bSplit;在工作線程分割或合并文件時,可以通過下面兩個變量控制它的執行,了解它的狀態。
// 狀態標志 BOOL m_bContinue; // 是否繼續工作 BOOL m_bRunning; // 是否處于工作狀態下面是具體的分割文件的程序代碼。
void CFileCutter::DoSplit() {int nCompleted = 0;CString strSourceFile = m_strSource;CString strDestDir = m_strDest;CFile sourceFile, destFile;// 打開源文件BOOL bOK = sourceFile.Open(strSourceFile,CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary);if(!bOK){// 通知用戶,源文件出錯::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitSourceErr, nCompleted);return;}// 確保目標目錄存在(逐層創建它們)int nPos = -1;while((nPos = strDestDir.Find('\\', nPos+1)) != -1){::CreateDirectory(strDestDir.Left(nPos), NULL);} ::CreateDirectory(strDestDir, NULL);if(strDestDir.Right(1) != '\\')strDestDir += '\\';// 通知用戶,開始分割文件int nTotalFiles = sourceFile.GetLength()/m_uFileSize + 1;::PostMessage(m_hWndNotify, WM_CUTTERSTART, nTotalFiles, TRUE);// 開始去讀源文件,將數據寫入目標文件const int c_page = 4*1024;char buff[c_page];DWORD dwRead;CString sDestName;int nPreCount = 1;UINT uWriteBytes;do{// 創建一個目標文件sDestName.Format("%d__", nPreCount);sDestName += sourceFile.GetFileName();if(!destFile.Open(strDestDir + sDestName, CFile::modeWrite|CFile::modeCreate)){::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitDestErr, nCompleted);sourceFile.Close();return;}// 向目標文件寫數據,直到大小符合用戶的要求,或者源文件讀完uWriteBytes = 0;do{// 首先判斷是否要求終止執行if(!m_bContinue){destFile.Close();sourceFile.Close();if(!m_bExitThread)::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitUserForce, nCompleted);return;}// 進行真正的讀寫操作dwRead = sourceFile.Read(buff, c_page);destFile.Write(buff, dwRead);uWriteBytes += dwRead;}while(dwRead > 0 && uWriteBytes < m_uFileSize);// 關閉這個目標文件destFile.Close();// 通知用戶,當前的狀態信息nCompleted = nPreCount++;::PostMessage(m_hWndNotify, WM_CUTTERSTATUS, 0, nCompleted);}while(dwRead > 0);// 關閉源文件sourceFile.Close();// 通知用戶,工作完成::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitSuccess, nCompleted); }程序它先以只讀方式打開源文件(待分割的文件),如果打開出錯就退出;接著程序逐層創建目標目錄,以確保目標目錄存在;最后程序就開始真正地分割文件了。外層的每次循環,都對應著一個目標文件的創建、寫入和關閉。在創建目標文件前,程序先要構建一個合適的文件名,也就是在原文件名的基礎上加上前面規定的前綴。
下面是合并文件的代碼。
void CFileCutter::DoMerge() {int nCompleted = 0;CString strSourceDir = m_strSource;CString strDestFile = m_strDest;if(strSourceDir.Right(1) != '\\')strSourceDir += '\\';if(strDestFile.Right(1) != '\\')strDestFile += '\\';// 取得源目錄中待合并的文件的文件名稱和數量CString strFileName;int nTotalFiles = 0;CFileFind find;BOOL bRet;if(find.FindFile(strSourceDir + "*.*")){do{bRet = find.FindNextFile();if(find.IsDirectory() && find.IsDots())continue;if(find.GetFileName().Find("__", 0) != -1){nTotalFiles++;strFileName = find.GetFileName();}}while(bRet);}find.Close();if(nTotalFiles == 0){// 通知用戶,源文件出錯::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitSourceErr, nCompleted);return;}// 取得文件名稱strFileName = strFileName.Mid(strFileName.Find("__") + 2);// 確保目標目錄存在(逐層創建它們)int nPos = 0;while((nPos = strDestFile.Find('\\', nPos+1)) != -1){::CreateDirectory(strDestFile.Left(nPos + 1), NULL);}::CreateDirectory(strDestFile, NULL);// 創建目標文件CFile sourceFile, destFile;strDestFile += strFileName;if(!destFile.Open(strDestFile, CFile::modeRead|CFile::modeWrite|CFile::modeCreate)){::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitDestErr, nCompleted);return;}// 通知用戶,開始分割文件::PostMessage(m_hWndNotify, WM_CUTTERSTART, nTotalFiles, nCompleted); // 開始去讀源文件,將數據寫入目標文件const int c_page = 4*1024;char buff[c_page];int nPreCount = 1;CString sSourceName;DWORD dwRead;do{// 打開一個源文件sSourceName.Format("%d__", nPreCount);sSourceName += strFileName;if(!sourceFile.Open(strSourceDir + sSourceName, CFile::modeRead|CFile::shareDenyWrite)){break;}// 將這個源文件中的數據全部寫入目標文件do{if(!m_bContinue){sourceFile.Close();destFile.Close();if(!m_bExitThread)::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitUserForce, nCompleted);return;}dwRead = sourceFile.Read(buff, c_page);destFile.Write(buff, dwRead);}while(dwRead > 0);sourceFile.Close();// 通知用戶,當前的狀態信息nCompleted = nPreCount++;::PostMessage(m_hWndNotify, WM_CUTTERSTATUS, 0, nCompleted);}while(TRUE);// 通知用戶,工作完成::PostMessage(m_hWndNotify, WM_CUTTERSTOP, exitSuccess, nCompleted); }為了計算工作進度,DoMerge首先要查看目標目錄中有多少個待合并的文件。只要文件名中帶有“__”字符串,就認為它是一個符合合并規則的文件。剩下的代碼是分割文件的逆過程,就不再多說了。
8.5.3 接口函數
核心代碼有了,下面研究如何設計類接口成員,這是體現系統友好程度的一個環節。CFileCutter類提供的服務主要是分割指定的文件和合并已分割的文件,用戶可以進行的基本操作有開始分割、開始合并、暫停工作、恢復工作和終止工作。用函數來描述它們如下。
public: // 操作 BOOL StartSplit(LPCTSTR lpszDestDir, LPCTSTR lpszSourceFile, UINT uFileSize); BOOL StartMerge(LPCTSTR lpszDestFile, LPCTSTR lpszSourceDir); BOOL SuspendCutter(); BOOL ResumeCutter(); void StopCutter(); // …………其他成員惟一的狀態信息就是指明當前是否正在為用戶服務。
// 屬性 BOOL IsRunning() const { return m_bRunning; }8.5.4 最終實現
完整的定義CFileCutter類的代碼在FileCutter.h文件中。
// FileCutter.h文件#ifndef __FILECUTTER_H_ #define __FILECUTTER_H_#include <afxwin.h>// CFileCutter類發給主窗口的通知消息 #define WM_CUTTERSTART WM_USER + 100 // wParam == nTatolCount #define WM_CUTTERSTOP WM_USER + 101 // wParam == nExitCode, lParam == nCompletedCount #define WM_CUTTERSTATUS WM_USER + 102 // lParam == nCompletedCountclass CFileCutter { public: // 工作退出代碼enum ExitCode{ exitSuccess, // 成功完成任務exitUserForce, // 用戶終止exitSourceErr, // 源文件出錯exitDestErr // 目標文件出錯};// 構造函數CFileCutter(HWND hWndNotify);// 屬性BOOL IsRunning() const { return m_bRunning; }// 操作BOOL StartSplit(LPCTSTR lpszDestDir, LPCTSTR lpszSourceFile, UINT uFileSize);BOOL StartMerge(LPCTSTR lpszDestFile, LPCTSTR lpszSourceDir);BOOL SuspendCutter();BOOL ResumeCutter();void StopCutter();// 具體實現 public:~CFileCutter();protected:// 重置參數信息和狀態標志void Reset();// 進行真正的分割操作void DoSplit();// 進行真正的合并操作void DoMerge();// 工作線程UINT friend _CutterEntry(LPVOID lpParam);// 參數信息CString m_strSource;CString m_strDest;UINT m_uFileSize;BOOL m_bSplit;// 狀態標志BOOL m_bContinue; // 是否繼續工作BOOL m_bRunning; // 是否處于工作狀態// 同步以上兩組數據CRITICAL_SECTION m_cs; // Data gardprivate:// 對象的生命周期全局有效的數據HWND m_hWndNotify; // 接受消息通知事件的窗口句柄HANDLE m_hWorkEvent; // 通知開始工作的事件對象句柄CWinThread* m_pThread; // 工作線程BOOL m_bSuspend; // 暫停標志BOOL m_bExitThread; // 退出標志 };#endif // __FILECUTTER_H_工作線程是CFileCutter類在構造函數中創建的,其作用是在對象的整個生命周期響應用戶的工作請求,調用CFileCutter對象的DoSplit或DoMerge進行真正的工作。
// 內部工作線程 UINT _CutterEntry(LPVOID lpParam) {// 得到CFileCutter對象的指針CFileCutter* pCutter = (CFileCutter*)lpParam;// 循環處理用戶的工作請求while(::WaitForSingleObject(pCutter->m_hWorkEvent, INFINITE) == WAIT_OBJECT_0 && !pCutter->m_bExitThread){// 設置狀態標志,說明正在工作::EnterCriticalSection(&pCutter->m_cs);pCutter->m_bRunning = TRUE;::LeaveCriticalSection(&pCutter->m_cs);// 開始真正的工作if(pCutter->m_bSplit)pCutter->DoSplit();elsepCutter->DoMerge();// 準備接受新的工作任務pCutter->Reset();}return 0; }類的構造函數要做許多事情,如初始化成員變量、創建等待事件對象、創建工作線程等。而析夠函數又要做對應的清除工作。它們的實現代碼如下。
CFileCutter::CFileCutter(HWND hWndNotify) {// 初始化全局有效變量m_hWndNotify = hWndNotify;m_bExitThread = FALSE;m_bSuspend = FALSE;// 創建等待事件對象m_hWorkEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);// 創建工作線程m_pThread = AfxBeginThread(_CutterEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL);m_pThread->m_bAutoDelete = FALSE;m_pThread->ResumeThread();// 初始化工作期間有效變量// 創建關鍵代碼段::InitializeCriticalSection(&m_cs); Reset(); }void CFileCutter::Reset() {::EnterCriticalSection(&m_cs);// 重置參數信息m_strSource.Empty();m_strDest.Empty();m_uFileSize = 0;m_bSplit = TRUE;// 重置狀態標志m_bContinue = TRUE;m_bRunning = FALSE;::LeaveCriticalSection(&m_cs); }CFileCutter::~CFileCutter() {// 設置結束標志m_bExitThread = TRUE;// 設置強制退出標志::EnterCriticalSection(&m_cs);m_bContinue = FALSE;::LeaveCriticalSection(&m_cs);// 防止線程在m_hWorkEvent事件上等待::SetEvent(m_hWorkEvent);// 確保工作線程結束::WaitForSingleObject(m_pThread->m_hThread, INFINITE);// 釋放所有資源::CloseHandle(m_hWorkEvent);::DeleteCriticalSection(&m_cs); delete m_pThread; }下面是一些接口成員的實現代碼,它們主要通過一些狀態標記實現對工作線程的控制。
BOOL CFileCutter::StartSplit(LPCTSTR lpszDestDir, LPCTSTR lpszSourceFile, UINT uFileSize) {if(m_bRunning)return FALSE;// 保存參數::EnterCriticalSection(&m_cs);m_strSource = lpszSourceFile; m_strDest = lpszDestDir;m_uFileSize = uFileSize;m_bSplit = TRUE;::LeaveCriticalSection(&m_cs);// 通知線程開始工作::SetEvent(m_hWorkEvent);return TRUE; }BOOL CFileCutter::StartMerge(LPCTSTR lpszDestFile, LPCTSTR lpszSourceDir) {if(m_bRunning)return FALSE;// 保存參數::EnterCriticalSection(&m_cs);m_strSource = lpszSourceDir; m_strDest = lpszDestFile;m_bSplit = FALSE;::LeaveCriticalSection(&m_cs);// 通知線程開始工作::SetEvent(m_hWorkEvent);return TRUE; }BOOL CFileCutter::SuspendCutter() {if(!m_bRunning)return FALSE;// 暫停工作線程if(!m_bSuspend){m_pThread->SuspendThread();m_bSuspend = TRUE;}return TRUE; }BOOL CFileCutter::ResumeCutter() {if(!m_bRunning)return FALSE;// 喚醒工作線程if(m_bSuspend){m_pThread->ResumeThread();m_bSuspend = FALSE;}return TRUE; }void CFileCutter::StopCutter() {// 設置強制退出標志::EnterCriticalSection(&m_cs);m_bContinue = FALSE;::LeaveCriticalSection(&m_cs);// 防止線程處于暫停狀態ResumeCutter(); }到此,文件切割系統就完全實現了。下一節將以此為基礎寫一個實用的文件切割程序。
總結
以上是生活随笔為你收集整理的8.5 一个文件切割系统的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 主攻互动娱乐和视频自媒体,新浪SHOW是
- 下一篇: 新浪微博百万用户分布式压测实践手记