1、演示內容
文件復制
2、提要
復制大文件時,使用FILE_FLAG_NO_BUFFERING標志
同時需要注意:
讀寫文件的偏移地址為 磁盤扇區 的整數倍
讀寫文件的字節數為 磁盤扇區 的整數倍
讀文件到的緩沖區在進程地址空間中的地址為 磁盤扇區 的整數倍
3、JUST CODING
#include "stdafx.h" #include <Windows.h> #include <process.h> #include <iostream>
using namespace std; //完成鍵
#define CK_READ 1
#define CK_WRITE 2
void ShowErrMsg(LPCSTR lpMsg); //傳給線程函數的參數
typedef struct _tagThreadParam { HANDLE hIOCP; //IOCP
LPVOID lpAddr; //讀入的內存地址
LARGE_INTEGER liFileSize; //源文件大小
size_t nDataBlockSize; //每次讀寫的數據塊大小
HANDLE hSrc; //源文件
HANDLE hDest; //目的文件
LPOVERLAPPED lpOLPSrc; //源文件的OVERLAPPED結構指針
LPOVERLAPPED lpOLPDest; //目的文件的OVERLAPPED結構指針
}ThreadParam, *LPTHREADPARAM; int _tmain(int argc, _TCHAR* argv[]) { /*LPCTSTR lpSrc = TEXT("D:\\SourceSoftware\\myeclipse-8.5.0.rar"); LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\myeclipse-8.5.0_copy.rar");*/
/*LPCTSTR lpSrc = TEXT("D:\\SourceSoftware\\VS2010\\cn_visual_studio_2010_ultimate_x86_dvd_532347.iso"); LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\VS2010\\cn_visual_studio_2010_ultimate_x86_dvd_532347_copy.iso");*/
/*LPCTSTR lpSrc = TEXT("D:\\SourceSoftware\\SQLServer2008\\cn_sql_server_2008_r2_developer_x86_x64_ia64_dvd_522724.iso"); LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\SQLServer2008\\cn_sql_server_2008_r2_developer_x86_x64_ia64_dvd_522724_copy.iso"); */
/*LPCTSTR lpSrc = TEXT("D:\\SourceSoftware\\SQLServer2008\\cn_sql_server_2008_r2_developer_x86_x64_ia64_dvd_522724.rar"); LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\SQLServer2008\\cn_sql_server_2008_r2_developer_x86_x64_ia64_dvd_522724_copy.rar");*/ LPCTSTR lpSrc = TEXT("D:\\SourceSoftware\\VS2012旗艦版\\VS2012_ULT_chs.iso"); LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\VS2012旗艦版\\VS2012_ULT_chs_copy.iso"); HANDLE hSrcFile = INVALID_HANDLE_VALUE; //源文件句柄
HANDLE hDestFile = INVALID_HANDLE_VALUE; //目標文件句柄
HANDLE hIOCP = NULL; //IOCP
LPVOID lpAddr = NULL; //申請內存地址
__try { cout << endl << "開始打開源文件" <<endl; //源文件
hSrcFile = CreateFile( lpSrc, //源文件
GENERIC_READ, //讀模式
FILE_SHARE_READ, //讀共享
NULL, //安全屬性
OPEN_EXISTING, //必須存在
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,//異步 | 不用緩存
NULL //文件模板為空
); if(INVALID_HANDLE_VALUE == hSrcFile) { ShowErrMsg("源文件打開錯誤"); return -1; } cout << endl << "開始打開目的文件" << endl; //目的文件
hDestFile = CreateFile( lpDest, //目的文件
GENERIC_WRITE, //寫模式
0, //獨占訪問
NULL, //安全屬性
CREATE_ALWAYS, //總是創建
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, //異步 | 不用緩存
hSrcFile //文件屬性同源文件
); if (INVALID_HANDLE_VALUE == hDestFile) { ShowErrMsg("目的文件打開錯誤"); return -2; } cout << endl << "開始獲取文件尺寸" << endl; //源文件尺寸
LARGE_INTEGER liFileSize; BOOL bRet = GetFileSizeEx(hSrcFile, &liFileSize); if (FALSE == bRet) { ShowErrMsg("獲取源文件尺寸失敗"); return -3; } cout << endl << "開始用源文件尺寸設置目的文件大小" << endl; //設置目的文件指針位置為源文件尺寸 並 設置文件尾
BOOL bRet2 = SetFilePointerEx(hDestFile, liFileSize, NULL, FILE_BEGIN); BOOL bRet3 = SetEndOfFile(hDestFile); if (FALSE == bRet2 || FALSE == bRet3) { ShowErrMsg("設置目的文件尺寸失敗"); return -4; } cout << endl << "開始獲取磁盤扇區大小 和 系統信息" << endl; SYSTEM_INFO sysInfo = {0}; GetSystemInfo(&sysInfo); DWORD dwBytesPerSector = 0UL; bRet = GetDiskFreeSpace(TEXT("D:"), NULL, &dwBytesPerSector, NULL, NULL); if (FALSE == bRet) { ShowErrMsg("開始獲取磁盤扇區大小 錯誤"); return -5; } //讀
OVERLAPPED ovlpRead; ovlpRead.Offset = 0; ovlpRead.OffsetHigh = 0; ovlpRead.hEvent = NULL; //寫
OVERLAPPED ovlpWrite; ovlpWrite.Offset = 0; ovlpWrite.OffsetHigh = 0; ovlpWrite.hEvent = NULL; //創建IOCP 並和 文件關聯
hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, sysInfo.dwNumberOfProcessors); if (NULL == hIOCP) { DWORD dwErr = GetLastError(); if (ERROR_ALREADY_EXISTS != dwErr) { ShowErrMsg("創建IOCP 失敗"); return -6; } } hIOCP = CreateIoCompletionPort(hSrcFile, hIOCP, CK_READ, sysInfo.dwNumberOfProcessors); hIOCP = CreateIoCompletionPort(hDestFile, hIOCP, CK_WRITE, sysInfo.dwNumberOfProcessors); //申請扇區大小的5倍的內存
size_t sizeMAX = dwBytesPerSector * 1024 * 64 * 2; //512K * 64 * 2
size_t sizeMIN = dwBytesPerSector * 1024 * 64 * 2; //申請內存
lpAddr = VirtualAlloc(NULL, sizeMAX, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (NULL == lpAddr) { ShowErrMsg("申請內存錯誤"); return -7; } //先往IOCP的完成隊列插入一個 寫完成 項 //寫0字節
PostQueuedCompletionStatus( hIOCP, //IOCP
0, //GetQueuedCompletionStatus取到的傳送字節為0
CK_WRITE, //寫操作
&ovlpWrite //寫OVERLAPPED
); DWORD dwBytesTrans = 0; //傳輸字節數
ULONG_PTR ulCompleteKey = 0; //完成鍵
LPOVERLAPPED lpOverlapped = NULL; //OVERLAPPED結構
BOOL bLastTime = FALSE; //最后一個讀操作
int i = 0; int j = 0; int nCountZero = 0; //計數
/************************************************************************/
/* 因為前一次只是往IOCP的完成隊列插入了一項【寫完成】,而並非真的寫 只是讓下面的代碼從 【讀操作】開始, 執行序列為: 讀-寫, 讀-寫, ... ,讀-寫 當每個【讀操作】完成時:把緩沖區中的數據寫入【目的文件】,並更新【源文件】的偏移量 當每個【寫操作】完成時:更新【目的文件】的偏移量, 同時,因為操作序列是寫操作在后,因此寫操作完成后,根據更新后的【源文件】的偏移量 和【源文件】大小做比較,如果大於等於源文件大小,則說明這是最后一次讀取操作,則當下一次 寫操作完成時 退出循環。 如果當前【源文件偏移量】沒有達到【源文件大小】則再次從【源文件】 中讀取數據進緩沖區, /************************************************************************/
while(TRUE) { BOOL bRet = GetQueuedCompletionStatus(hIOCP, &dwBytesTrans, &ulCompleteKey, &lpOverlapped, INFINITE); if (FALSE == bRet) { DWORD dwErr = GetLastError(); if (NULL != lpOverlapped) { ShowErrMsg("線程函數返回錯誤, 錯誤原因:"); cout << dwErr <<endl; break; } //if
else { if (ERROR_TIMEOUT == dwErr) { ShowErrMsg("等待超時"); } else { ShowErrMsg("線程函數返回錯誤, 錯誤原因2:"); cout << dwErr <<endl; } continue; } //else
} //if //讀操作完成
if (ulCompleteKey == CK_READ) { cout << endl << "-------------第 " << ++ i << " 次操作完成,開始寫文件 ---------------- "<<endl; WriteFile(hDestFile, lpAddr, sizeMIN, NULL, &ovlpWrite); //讀操作完成 更新 源文件的偏移量
LARGE_INTEGER liSrcFile; liSrcFile.QuadPart = dwBytesTrans; ovlpRead.Offset += liSrcFile.LowPart; ovlpRead.OffsetHigh += liSrcFile.HighPart; } //if //寫操作完成
else if (ulCompleteKey == CK_WRITE) { //寫操作完成, 更新目的文件的偏移量
LARGE_INTEGER liDestFile; liDestFile.QuadPart = dwBytesTrans; ovlpWrite.Offset += liDestFile.LowPart; ovlpWrite.OffsetHigh += liDestFile.HighPart; //當前源文件的偏移量
LARGE_INTEGER liTemp; liTemp.LowPart = ovlpRead.Offset; liTemp.HighPart = ovlpRead.OffsetHigh; //當前文件偏移是超過文件大小
if (liTemp.QuadPart >= liFileSize.QuadPart) { break; } cout << endl << "*************第 " << ++ j << " 次讀寫操作完成,開始讀文件 ***************"<<endl; ReadFile(hSrcFile, lpAddr, sizeMIN, NULL, &ovlpRead); } //else if
} //while
SetFilePointerEx(hDestFile, liFileSize, NULL, FILE_BEGIN); SetEndOfFile(hDestFile); cout << endl << " $$$$$$$$$$$$$$$$$$$$$ 操作完成 $$$$$$$$$$$$$$$$$" <<endl; } __finally { cout << endl << "清理資源" <<endl; if (INVALID_HANDLE_VALUE != hSrcFile) CloseHandle(hSrcFile); hSrcFile = INVALID_HANDLE_VALUE; if(INVALID_HANDLE_VALUE != hDestFile) CloseHandle(hDestFile); hDestFile = INVALID_HANDLE_VALUE; if(NULL != lpAddr) VirtualFree(lpAddr, 0, MEM_RELEASE | MEM_DECOMMIT); lpAddr = NULL; } cout << endl << endl; return 0; } void ShowErrMsg(LPCSTR lpMsg){ cout << endl << "Some error happened : " << lpMsg << "\n"; }
4、細節和問題
過程中發現:某個文件的復制進入死循環,判斷break退出while的條件永遠不成立,即【目的文件的偏移量】沒有達到【源文件的大小】這一條件,單步過程中發現是如下問題
另外:關於讀寫邏輯的問題,一開始是在收到CK_WRITE的時候更新【源文件】偏移量,在收到CK_READ時更新【目的文件】的偏移量,而且發現網上也有這么做的,后來經過折騰發現邏輯有點問題,反過來比較合理,
即 收到CK_WRITE時更新【目的文件偏移量】,收到CK_READ時更新【源文件偏移量】。
5、執行結果