PoEdu培训 Windows班 第十三课 Windows异步IO完成端口实战-CopyFile
文章类别: 培训笔记 0 评论

PoEdu培训 Windows班 第十三课 Windows异步IO完成端口实战-CopyFile

文章类别: 培训笔记 0 评论

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;
}

未完待续...

如有错误,请提出指正!谢谢.

回复