首页 > 代码库 > 【windows核心编程】IO完成端口(IOCP)复制文件小例

【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 2void 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 = http://www.mamicode.com/TEXT("D:\\SourceSoftware\\myeclipse-8.5.0.rar");      LPCTSTR lpDest = TEXT("D:\\SourceSoftware\\myeclipse-8.5.0_copy.rar");*/     /*LPCTSTR lpSrc = http://www.mamicode.com/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 = http://www.mamicode.com/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 = http://www.mamicode.com/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 =http://www.mamicode.com/= 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 =http://www.mamicode.com/= 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、执行结果