一. 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完成端口的更多相关文章

  1. 【C# 线程】IOCP IO完成端口-Windows系统下常见的7种I/O模型

    一.IOCP(I/O Completion Ports)简介        要实现异步通信,必须要用到一个很风骚的I/O数据结构 ,叫重叠结构"Overlapped",Window ...

  2. 【windows核心编程】IO完成端口(IOCP)复制文件小例前简单说明

    1.关于IOCP IOCP即IO完成端口,是一种高伸缩高效率的异步IO方式,一个设备或文件与一个IO完成端口相关联,当文件或设备的异步IO操作完成的时候,去IO完成端口的[完成队列]取一项,根据完成键 ...

  3. IO完成端口

    从MSDN中翻译了IO完成端口的文章,不得不说翻译的很烂,英语需要继续提高啊... 在一个多处理器系统上,IO完成端口提供一个非常高效的线程模型来处理多个异步IO请求.当一个进程创建了一个IO完成端口 ...

  4. 异步机制 - IO完成端口

    1 KQUEUE KeInitializeQueue VOID KeInitializeQueue( IN PKQUEUE  Queue, IN ULONG  Count  OPTIONAL ); l ...

  5. 【windows核心编程】IO完成端口(IOCP)复制文件小例

    1.演示内容 文件复制 2.提要 复制大文件时,使用FILE_FLAG_NO_BUFFERING标志 同时需要注意: 读写文件的偏移地址为 磁盘扇区 的整数倍 读写文件的字节数为 磁盘扇区 的整数倍 ...

  6. IOCP Input/Output Completion Port IO完成端口

    I/O completion ports provide an efficient threading model for processing multiple asynchronous I/O r ...

  7. PIC16SCM设置不同IO功耗端口状态的影响

    最近做的PIC低功耗微控制器,因此,要设置不同的IO端口状态有关电源的情况测试,在系列万用表的方法来测量电流,供应链管理IO港是在地狱,无头整个系统驱动器.的是PIC16F690单片机. 思路例如以下 ...

  8. led,key通用IO的端口

    1 注意通用IO端口, GPBCON 只能控制一个GPBDAT位(对应的位),而GPBUP可以使能GPBCON.

  9. Windows服务器高并发处理IOCP(完成端口)详细说明

    一. 完成端口的优点 1. 我想只要是写过或者想要写C/S模式网络服务器端的朋友,都应该或多或少的听过完成端口的大名吧,完成端口会充分利用Windows内核来进行I/O的调度,是用于C/S通信模式中性 ...

随机推荐

  1. Linux中符号总结

    常用符号~   登陆用户当前的家目录 .   当前目录..   当前目录的上一级目录cd -   返回上一次的目录;   命令分隔符#   表示注释 ?   通配符中表示任意一个字符*   通配符中表 ...

  2. 20190118_xlVBA多表合并

    Public Sub simple() Set wb = ActiveWorkbook Set sht = ActiveSheet msg = MsgBox("程序准备清除活动工作表内容?按 ...

  3. 20180830xlVBA_合并计算

    Sub WorkbooksSheetsConsolidate() Rem 设置求和区域为 sheet名称/单元格区域;sheet名称/单元格区域 Const Setting As String = & ...

  4. p1215

    一开始没用数组,没成功.后来确定用深搜后,用数组.出现一个不同的abc状态就记录下来,以免重复.一开始要倒的肯定是c杯,之后出现新状态要递归dfs3次.另外发现algorithm里的copy是原数组在 ...

  5. 微信小程序如何导入字体图标

    前提:我们已经拥有了从阿里图标库下载下来的一系列的字体图标文件1:找个其中的ttf格式的文件,然后打开https://transfonter.org/网站2:点击Add fonts按钮,加载ttf格式 ...

  6. [LintCode] Number of Islands(岛屿个数)

    描述 给一个01矩阵,求不同的岛屿的个数. 0代表海,1代表岛,如果两个1相邻,那么这两个1属于同一个岛.我们只考虑上下左右为相邻. 样例 在矩阵: [ [1, 1, 0, 0, 0], [0, 1, ...

  7. Ftp服务端安装-Linux环境

    目的 为什么要搭建FTP服务器,进入maven仓库下载Jar包时点击相应的链接进去会看到目录结构,这个就是ftp站点.可以随意的下载. 环境 Linux系统为CentOS6.5 安装步骤 查询是否已安 ...

  8. python3 设置滚动条

    #!python3#coding=utf-8from selenium import webdriverfrom selenium.webdriver.common.by import Byimpor ...

  9. C/S,B/S的区别与联系

    C/S 是Client/Server 的缩写.服务器通常采用高性能的PC.工作站或小型机,并采用 大型数据库系统,如Oracle.Sybase.Informix 或SQL Server.客户端需要安装 ...

  10. js的正则表达式的替换

    <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Inse ...