【windows核心編程】IO完成端口(IOCP)復制文件小例


 

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、執行結果

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM