Windows 异步IO完成端口实战-CopyFile
在学习了完成端口机制之后, 写一个使用完成端口来进行拷贝文件的小例子
// HadesCopyFile.cpp : 定义控制台应用程序的入口点。
//
#include <Windows.h>
#include <iostream>
#define IOCP_KEY_READ 1
#define IOCP_KEY_WRITE 2
INT _tmain(INT argc, TCHAR* argv[])
{
INT nRet = -1;
BOOL bIsOk = FALSE;
setlocale(LC_CTYPE, "");
HANDLE hSrcFile = INVALID_HANDLE_VALUE, hDestFile = INVALID_HANDLE_VALUE;
LPVOID lpAddr = NULL;
do
{
if (argc != 3)
{
_tprintf(TEXT("参数不足!\n")
TEXT("\t用法: %s srcFilePath destFilePath\n") \
TEXT("\t举例: %s C:\\1.exe D:\\2.exe参数不足!\n") \
TEXT("\n\n\t\t花心胡萝卜工作室\n"),
argv[0],
argv[0]
);
bIsOk = TRUE;
nRet = 0;
break;
}
LPCTSTR lpszSrcFilePath = argv[1];
LPCTSTR lpszDestFilePath = argv[2];
hSrcFile = CreateFile(
lpszSrcFilePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
NULL);
if (INVALID_HANDLE_VALUE == hSrcFile)
break;
hDestFile = CreateFile(
lpszDestFilePath,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, // 异步方式 + 无缓存模式
hSrcFile);
if (INVALID_HANDLE_VALUE == hDestFile)
break;
LARGE_INTEGER liFileSize = { 0 };
if (!GetFileSizeEx(hSrcFile, &liFileSize))
break;
if (!SetFilePointerEx(hDestFile, liFileSize, NULL, FILE_BEGIN))
break;
if (!SetEndOfFile(hDestFile))
break;
// 如果使用了 FILE_FLAG_NO_BUFFERING, 必须按照磁盘扇区为基础, 以它的整数倍进行操作
DWORD dwBytePerSector = 0;
TCHAR szDriver[_MAX_DRIVE] = { 0 };
_tsplitpath_s(lpszDestFilePath, szDriver, _MAX_DRIVE, NULL, 0, NULL, 0, NULL, 0);
if (!GetDiskFreeSpace(szDriver, NULL, &dwBytePerSector, NULL, NULL))
break;
// 获取系统信息
SYSTEM_INFO sysInfo = { 0 };
GetSystemInfo(&sysInfo);
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, sysInfo.dwNumberOfProcessors);
if (!hIOCP)
if (ERROR_ALIAS_EXISTS != GetLastError())
break;
hIOCP = CreateIoCompletionPort(hSrcFile, hIOCP, IOCP_KEY_READ, sysInfo.dwNumberOfProcessors);
hIOCP = CreateIoCompletionPort(hDestFile, hIOCP, IOCP_KEY_WRITE, sysInfo.dwNumberOfProcessors);
OVERLAPPED oRead = { 0 }, oWrite = { 0 };
// 执行逻辑如下
// 首先往 IOCP 的完成队列插入一项 "写完成"
// 让我们从 "读操作" 开始, 然后依次进行 读->写->读->写... 这样的逻辑
// 在 "读" 完后, 将缓冲区中的数据写入复制的文件, 然后更新源文件读取的偏移量
// 如果写入的真实数据数比指定的数据数小, 说明已经读到最后一次 设置标志位不再进行读取了
// 然后执行到 "写操作"
// 在写操作完成后, 先更新复制到的新文件的写入偏移量, 然后进行新的读取
// 读取时判断是否到达文件尾
// 更新后的 源文件 的偏移量和 源文件 大小做比较, 如果大于等于源文件大小, 说明读完了 该退出了
// 否则继续读
// POST一个写请求, 为了触发我们的读操作
if (!PostQueuedCompletionStatus(hIOCP, 0, IOCP_KEY_WRITE, &oWrite))
break;
SIZE_T sizeLen = dwBytePerSector * 1024 * 2; // 512KB * 2 == 1M
lpAddr = VirtualAlloc(NULL, sizeLen, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
// 另外一个线程进行读写
// 控制台我们就写一个死循环好了
DWORD dwByteTrans = 0;
ULONG_PTR ulKey = 0;
LPOVERLAPPED lpOverLapped = NULL;
BOOL bIsContinueRun = TRUE, bIsEOF = FALSE;
LONGLONG llWriteTotalLen = 0;
while (bIsContinueRun)
{
BOOL bRet = GetQueuedCompletionStatus(hIOCP, &dwByteTrans, &ulKey, &lpOverLapped, INFINITE);
if (!bRet)
if (!lpOverLapped)
{
DWORD dwError = GetLastError();
// 超时了
if (ERROR_TIMEOUT == dwError)
{
// 等待超时
_tprintf(TEXT("获取完成端口队列状态超时....\n"));
continue;
}
else
{
_tprintf(TEXT("获取完成端口队列状态出错....错误码:[%d]\n"), dwError);
bIsContinueRun = FALSE;
break;
}
}
else
{
// 失败了
_tprintf(TEXT("获取完成端口队列状态出错....错误码:[%d]\n"), GetLastError());
bIsContinueRun = FALSE;
break;
}
switch (ulKey)
{
case IOCP_KEY_READ:
{
// 写文件
// 注意这里的写入byte数, 一定要是sizeLen
// 如果不是硬盘扇区(512)的整数倍, 就会发生 ERROR_INVALID_PARAMETER(87) 错误
// 所以写入之后, 在进行设置文件尾解决这个问题
if (!WriteFile(hDestFile, lpAddr, sizeLen, NULL, &oWrite))
{
DWORD dwError = GetLastError();
if (ERROR_IO_PENDING != dwError)
{
// 写文件出错了
_tprintf(TEXT("写文件出错了....错误码:[%d]\n"), dwError);
bIsContinueRun = FALSE;
}
llWriteTotalLen += dwByteTrans;
if (sizeLen > dwByteTrans)
{
// 读取的不够长度了 说明读完了
//bIsContinueRun = FALSE;
// 读完了还不能退 因为要等写操作完成
bIsEOF = TRUE;
}
else
{
// 更新oRead的Offset
LARGE_INTEGER liReadLen = { 0 };
liReadLen.QuadPart = llWriteTotalLen;
oRead.Offset = liReadLen.LowPart;
oRead.OffsetHigh = liReadLen.HighPart;
}
_tprintf(TEXT("\r源文件大小:[%lld], 本次写入大小: [%lu], 已复制大小:[%lld], 已复制 [%.2f%%]...."),
liFileSize.QuadPart,
dwByteTrans,
llWriteTotalLen,
(double)((double)llWriteTotalLen / (double)liFileSize.QuadPart) * 100);
}
}
break;
case IOCP_KEY_WRITE:
{
if (bIsEOF)
{
// 设置文件尾
SetFilePointerEx(hDestFile, liFileSize, NULL, FILE_BEGIN);
SetEndOfFile(hDestFile);
bIsContinueRun = FALSE;
break;
}
// 更新oWrite的Offset
LARGE_INTEGER liWriteLen = { 0 };
liWriteLen.QuadPart = llWriteTotalLen;
oWrite.Offset = liWriteLen.LowPart;
oWrite.OffsetHigh = liWriteLen.HighPart;
// 读文件
// 当前源文件的偏移量
LARGE_INTEGER liTemp;
liTemp.LowPart = oRead.Offset;
liTemp.HighPart = oRead.OffsetHigh;
// 如果最后一次读取正好等于需要读的长度 还不保险
// 所以在判断一下当前文件偏移是超过文件大小
if (liTemp.QuadPart >= liFileSize.QuadPart)
{
bIsContinueRun = FALSE;
break;
}
if (!ReadFile(hSrcFile, lpAddr, sizeLen, NULL, &oRead))
{
DWORD dwError = GetLastError();
switch (dwError)
{
case ERROR_HANDLE_EOF:
// 文件读完了
// 这里就没进来过!
bIsContinueRun = FALSE;
break;
case ERROR_IO_PENDING:
// 文件读取操作没完成 继续
break;
default:
// 读文件出错了...
bIsContinueRun = FALSE;
_tprintf(TEXT("复制文件出现错误, 错误码:[%d]\n"), dwError);
break;
}
}
}
break;
default:
break;
}
}
bIsOk = TRUE;
nRet = 0;
} while (FALSE);
if (!bIsOk)
{
DWORD dwError = GetLastError();
_tprintf(TEXT("出现错误, 错误码:[%d]\n"), dwError);
}
if (lpAddr)
{
VirtualFree(lpAddr, 0, MEM_RELEASE | MEM_DECOMMIT);
lpAddr = NULL;
}
if (hSrcFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hSrcFile);
hSrcFile = INVALID_HANDLE_VALUE;
}
if (hDestFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hDestFile);
hDestFile = INVALID_HANDLE_VALUE;
}
return nRet;
}未完待续...
如有错误,请提出指正!谢谢.
本文由 花心胡萝卜 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: 2017-06-05 at 01:54 am