IOCP IO完成端口
一. IO完成端口概念
IO完成端口的出现是为了解决并发模型中可运行线程上下文切换开销过大而出现的。
在《Windows核心编程》的描述中,IO完成端口是Wnidows系统提供的最复杂的内核对象,是一种解决并发IO请求的最佳模型,是用来实现高容量网路服务器的最佳方法。既然是一个对象,那么就直接分析一下操作系统眼中的完成端口的具体定义吧。Windows中利用CreateIoCompletionPort命令创建完成端口对象时,系统内部自动创建了5个相应的数据结构,分别是:设备列表(Device List)、IO完成请求队列(I/O Completion Queue-FIFO)、等待线程队列(WaitingThread List-LIFO)、释放线程队列(Released Thread List)和暂停线程队列(Paused Thread List)。
|
设备列表 |
ADD |
每当调用CreateIoCompletionPort绑定到某个设备时,系统会将该设备句柄添加到设备列表中;(与完成端口相关联的<设备列表,完成键>对) |
|
REMOVE |
每当调用CloseHandle关闭了某个设备句柄时,系统会将该设句柄从设备列表中删除; |
|
|
I/O 完成队列 |
ADD |
当I/O请求操作完成时,或者调用了PostQueuedCompeltionStatus函数时,系统会将I/O请求完成状态添加到I/O完成队列中,该队列是FIFO。 |
|
REMOVE |
当完成端口从等待线程队列中取出某一个工作线程时,系统会同时从I/O完成队列中取出一个元素。 |
|
|
等待 线程 队列 |
ADD |
当线程中调用GetQueuedCompletionStatus函数时,系统会将该线程压入到等待线程队列中,该队列是LIFO(为了减少线程切换)。 |
|
REMOVE |
当I/O完成队列非空,且工作线程并未超出总的并发数时,系统从等待线程队列中取出线程,该线程从GetQueuedCompletoinStatus函数返回开始工作。 |
|
|
释放 线程 队列 |
ADD |
1)当系统从等待线程队列中激活了一个工作线程时,或者挂起的线程重新被激活时,该线程被压入释放线程队列中 2)当线程重新调用GetQueuedCompeltionStatus函数时,线程被添加到等待线程队列中; |
|
REMOVE |
当线程调用其他函数使得线程挂起时,该线程被添加到挂起线程队列中。 |
|
|
挂起 线程 队列 |
ADD |
释放线程队列中的线程被挂起的时候,线程被压入到挂起线程队列中; |
|
REMOVE |
当挂起的线程重新被唤醒时,从挂起线程队列中取出。 |

1.创建IO完成端口
HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle, //文件 设备句柄
__in_opt HANDLE ExistingCompletionPort, //与设备关联的IO完成端口句柄,为NULL时,系统会创建新的完成端口
__in ULONG_PTR CompletionKey, //完成键,用它来区分各个设备。
__in DWORD NumberOfConcurrentThreads //允许运行的最大线程数量,如果传0表示允许并发执行的线程数量等于CPU主机数量(我的本机是4核8线程,计算机将CPU主机数量当作了8)
);
这个函数会完成两个任务: 一是创建一个IO完成端口对象,二是将一个设备与一个IO完成端口关联起来。
2.将已完成的IO请求投递到IO完成端口的队列
PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort, //完成端口的句柄
_In_ DWORD dwNumberOfBytesTransferred,
_In_ ULONG_PTR dwCompletionKey,
_In_opt_ LPOVERLAPPED lpOverlapped
);
后三个参数是为调用了那个GetQueuedCompletionStatus的线程而准备的。
3.GetQueuedCompletionStatus函数在检查IO完成队列里是否有已经完成的IO请求。
BOOL
WINAPI
GetQueuedCompletionStatus(
_In_ HANDLE CompletionPort, //完成端口句柄
_Out_ LPDWORD lpNumberOfBytesTransferred,
_Out_ PULONG_PTR lpCompletionKey,
_Out_ LPOVERLAPPED * lpOverlapped,
_In_ DWORD dwMilliseconds //等待时间
);
(1)在等待线程队列中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中存在已完成的IO请求,则GetQueuedCompletionStatus先删除IO完成队列中这个对应的项,然后将线程ID转移到已释放线程列表中(即当前线程属于已释放列表中的一员了)
(2)在已释放列表中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中不再存在已完成的IO请求,则线程ID再次回到等待线程队列中中。(即当前线程属于等待线程队列中的一员了)
二. IO完成端口实现文件拷贝流程。
1.创建IO完成端口,并将源文件,目标文件和端口相关联
2.将一个已完成的IO通知追加到IO完成队列中,(并非真的写只是让下面的代码从 【读操作】开始,执行序列为: 读-写, 读-写, ... ,读-写)
这里的代码是在《windows核心编程》中精简出来的,我也发现了《windows核心编程》源代码中的一个问题——更新OVERLAPPED结构成员,低32位偏移值和高32位偏移值Offset和OffsetHigh时,最后一次更新会无法更新上:
case CK_WRITE:
//写入IO操作已经完成,下一步进行读取操作
WritesInProgress--;
//当前文件偏移不能超过文件大小
//超过文件大小就break,之后不再进入while循环,ReadsInProgress无法自加一,推出循环
if (ReadOffset.QuadPart < SourceFileDataLength.QuadPart) {
// Not EOF, read the next block of data from the source file.
v1->Read(SourceFileHandle, &ReadOffset);
ReadsInProgress++;
ReadOffset.QuadPart += BUFFER_LENGTH;
}
break;
}
每次写入IO操作已经完成,下一步进行读取操作时,read函数中更新了一次OVERLAPPED结构的Offset和OffsetHigh,但实际上!他们的更新值是上一次操作的旧偏移值了!因为ReadOffset.QuadPart += BUFFER_LENGTH;这一句在最后才执行,
文件偏移ReadOffset.QuadPart 的刷新并没有赋值给Offset和OffsetHigh,而是到了下一次循环进来,才赋值上去,已经是陈旧的刷新值了,到了最后一次文件偏移不再小于源文件大小的时候,Read不再被调用,也就无法最后一次更新Offset和OffsetHigh。
IOCompletionPort.h
#pragma once
#include <windows.h>
#include <iostream>
using namespace std; #define CK_READ 0
#define CK_WRITE 1 #define MAX_PENDING_IO_REQUEST 100
#define BUFFER_LENGTH 1024 class CIOCP
{
public:
CIOCP(int NumberOfConcurrentThreads = -1)
{
m_IOCompletionPortHandle = NULL;
if (NumberOfConcurrentThreads != -1)
(void) Create(NumberOfConcurrentThreads);
} ~CIOCP() {
if (m_IOCompletionPortHandle != NULL)
{
CloseHandle(m_IOCompletionPortHandle);
m_IOCompletionPortHandle = NULL;
} }
BOOL Close() {
BOOL IsOk = CloseHandle(m_IOCompletionPortHandle);
m_IOCompletionPortHandle = NULL;
return IsOk;
} BOOL Create(int NumberOfConcurrentThreads = 0) {
m_IOCompletionPortHandle = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
0,
NumberOfConcurrentThreads);//允许并发执行的线程数量等于主机的CPU数量
return(m_IOCompletionPortHandle != NULL);
} BOOL AssociateDevice(HANDLE DeviceHandle, ULONG_PTR CompletionKey) {
BOOL IsOk = (CreateIoCompletionPort(DeviceHandle, m_IOCompletionPortHandle, CompletionKey, 0)
== m_IOCompletionPortHandle);
return IsOk;
} BOOL AssociateSocket(SOCKET SocketObject, ULONG_PTR CompletionKey) {
return(AssociateDevice((HANDLE)SocketObject, CompletionKey));
} BOOL PostStatus(ULONG_PTR CompletionKey, DWORD ReturnLength = 0,
OVERLAPPED* Overlapped = NULL) { BOOL IsOk = PostQueuedCompletionStatus(m_IOCompletionPortHandle, ReturnLength, CompletionKey, Overlapped); return IsOk;
} BOOL GetStatus(ULONG_PTR* CompletionKey, PDWORD ReturnLength,
OVERLAPPED** Overlapped, DWORD Milliseconds = INFINITE) { return(GetQueuedCompletionStatus(m_IOCompletionPortHandle, ReturnLength,
CompletionKey, Overlapped, Milliseconds));
} private:
HANDLE m_IOCompletionPortHandle;
}; class CIORequest : public OVERLAPPED {
public:
CIORequest()
{
Internal = InternalHigh = 0;
Offset = OffsetHigh = 0;
hEvent = NULL;
m_BufferLength = 0;
m_BufferData = NULL;
} ~CIORequest() {
if (m_BufferData != NULL)
VirtualFree(m_BufferData, 0, MEM_RELEASE);
} BOOL AllocBuffer(SIZE_T BufferLength) {
m_BufferLength = BufferLength;
m_BufferData = VirtualAlloc(NULL, m_BufferLength, MEM_COMMIT, PAGE_READWRITE);
return(m_BufferData != NULL);
} BOOL Read(HANDLE DeviceHandle, PLARGE_INTEGER ReadOffset = NULL) {
//更新OVERLAPPED结构成员,低32位偏移值和高32位偏移值(本代码并未使用)
if (ReadOffset != NULL) {
Offset = ReadOffset->LowPart;
OffsetHigh = ReadOffset->HighPart;
}
return ReadFile(DeviceHandle, m_BufferData, m_BufferLength,NULL, this);//读1024字节
} BOOL Write(HANDLE DeviceHandle, PLARGE_INTEGER WirteOffset = NULL) {
//参数WirteOffset为NULL,不走这里
if (WirteOffset != NULL) {
Offset = WirteOffset->LowPart;
OffsetHigh = WirteOffset->HighPart;
} return WriteFile(DeviceHandle, m_BufferData, m_BufferLength, NULL, this);//写1024字节
} private:
SIZE_T m_BufferLength;
PVOID m_BufferData;
};
.cpp
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include "IOCompletionPort.h" using namespace std;
BOOL SeFileCopy(PCTSTR SourceFileFullPathData, PCTSTR DestinationFileFullPathData);
int main()
{ WCHAR SourceFileFullPathData[] = L"ReadMe.txt";
WCHAR DestinationFileFullPathData[] = L"CopyFile.txt"; if (SeFileCopy(SourceFileFullPathData, DestinationFileFullPathData))
{
} printf("Input AnyKey To Exit\r\n");
getchar(); return 0;
} BOOL SeFileCopy(PCTSTR SourceFileFullPathData, PCTSTR DestinationFileFullPathData)
{ BOOL IsOk = FALSE;
LARGE_INTEGER SourceFileDataLength = { 0 };
LARGE_INTEGER DestinationFileDataLength = { 0 };
HANDLE SourceFileHandle = NULL;
HANDLE DestinationFileHandle = NULL;
DWORD ReturnLength = 0;
try {
{ SourceFileHandle = CreateFile(SourceFileFullPathData, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL);
if (SourceFileHandle == INVALID_HANDLE_VALUE) goto Exit; GetFileSizeEx(SourceFileHandle, &SourceFileDataLength); DestinationFileDataLength.QuadPart = SourceFileDataLength.QuadPart; DestinationFileHandle = CreateFile(DestinationFileFullPathData, GENERIC_WRITE,
0, NULL, CREATE_ALWAYS,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED,
SourceFileHandle); //新文件将从这个文件中复制扩展属性
if (DestinationFileHandle == INVALID_HANDLE_VALUE) goto Exit; // File systems extend files synchronously. Extend the destination file
// now so that I/Os execute asynchronously improving performance.
//先把磁盘空间占用起来,以便后面拷贝顺利进行
SetFilePointerEx(
DestinationFileHandle, //目标文件句柄
DestinationFileDataLength, //指针将要移动的字节数
NULL, //在pliNewFilePointer参数指向的LARGE_INTEGER结构体中保存文件指针的新值
FILE_BEGIN); //文件指针起始位置为文件起始位置
//将指定文件的物理文件大小设置为文件指针的当前位置。
SetEndOfFile(DestinationFileHandle); //创建IO完成端口,并将文件和端口相关联
CIOCP IoCompletionPortObject(0); //默认线程个数
IoCompletionPortObject.AssociateDevice(SourceFileHandle, CK_READ); // Read from source file
IoCompletionPortObject.AssociateDevice(DestinationFileHandle, CK_WRITE); // Write to destination file // Initialize record-keeping variables
CIORequest IoRequestObject[MAX_PENDING_IO_REQUEST];
LARGE_INTEGER ReadOffset = { 0 }; int ReadsInProgress = 0;
int WritesInProgress = 0; // Prime the file copy engine by simulating that writes have completed.
// This causes read operations to be issued.
for (int i = 0; i < _countof(IoRequestObject); i++) { // Each I/O request requires a data buffer for transfers
IoRequestObject[i].AllocBuffer(BUFFER_LENGTH);
WritesInProgress++;
//将一个已完成的IO通知追加到IOCP的【完成队列】中
IoCompletionPortObject.PostStatus(CK_WRITE, 0, &IoRequestObject[i]);
} //[CK_WRITE 1024][CK_WRITE 1024][CK_WRITE 1024]投递到IO完成队列
BOOL IsOk = FALSE;
/************************************************************************/
/* 因为前一次只是往IOCP的完成队列插入了一项【写完成】,而并非真的写
只是让下面的代码从 【读操作】开始,
执行序列为: 读-写, 读-写, ... ,读-写
当每个【读操作】完成时:把缓冲区中的数据写入【目的文件】,并更新【源文件】的偏移量 当每个【写操作】完成时:更新【目的文件】的偏移量,
同时,因为操作序列是写操作在后,因此写操作完成后,根据更新后的【源文件】的偏移量
和【源文件】大小做比较,如果大于等于源文件大小,则说明这是最后一次读取操作,则当下一次
写操作完成时 退出循环。 如果当前【源文件偏移量】没有达到【源文件大小】则再次从【源文件】
中读取数据进缓冲区,
/************************************************************************/
// Loop while outstanding I/O requests still exist
while ((ReadsInProgress > 0) || (WritesInProgress > 0)) { // Suspend the thread until an I/O completes
ULONG_PTR CompletionKey;
DWORD ReturnLength = 0;
CIORequest* v1; IsOk = IoCompletionPortObject.GetStatus(&CompletionKey, &ReturnLength, (OVERLAPPED**)&v1, INFINITE); switch (CompletionKey) {
case CK_READ:
//读取IO操作已经完成,下一步进行写入操作
ReadsInProgress--;
v1->Write(DestinationFileHandle,NULL); // Write to same offset read from source
WritesInProgress++;
break; case CK_WRITE:
//写入IO操作已经完成,下一步进行读取操作
WritesInProgress--;
//当前文件偏移不能超过文件大小
//超过文件大小就break,之后不再进入while循环,ReadsInProgress无法自加一,推出循环
if (ReadOffset.QuadPart < SourceFileDataLength.QuadPart) {
// Not EOF, read the next block of data from the source file.
v1->Read(SourceFileHandle, &ReadOffset);
ReadsInProgress++;
ReadOffset.QuadPart += BUFFER_LENGTH;
}
//else
// {
//可以将ReadOffset定义为全局,在CIORequest 的成员函数Read()中更新ReadOffset.QuadPart,
//再更新Offset,OffsetHigh,这样Offset,OffsetHigh就不会少更新一次了。
//Offset = ReadOffset->LowPart;
//OffsetHigh = ReadOffset->HighPart;
//}
break;
}
}
IsOk = TRUE;
}
Exit:; if (SourceFileHandle != NULL)
{
CloseHandle(SourceFileHandle);
SourceFileHandle = NULL;
} if (DestinationFileHandle != NULL)
{
CloseHandle(DestinationFileHandle);
DestinationFileHandle = NULL;
}
}
catch (...) {
} if (IsOk) {
//目标文件大小是页面大小的倍数。用缓冲区打开文件以缩小其大小到源文件的大小
HANDLE DestinationFileHandle = CreateFile(DestinationFileFullPathData, GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (DestinationFileHandle!=INVALID_HANDLE_VALUE)
{
//指针设置,源文件的大小
SetFilePointerEx(DestinationFileHandle,SourceFileDataLength, NULL, FILE_BEGIN);
SetEndOfFile(DestinationFileHandle);
CloseHandle(DestinationFileHandle);
DestinationFileHandle = NULL;
}
} return(IsOk);
}

IOCP IO完成端口的更多相关文章
- 【C# 线程】IOCP IO完成端口-Windows系统下常见的7种I/O模型
一.IOCP(I/O Completion Ports)简介 要实现异步通信,必须要用到一个很风骚的I/O数据结构 ,叫重叠结构"Overlapped",Window ...
- 【windows核心编程】IO完成端口(IOCP)复制文件小例前简单说明
1.关于IOCP IOCP即IO完成端口,是一种高伸缩高效率的异步IO方式,一个设备或文件与一个IO完成端口相关联,当文件或设备的异步IO操作完成的时候,去IO完成端口的[完成队列]取一项,根据完成键 ...
- IO完成端口
从MSDN中翻译了IO完成端口的文章,不得不说翻译的很烂,英语需要继续提高啊... 在一个多处理器系统上,IO完成端口提供一个非常高效的线程模型来处理多个异步IO请求.当一个进程创建了一个IO完成端口 ...
- 异步机制 - IO完成端口
1 KQUEUE KeInitializeQueue VOID KeInitializeQueue( IN PKQUEUE Queue, IN ULONG Count OPTIONAL ); l ...
- 【windows核心编程】IO完成端口(IOCP)复制文件小例
1.演示内容 文件复制 2.提要 复制大文件时,使用FILE_FLAG_NO_BUFFERING标志 同时需要注意: 读写文件的偏移地址为 磁盘扇区 的整数倍 读写文件的字节数为 磁盘扇区 的整数倍 ...
- IOCP Input/Output Completion Port IO完成端口
I/O completion ports provide an efficient threading model for processing multiple asynchronous I/O r ...
- PIC16SCM设置不同IO功耗端口状态的影响
最近做的PIC低功耗微控制器,因此,要设置不同的IO端口状态有关电源的情况测试,在系列万用表的方法来测量电流,供应链管理IO港是在地狱,无头整个系统驱动器.的是PIC16F690单片机. 思路例如以下 ...
- led,key通用IO的端口
1 注意通用IO端口, GPBCON 只能控制一个GPBDAT位(对应的位),而GPBUP可以使能GPBCON.
- Windows服务器高并发处理IOCP(完成端口)详细说明
一. 完成端口的优点 1. 我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S通信模式中性 ...
随机推荐
- Setting the Java Class Path
The class path is the path taht Java Runtime Environment(JRE) searches for classes and other resourc ...
- R语言中知识点总结(二)
一些函数不知道什么意思要查,看数值例子,做笔记,知道函数的功能,函数和返回值. 网页上查找关键词,巧用查找(ctrl+F) 数据读取处理,有read.table read R-读取数据(导入csv ...
- 小程序传id值
xml文件 <view class='bgcf bsbb pl30 pr30 pt30 pb30 df fww' > <block wx:for="{{intr ...
- 更改 Windows 软件默认安装位置教程
在打开的“运行”窗口中,输入命令regedit,然后点击确定按钮 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion 百度 ...
- python-day91--JS实现的ajax
一.AJAX核心(XMLHttpRequest) 其实AJAX就是在Javascript中多添加了一个对象:XMLHttpRequest对象.所有的异步交互都是使用XMLHttpServlet对象完成 ...
- Leetcode 869. 重新排序得到 2 的幂
869. 重新排序得到 2 的幂 显示英文描述 我的提交返回竞赛 用户通过次数102 用户尝试次数134 通过次数103 提交次数296 题目难度Medium 从正整数 N 开始,我们按任何顺序 ...
- [LightOJ 1265] Island of Survival
Island of Survival You are in a reality show, and the show is way too real that they threw into an i ...
- 如何解决Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} failed due to the following error: 8000401a. 问题
在系统中需要把数据导出到excel并且压缩,然后报了这个问题: 我在网站上找到了方法: 地址:https://social.msdn.microsoft.com/Forums/vstudio/en-U ...
- Oracle12c中容错&性能新特性之表空间组
1. 简介 表空间组可以使用户消耗来自多个表空间的临时表空间.表空间组有如下特点: 1) 至少包含一个表空间.表空间组中包含的最大表空间数没有限制. 2) 和表空间共 ...
- java反序列化漏洞原理研习
零.Java反序列化漏洞 java的安全问题首屈一指的就是反序列化漏洞,可以执行命令啊,甚至直接getshell,所以趁着这个假期好好研究一下java的反序列化漏洞.另外呢,组里多位大佬对反序列化漏洞 ...