IOCP 简单的完成端口读写文件

  • 简单的移动文件代码
  • IOCP详细讲解
    • 注意:IO完成端口它其实是一个内核对象,不要认为它是SOCKET中的端口。

简单的移动文件代码

// IOCP.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;//完成键
#define CK_READ  1
#define CK_WRITE 2void ShowErrMsg(LPCSTR lpMsg);int main(int argc, char* argv[])
{LPCTSTR lpSrc = TEXT("D:\\超大文件.zip");LPCTSTR lpDest = TEXT("D:\\换个名字.zip");HANDLE hSrcFile = INVALID_HANDLE_VALUE;  //源文件句柄HANDLE hDestFile = INVALID_HANDLE_VALUE; //目标文件句柄HANDLE hIOCP = NULL;                     //IOCPLPVOID 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);printf_s("共有%d个CPU\n", sysInfo.dwNumberOfProcessors);/*SYSTEM_INFO结构体参数说明:wProcessorArchitecture: Word; {处理器的体系结构}wReserved: Word;  {保留}dwPageSize: DWORD;  {分页大小}lpMinimumApplicationAddress: Pointer;{最小寻址空间}lpMaximumApplicationAddress: Pointer;{最大寻址空间}dwActiveProcessorMask: DWORD; {处理器掩码; 0..31 表示不同的处理器}dwNumberOfProcessors: DWORD;  {处理器数目}dwProcessorType: DWORD; {处理器类型}dwAllocationGranularity: DWORD; {虚拟内存空间的粒度}wProcessorLevel: Word;  {处理器等级}wProcessorRevision: Word);  {处理器版本}*/DWORD dwBytesPerSector = 0UL;bRet = GetDiskFreeSpace(TEXT("D:"), NULL, &dwBytesPerSector, NULL, NULL);/*BOOL WINAPI GetDiskFreeSpaceW(_In_opt_ LPCWSTR lpRootPathName,//路径_Out_opt_ LPDWORD lpSectorsPerCluster,//每个簇有多少个扇区_Out_opt_ LPDWORD lpBytesPerSector,//每个扇区有多少个字节_Out_opt_ LPDWORD lpNumberOfFreeClusters,//可用的簇_Out_opt_ LPDWORD lpTotalNumberOfClusters//总的簇);*/if (FALSE == bRet){ShowErrMsg("开始获取磁盘扇区大小 错误");return -5;}//读 ol结构OVERLAPPED ovlpRead;ovlpRead.Offset = 0;ovlpRead.OffsetHigh = 0;ovlpRead.hEvent = NULL;//写 ol结构OVERLAPPED ovlpWrite;ovlpWrite.Offset = 0;ovlpWrite.OffsetHigh = 0;ovlpWrite.hEvent = NULL;//创建IOCP 并和 hFile关联hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, sysInfo.dwNumberOfProcessors);if (NULL == hIOCP)//新建一个IOCP。{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);//申请扇区X倍的内存(因为使用了FILE_FLAG_NO_BUFFERING 所以必须为扇区大小的整数倍)size_t sizeMAX = dwBytesPerSector * 1024 * 64 * 2; //512K * 64 * 2size_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,     //IOCP0,           //GetQueuedCompletionStatus取到的传送字节为0(用来触发写完成,第一次是0 所以它会从0开始读)CK_WRITE,  //写操作&ovlpWrite //写OVERLAPPED);//如果不用post 你就得自己开个线程去触发了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;} //ifelse{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 liDoneBytes;liDoneBytes.QuadPart = dwBytesTrans;//ovlpRead.Offset += liDoneBytes.LowPart;//ovlpRead.OffsetHigh += liDoneBytes.HighPart;//用指针或全局定义的都可以更新//更新读文件的偏移,完成后我们需要更新OL结构的偏移lpOverlapped->Offset = liDoneBytes.LowPart;lpOverlapped->OffsetHigh = liDoneBytes.HighPart;} //if//写操作完成 else if (ulCompleteKey == CK_WRITE)//如果获取到是写完成 就该读文件 //刚才post用来触发写完成 所以下面是读{//获取写完成 写了多少字节(已传输字节)LARGE_INTEGER liDoneBytes;liDoneBytes.QuadPart = dwBytesTrans;//ovlpWrite.Offset += liDoneBytes.LowPart;//ovlpWrite.OffsetHigh += liDoneBytes.HighPart;//用指针或全局定义的都可以更新//更新写文件的偏移,完成后我们需要更新OL结构的偏移lpOverlapped->Offset = liDoneBytes.LowPart;lpOverlapped->OffsetHigh = liDoneBytes.HighPart;//获取当前读文件的偏移(用来判断写完后该不该再读了)//第一次用来触发时是0LARGE_INTEGER liTemp;liTemp.LowPart = ovlpRead.Offset;liTemp.HighPart = ovlpRead.OffsetHigh;//当前读文件偏移小于文件大小 就 继续读 读后会继续触发写if (liTemp.QuadPart < liFileSize.QuadPart){cout << endl << "*************第 " << ++j << " 次写操作完成,开始读文件 ***************" << endl;ReadFile(hSrcFile, lpAddr, sizeMIN, NULL, &ovlpRead);}else {break;}}}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"; }

IOCP详细讲解

IO完成端口背后的理论是并发运行的线程的数量必须有一个上限
也就是说,同时发出的500个客户请求不应该允许出现500个可运行的线程。
那么,可运行线程的数量是多少才算合适呢?
无需考虑太长的时间,我们就会意识到如果机器只有2个CPU,那么允许可运行线程的数量大于2–每个处理器1个线程将没有什么意义。
一旦可运行线程的数量大于可用CPU数量,系统就必须花时间来执行线程上下文切换,而这会浪费宝贵的CPU周期–这也是并发模型的一个潜在缺点。

并发模型的另一个缺点是需要为每个客户请求创建一个新的线程。虽然和创建一个有自己的虚拟地址空间的进程相比,创建一个线程的开销要低得多
如果能在应用程序初始化的时候创建一个线程池,并让线程池中的线程在应用程序运行起见一直保持可用状态,那么服务应用程序的性能就能得到提高。
IO完成端口的设计初衷就是与线程池配合使用。

注意:IO完成端口它其实是一个内核对象,不要认为它是SOCKET中的端口。

IO完成端口可能是最复杂的内核对象了。为了创建一个IO完成端口,我们应该调用

HANLDE CreateIoCompletionPort(
HANDLE hFile,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD dwNumberOfConcurrentThreads //告诉IO完成端口最多能有多少线程处于可运行状态,如果传0,那么会使用默认值(主机CPU数量)
);

这个函数执行2项不同的任务,它不仅会创建一个IO完成端口,而且会将一个设备与一个IO完成端口关联起来。

只有当我们要将一个设备与一个IO完成端口关联在一起的时候,才会用到前三个参数,为了只创建一个IO完成端口 需要给前3个参数分别传入
INVALID_HANDLE_VALUE、NULL和0。

当我们创建一个IO完成端口时,系统内核实际上会创建5个不同的数据结构。 由于我看的是纸质书,懒得截图,我用文字方式叙述。

  • 设备列表:
    每条记录包含hDevice和dwCompletionKey
    调用CreateIoCompletionPort时,会在列表中添加新项。
    hFile(或是hDeviec)设备句柄被关闭,会将列表中的项删除。
  • I/O完成队列(先入先出):
    每条记录包含dwBytesTransferred、dwCompletionKey、pOverlapped、dwError
    IO请求完成,PostQueuedCompletionStatus被调用(和QueueUserAPC 类似),会在列表中添加新项。
    完成端口从等待线程队列中删除一项会将列表中的项删除。
  • 等待线程队列(后入先出):
    每条记录包含dwThreadId
    当线程调用GetQueuedCompletionStatus,会在列表中添加新项。
    当IO完成队列不为空而且正在运行的线程数小于最大并发线程数(GetQueuedCompletionStatus会先从I/O完成队列中删除对应的项,
    接着将dwThreadId转移到已释放线程列表,最后函数返回),会将列表中的项删除。
  • 已释放线程列表:
    每条记录包含dwThreadId。
    完成端口在等待线程队列中唤醒一个线程(Resume),会在列中添加新项。
    线程再次调用GetQueuedCompletionStatus(dwThradId再次回到等待线程队列) 或 线程调用一个函数将自己挂起(dwThreadId转移到已暂停线程列表),
    会将列表中的项删除。
  • 已暂停线程列表:
    每条记录包含dwThreadId。
    已释放的线程调用一个函数将自己挂起,会在列表中添加新项。
    已挂起的线程被唤醒,dwThreadId回到已释放线程队列,会将列表中的项删除。

IOCP 简单的完成端口读写文件相关推荐

  1. Java 读写文件工具类

    今天简单写了一下读写文件用的工具类,方便后面开发或者测试时直接使用. import lombok.Cleanup; import java.io.*; import java.util.ArrayLi ...

  2. c#语言中读取txt文件,简单的c#文本文件读写-.NET教程,C#语言

    system.io命名空间中的类为托管应用程序提供文件以及其他形式的输入输出.托管i/o的基本构件是流,而流是字节导向的数据的抽象表示.流通过system.io.stream类表示. system.i ...

  3. python读写文件实例_python读写文件的简单示例

    这篇文章主要为大家详细介绍了python读写文件的简单示例,具有一定的参考价值,可以用来参考一下. 感兴趣的小伙伴,下面一起跟随512笔记的小编罗X来看看吧. 首先看一个例子: # 来自www.512 ...

  4. c/c++读取txt文件中指定行的内容_和尧名大叔一起从0开始学Python编程-简单读写文件

    0基础自学编程是很痛苦的一件事情,所以我想把自己学习的这个过程记录下来,让想学编程的人少走弯路,大叔文化程度较低,可能会犯一些错误,欢迎大家督促我. 今天,我们来学习一下用Python简单读写文件,这 ...

  5. 4 读写文件_和尧名大叔一起从0开始学Python编程-简单读写文件

    0基础自学编程是很痛苦的一件事情,所以我想把自己学习的这个过程记录下来,让想学编程的人少走弯路,大叔文化程度较低,可能会犯一些错误,欢迎大家督促我. 今天,我们来学习一下用Python简单读写文件,这 ...

  6. 一个简单的完成端口(服务端/客户端)类

    一个简单的完成端口(服务端/客户端)类 作者:spinoza 翻译:麦子芽儿, POWERCPP(后面部分内容) 下载源代码 原文网址:http://www.codeproject.com/KB/IP ...

  7. c语言格式化写入文件大小,利用C语言格式化读写文件

    "在C语言中进行格式化读写文件"按照字面意思通常大家都会理解为将读写文件进行格式化的操作,那么到底我们是不是真的将读写文件进行格式化的操作呢?下面课课家笔者就为大家简单介绍C语言中 ...

  8. python文件对象提供了3个读方法、分别是-Python读写文件模式和文件对象方法实例详解...

    本文实例讲述了Python读写文件模式和文件对象方法.分享给大家供大家参考,具体如下: 一. 读写文件模式 利用open() 读写文件时,将会返回一个 file 对象,其基本语法格式如: open ( ...

  9. python怎么读取文件-python怎么读写文件操作

    本节内容:I/O操作概述 文件读写实现原理与操作步骤 文件打开模式 Python文件操作步骤示例 Python文件读取相关方法 文件读写与字符编码 一.I/O操作概述 I/O在计算机中是指Input/ ...

最新文章

  1. caffe libcudnn.so.6: cannot open shared object file: No such file or directo
  2. 全球及中国吉他霉素预混剂行业创新现状与可持续发展分析报告2022-2027年版
  3. python官方手册笔记_Python学习手册笔记
  4. mfc之DDX_Control作用
  5. 拼接播放地址_西安户外did拼接屏批发业务广泛_金伟达电子
  6. nutch batchid
  7. 超好用的抠图软件:InPixio Photo Eraser mac版
  8. 空间直线与球面的相交算法
  9. MySQL窗口函数(分组内排序、筛选)
  10. java实训项目百度脑图
  11. 【修真院PM小课堂】什么是三种环境?
  12. php的知识体系结构图,高中英语全部知识体系结构图汇总
  13. 神经网络历史_神经网络完整有趣且令人费解的历史
  14. 51cto的请看过来
  15. mysql truncate 大表_MySQL删除大表时潜在的问题(drop table,truncate table)
  16. 杭州端点网络java开发实习生笔试题自我反省
  17. PAT乙级题解1005(超级详细分析,看完就懂)
  18. 图解图库Janusgraph系列-一文知晓图数据底层存储结构
  19. 2021-07-11谓词执行
  20. pve远程连接 spcie_PVE相关 篇一:解决CX341a PVE 中报错PCIe Bus Error

热门文章

  1. 爬虫入门经典(八) | 一文带你快速爬取股吧
  2. 系统学习iOS动画之七:其它类型的动画
  3. pythonspark安装_Spark学习笔记--Spark在Windows下的环境搭建
  4. Joel on Software 祖尔谈软件:行进中开火
  5. AI预测的七宗致命罪恶
  6. 赛效:WPS文字(Word)如何合并多个文档
  7. 基于PhalApi的PHPExcel扩展实现数据导入导出
  8. 将文件夹内多个子文件里的Excel数据合并到一个文件,保存为CSV格式
  9. mysql数据库性别字段你选用什么数据类型呢
  10. Ubuntu安装SSH服务