利用IO完成端口实现文件复制

本文介绍了一种使用Windows API中的完成端口(I/O Completion Ports)和OVERLAPPED结构来实现高效文件复制的方法。通过创建EnsureCloseFile类确保文件句柄在不再需要时被自动关闭,以及CIOReq类来管理I/O操作,文章详细展示了如何通过异步I/O读取源文件并写入目标文件,直至完成整个文件的复制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先实现一个句柄自动关闭的类EnsureCloseFile

以下是头文件 

#pragma once
#include <windows.h>
class EnsureCloseFile
{
public:
	EnsureCloseFile(HANDLE hHandle);
	~EnsureCloseFile();

	EnsureCloseFile(const EnsureCloseFile&) = delete;
	EnsureCloseFile& operator=(const EnsureCloseFile&) = delete;
	

	BOOL IsInvalid();

	HANDLE GetHandle();

private:
	HANDLE m_hHandle;
};

EnsureCloseFile的实现:

#include "EnsureCloseFile.h"

EnsureCloseFile::EnsureCloseFile(HANDLE hHandle)
{
	m_hHandle = hHandle;
}

EnsureCloseFile::~EnsureCloseFile()
{
	CloseHandle(m_hHandle);
}

BOOL EnsureCloseFile::IsInvalid()
{
	if (m_hHandle == INVALID_HANDLE_VALUE||m_hHandle==NULL)
		return TRUE;
	return FALSE;
}

HANDLE EnsureCloseFile::GetHandle()
{
	return m_hHandle;
}

然后,在实现一个CIOReq类,该类的本质就是采用继承的关系将OVERLAPPED和其读取到的数据做一个联系,头文件如下:

#pragma once
#include <windows.h>
class CIOReq :
	public OVERLAPPED
{
public:
	CIOReq();
	~CIOReq();

	BOOL AllocBuffer(SIZE_T nBuffSize);
	BOOL Read(HANDLE hDevice, PLARGE_INTEGER plioffset = NULL);
	BOOL Write(HANDLE hDevice, PLARGE_INTEGER plioffset = NULL);

private:
	SIZE_T m_nBuffSize;
	PVOID m_pvData;
};

实现如下:

#include "CIOReq.h"

CIOReq::CIOReq()
{
	Internal = InternalHigh = 0;
	Offset = 0;
	OffsetHigh = 0;
	hEvent = NULL;
	m_nBuffSize = 0;
	m_pvData = NULL;
}

CIOReq::~CIOReq()
{
	if (NULL != m_pvData)
		VirtualFree(m_pvData, 0, MEM_RELEASE);
}

BOOL CIOReq::AllocBuffer(SIZE_T nBuffSize)
{
	m_nBuffSize = nBuffSize;
	m_pvData = VirtualAlloc(NULL, m_nBuffSize, MEM_COMMIT, PAGE_READWRITE);
	return (m_pvData != NULL);
}

BOOL CIOReq::Read(HANDLE hDevice, PLARGE_INTEGER plioffset)
{
	if (plioffset != NULL)
	{
		Offset = plioffset->LowPart;
		OffsetHigh = plioffset->HighPart;
	}
	return ReadFile(hDevice, m_pvData, m_nBuffSize, NULL, this);
}

BOOL CIOReq::Write(HANDLE hDevice, PLARGE_INTEGER plioffset)
{
	if (plioffset != NULL)
	{
		Offset = plioffset->LowPart;
		OffsetHigh = plioffset->HighPart;
	}
	return WriteFile(hDevice, m_pvData, m_nBuffSize, NULL, this);
}

最后就是利用完成端口实现文件复制了,原理很简单,就是创建完成端口,然后发出4个IO请求,这些IO请求就是读取数据,如果读取完成了就发出写入IO请求,如此反复,直至文件复制完成,由于我只采用了一个主线程,因此这些IO请求之间不存在数据同步的问题。代码如下:

#include <iostream>
#include <string>
#include "CIOReq.h"
#include "EnsureCloseFile.h"

#define BUFFSIZE (64*1024)
#define CK_READ		1
#define CK_WRITE	2
#define MAX_PENDING_IO_REQS 4

template<class TV,class TM>
inline TV chROUNDDOWN(TV value, TM Multiple)
{
	return ((value / Multiple) * Multiple);
}

template<class TV,class TM>
inline TV chROUNDUP(TV value, TM Multiple)
{
	return (chROUNDDOWN(value, Multiple) + (((value % Multiple) > 0) ? Multiple : 0));
}

bool FileCopy(std::wstring srcFilePath, std::wstring dstFilePath)
{
	bool fOk = false;
	LARGE_INTEGER liFileSizeSrc = { 0 };
	LARGE_INTEGER liFileSizeDst;
    do 
    {
		//创建文件句柄
		EnsureCloseFile hFileSrc = CreateFile(srcFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
			FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);

		if (hFileSrc.IsInvalid())
			break;

		//获取文件大小
		GetFileSizeEx(hFileSrc.GetHandle(), &liFileSizeSrc);

		//使目标文件的大小保持为扇形大小的整数倍
		liFileSizeDst.QuadPart = chROUNDUP(liFileSizeSrc.QuadPart,BUFFSIZE);

		EnsureCloseFile hFileDst = CreateFile(dstFilePath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
			FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, hFileSrc.GetHandle());

		if (hFileDst.IsInvalid())
			break;

		SetFilePointerEx(hFileDst.GetHandle(), liFileSizeDst, NULL, FILE_BEGIN);
		//截断文件大小
		SetEndOfFile(hFileDst.GetHandle());

		//创建一个IO完成端口
		HANDLE hIOCompPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
		
		//将完成端口与设备关联
		HANDLE hReadIOCompPort = CreateIoCompletionPort(hFileSrc.GetHandle(), hIOCompPort, CK_READ, 0);
		if (hIOCompPort != hReadIOCompPort)
			break;

		HANDLE hWriteIoCompPort = CreateIoCompletionPort(hFileDst.GetHandle(), hIOCompPort, CK_WRITE, 0);
		if (hIOCompPort != hWriteIoCompPort)
			break;

		CIOReq ior[MAX_PENDING_IO_REQS];
		
		LARGE_INTEGER liNextReadOffset = { 0 };
		int nReadsInProgress = 0;
		int nWritesInProgress = 0;

		for (int i = 0; i < MAX_PENDING_IO_REQS; i++)
		{
			ior[i].AllocBuffer(BUFFSIZE);
			nWritesInProgress++;
			//发送一个读取完成通知
			PostQueuedCompletionStatus(hIOCompPort, 0, CK_WRITE, &ior[i]);
		}

		while ((nReadsInProgress > 0) || (nWritesInProgress > 0))
		{
			ULONG_PTR CompletionKey;
			DWORD dwNumBytes;
			CIOReq* pior;
			//获取状态
			GetQueuedCompletionStatus(hIOCompPort, &dwNumBytes, &CompletionKey, (OVERLAPPED**)&pior, INFINITE);
			switch (CompletionKey)
			{
			case CK_READ:
			{
				nReadsInProgress--;
				pior->Write(hFileDst.GetHandle());
				nWritesInProgress++;
				break;
			}
			case CK_WRITE:
			{
				nWritesInProgress--;
				if (liNextReadOffset.QuadPart < liFileSizeDst.QuadPart)
				{
					pior->Read(hFileSrc.GetHandle(), &liNextReadOffset);
					nReadsInProgress++;
					liNextReadOffset.QuadPart += BUFFSIZE;
				}
				break;
			}
			}
		}

		fOk = true;

    } while (0);

	if (fOk)
	{
		EnsureCloseFile fHandle = CreateFile(dstFilePath.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
		if (!fHandle.IsInvalid())
		{
			//重新设置文件大小
			SetFilePointerEx(fHandle.GetHandle(), liFileSizeSrc, NULL, FILE_BEGIN);
			SetEndOfFile(fHandle.GetHandle());
			return true;
		}

		return false;
	}

	return false;
}

int main()
{
	std::wstring src = L"N:\\1.zip";
	std::wstring dst = L"N:\\2.zip";

	FileCopy(src, dst);

    std::cout << "Copy Completion!\n";
	system("pause");
}

所有的代码我都贴出来了,我采用的编译器是V140,SDK8.1。可以直接运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值