zoukankan      html  css  js  c++  java
  • 【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 << "
    "; } 

    4、细节和问题

      过程中发现:某个文件的复制进入死循环,判断break退出while的条件永远不成立,即【目的文件的偏移量】没有达到【源文件的大小】这一条件,单步过程中发现是如下问题

    另外:关于读写逻辑的问题,一开始是在收到CK_WRITE的时候更新【源文件】偏移量,在收到CK_READ时更新【目的文件】的偏移量,而且发现网上也有这么做的,后来经过折腾发现逻辑有点问题,反过来比较合理,

    即 收到CK_WRITE时更新【目的文件偏移量】,收到CK_READ时更新【源文件偏移量】。

     

    5、执行结果

  • 相关阅读:
    CSS background API
    Vistual Studio 2019下载离线包与离线包更新
    flex布局属性速查表
    Vuex-核心概念-State 学习笔记
    前端环境搭配ESLint和Prettier
    Windows下VScode Scss开发环境配置
    Vue2.5开发去哪儿网App 从零基础入门到实战项目之6-1 Vue项目预热
    Webpack深入与实战 慕课网 免费 讲师qbaty 学习笔记
    Linux安装RabbitMQ
    docker-compose 搭建 kafka 集群
  • 原文地址:https://www.cnblogs.com/cuish/p/3857620.html
Copyright © 2011-2022 走看看