您的位置:首页 > 编程语言

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

2014-12-01 00:00 543 查看
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、执行结果





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: