完成端口
IO完成端口也是一个内核对象。调用以下函数创建IO完成端口内核对象。
函数原型:
HANDLE CreateIoCompletionPort
(
HANDLE hFile,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD dwNumberOfConcurrentThreads
);
函数参数:
hFile 就是设备句柄。
hExistingCompletionPort 是与设备关联的IO完成端口句柄。为NULL时,系统会创建新的完成端口。
dwCompletionKey 是一个对我们有意义的值,但是操作系统并不关心我们传入的值。一般用它来区分各个设备。
dwNumberOfConcurrentThreads 告诉IO完成端口在同一时间最多能有多少进程处于可运行状态。如果传入0,那么将使用默认值(并发的线程数量等于cpu数量)。在第二章我们曾介绍说几乎所有的内核对象都需要安全属性参数。那时的几乎就是因为IO完成端口这个例外。它是唯一一个不需要安全属性的内核对象。 这是因为IO完成端口在设计时就是只在一个进程中使用。
注意:每次调用CreateIoCompletionPort时,函数会判断hExistingCompletionKey是否为NULL,如果为NULL,会创建新的完成端口内核对象。并为此完成端口创建设备列表然后将设备加入到此完成端口设备列表中(先入先出)。
这个函数会完成两个任务:
一是创建一个IO完成端口对象。
二是将一个设备与一个IO完成端口关联起来。
任务一: HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, sysInfo.dwNumberOfProcessors);
任务二: hIOCP = CreateIoCompletionPort(hSrcFile, hIOCP, IOCP_KEY_READ, sysInfo.dwNumberOfProcessors); hSrcFile 源文件文件句柄 hIOCP 创建的IO完成端口对象
另外,Windows为IO完成端口提供了一个函数,可以将线程切换到睡眠状态,来等待设备IO请求完成并进入完成端口。
函数原型: BOOL GetQueuedCompletionStatus ( HANDLE hCompletionPort, PDWORD pdwNumberOfBytesTransferred, ULONG_PTR pCompletionKey, OVERLAPPED** ppOverlapped, DWORD dwMilliSeconds );
函数参数:
hCompletionPort 表示线程希望对哪个完成端口进行监视,GetQueuedCompletionStatus的任务就是将调用线程切换到睡眠状态,也就是阻塞在此函数上,直到指定的IO完成端口出现一项或者超时。
pdwNumberOfBytesTransferred 返回在异步IO完成时传输的字节数。
pCompletionKey 返回完成键。
ppOverlapped 返回异步IO开始时传入的OVERLAPPED结构地址。
dwMillisecond 指定等待时间。
函数执行成功则返回true,否则返回false。
实例:
DWORD dwByteTrans = 0; ULONG_PTR ulKey = 0; LPOVERLAPPED lpOverLapped = nullptr; BOOL bRet = GetQueuedCompletionStatus(hIOCP, &dwByteTrans, &ulKey, &lpOverLapped, INFINITE);
IO完成端口并不一定要用于设备IO,它还可以进行线程间通信。在可提醒IO中我们介绍了QueueUserAPC。该函数允许线程将一个APC项添加到另一个线程的队列中。IO完成端口也存在一个类似的函数:
函数原型: BOOL PostQueuedCompletionStatus ( HANDLE hCompletionPort, DWORD dwNumBytes, ULONG_PTR CompletionKey, OVERLAPPED*pOverlapped );
hCompletionPort:指定想向其发送一个完成数据包的完成端口对象。
dwNumberOfBytesTrlansferred:指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数
dwCompletlonKey:指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数
PostQueuedCompletionStatus(hIOCP, 0, IOCP_KEY_WRITE,&oWrite);
最后,当对完成端口调用CloseHandle时,系统会将所有正在等待GetQueuedCompletionStatus返回的线程唤醒,并返回false。GetLastError返回ERROR_INVALID_HANDLE,此时线程就可以知道应该退出了。
// CompleTionPortDemo.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <Windows.h> #include <iostream> using namespace std; #define BUFFERSIZE 10000 //文件复制的源文件和目标文件 const WCHAR *wszSrcFile = L"D:\\SourceFile.txt"; const WCHAR *wszDesFile = L"D:\\DestFile.txt"; //自定义读、写操作标识 enum FILESTATUE { IOCP_KEY_READ, IOCP_KEY_WRITE }; int _tmain(int argc,char *argv[]) { //获取系统线程核数 SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); //定义线程数,如四核;4*2+2. DWORD dwNumOfProcess = (sysInfo.dwNumberOfProcessors) * 2 + 2; //读源文件 HANDLE hSrcFile = CreateFile(wszSrcFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); if (hSrcFile == INVALID_HANDLE_VALUE) { printf("文件打开失败!"); } DWORD FileSizeHigh; DWORD FileSize = GetFileSize(hSrcFile, &FileSizeHigh); //创建目标文件 HANDLE hDstFile = CreateFile(wszDesFile, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_OVERLAPPED, NULL); //创建完成端口 HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumOfProcess); if (hIOCP == NULL) { cout << "完成端口创建失败" << endl; } //绑定完成端口 hIOCP=CreateIoCompletionPort(hSrcFile, hIOCP, IOCP_KEY_READ, dwNumOfProcess); if (hIOCP == NULL) { cout << "绑定源文件端口失败" << endl; } CreateIoCompletionPort(hDstFile, hIOCP, IOCP_KEY_WRITE, dwNumOfProcess); if (hIOCP == NULL) { cout << "绑定目标文件端口失败" << endl; } OVERLAPPED ov = { 0 }; //通知端口 BOOL bRet=PostQueuedCompletionStatus(hIOCP, 0, IOCP_KEY_WRITE, &ov); if (!bRet) { cout << "PostQueuedCompletionStatus error"<<endl; } OVERLAPPED ovSrc = { 0 }; OVERLAPPED ovDes = { 0 }; ULONG_PTR CompletionKey; BYTE* pBuffer = new BYTE[BUFFERSIZE]; int i = 0; int j = 0; while (true) { DWORD dwByteTrans=0; OVERLAPPED* o; //监视端口信息 bRet=GetQueuedCompletionStatus(hIOCP, &dwByteTrans, &CompletionKey, &o, INFINITE); if (bRet) { switch (CompletionKey) { case IOCP_KEY_READ: //代表读取IO操作已经完成,进行下一步写入操作 WriteFile(hDstFile, pBuffer, o->InternalHigh, NULL, &ovDes); cout << "write:" << ++i << endl; ovDes.Offset += o->InternalHigh; //if(ovDes.Offset== FileSize/1024 ) // return 0; break; case IOCP_KEY_WRITE: //代表写入IO操作已经完成,进行下一步读取操作 memset(pBuffer, 0, BUFFERSIZE * sizeof(BYTE)); if (ovSrc.Offset < FileSize)//文件读取未完成 { DWORD nBytes; if (ovSrc.Offset + BUFFERSIZE < FileSize) nBytes = BUFFERSIZE; else nBytes = FileSize - ovSrc.Offset; ReadFile(hSrcFile, pBuffer, nBytes, NULL, &ovSrc); cout << "read:" << ++j << endl; ovSrc.Offset += nBytes; } else return 0; break; default: break; } } else { continue; } } return 0; }