完成端口:Windows完成端口编程

目录
基本概念
2 OVERLAPPED数据结构
3 完成端口内部机制
创建完成端口
完成端口线程工作原理
线程间数据传递
线程安全退出

基本概念
       设备---windows操作系统上允许通信任何东西比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等绝大多数和设备打交道都是CreateFile/ReadFile/WriteFile等所以我们不能看到**File就只想到文件设备

       和设备通信有两种方式同步方式和异步方式同步方式下ReadFile会等待系统执行完所要求工作然后才返回;异步方式下ReadFile这类会直接返回系统自己去完成对设备操作然后以某种方式通知完成操作

       重叠I/O----顾名思义当你了某个(比如ReadFile)就立刻返回做自己其他动作时候同时系统也在对I/0设备进行你要求操作在这段时间内你和系统内部动作是重叠因此有更好性能所以重叠I/O是用于异步方式下使用I/O设备

重叠I/O需要使用个非常重要数据结构OVERLAPPED

       完成端口---是种WINDOWS内核对象完成端口用于异步方式重叠I/0情况下当然重叠I/O不定非使用完成端口不可还有设备内核对象、事件对象、告警I/0等但是完成端口内部提供了线程池管理可以避免反复创建线程开销同时可以根据CPU个数灵活决定线程个数而且可以让减少线程调度次数从而提高性能





2 OVERLAPPED数据结构
typedef struct _OVERLAPPED {



    ULONG_PTR Internal;//被系统内部赋值用来表示系统状态



    ULONG_PTR InternalHigh;// 被系统内部赋值传输字节数



    union {



        struct {



            DWORD Off;//和OffHigh合成个64位整数用来表示从文件头部多少字节开始



            DWORD OffHigh;//操作如果不是对文件I/O来操作则必须设定为0



        };



        PVOID Poer;



    };



    HANDLE  hEvent;//如果不使用就务必设为0,否则请赋个有效Event句柄



} OVERLAPPED, *LPOVERLAPPED;







下面是异步方式使用ReadFile个例子



OVERLAPPED Overlapped;



Overlapped.Off=345;



Overlapped.OffHigh=0;



Overlapped.hEvent=0;



//假定其他参数都已经被



ReadFile(hFile,buffer,(buffer),&dwNumBytesRead,&Overlapped);



这样就完成了异步方式读文件操作然后ReadFile返回由操作系统做自己事情吧



     



下面介绍几个和OVERLAPPED结构相关



等待重叠I/0操作完成



BOOL GetOverlappedResult (

HANDLE hFile,

LPOVERLAPPED lpOverlapped,//接受返回重叠I/0结构

LPDWORD lpcbTransfer,//成功传输了多少字节数

BOOL fWait //TRUE只有当操作完成才返回FALSE直接返回如果操作没有完成通过调//用GetLastError ( )会返回ERROR_IO_INCOMPLETE



);





宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成该宏对OVERLAPPED结构Internal成员进行了测试查看是否等于STATUS_PENDING值











3 完成端口内部机制
创建完成端口

       完成端口是个内核对象使用时他总是要和至少个有效设备句柄进行关联完成端口是个复杂内核对象创建它是:

HANDLE CreateIoCompletionPort(



    IN HANDLE FileHandle,



    IN HANDLE ExistingCompletionPort,



    IN ULONG_PTR CompletionKey,



    IN DWORD NumberOfConcurrentThreads



    );







通常创建工作分两步:

创建个新完成端口内核对象可以使用下面:

       HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)



{



           CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);



};

       

第 2步将刚创建完成端口和个有效设备句柄关联起来可以使用下面:

       bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)



{



          HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);



           hhCompPort;



};



介绍说明



1)  CreateIoCompletionPort也可以次性既创建完成端口对象又关联到个有效设备句柄



2)  CompletionKey是个可以自己定义参数我们可以把个结构地址赋给它然后在合适时候取出来使用最好要保证结构里面内存不是分配在栈上除非你有十分把握内存会保留到你要使用

3)  NumberOfConcurrentThreads通常用来指定要允许同时运行线程最大个数通常我们指定为0这样系统会根据CPU个数来自动确定





创建和关联动作完成后系统会将完成端口关联设备句柄、完成键作为条纪录加入到这个完成端口设备列表中如果你有多个完成端口就会有多个对应设备列表如果设备句柄被关闭则表中自动删除该纪录











完成端口线程工作原理

       完成端口可以帮助我们管理线程池但是线程池中线程需要我们使用_beghreadex来创建凭什么通知完成端口管理我们新线程呢?答案在GetQueuedCompletionStatus原型:



BOOL GetQueuedCompletionStatus(



    IN  HANDLE CompletionPort,



    OUT LPDWORD lpNumberOfBytesTransferred,



    OUT PULONG_PTR lpCompletionKey,



    OUT LPOVERLAPPED *lpOverlapped,



    IN  DWORD dwMilliseconds



);



这个试图从指定完成端口I/0完成队列中抽取纪录只有当重叠I/O动作完成时候完成队列中才有纪录凡是这个线程将被放入到完成端口等待线程队列中因此完成端口就可以在自己线程池中帮助我们维护这个线程



完成端口I/0完成队列中存放了当重叠I/0完成结果---- 条纪录该纪录拥有 4个字段前 3项就对应GetQueuedCompletionStatus2、3、4参数最后个字段是信息dwError我们也可以通过PostQueudCompletionStatus模拟完成了个重叠I/0操作



当I/0完成队列中出现了纪录完成端口将会检查等待线程队列该队列中线程都是通过GetQueuedCompletionStatus使自己加入队列等待线程队列很简单只是保存了这些线程ID完成端口会按照后进先出原则将个线程队列ID放入到释放线程列表中同时该线程将从等待GetQueuedCompletionStatus返回睡眠状态中变为可调度状态等待CPU调度



基本上情况就是如此所以我们线程要想成为完成端口管理线程就必须要



GetQueuedCompletionStatus出于性能优化实际上完成端口还维护了个暂停线程列表具体细节可以参考Windows高级编程指南我们现在知道知识已经足够了



    



线程间数据传递

       线程间传递数据最常用办法是在_beghreadex中将参数传递给线程或者使用全局变量但是完成端口还有自己传递数据思路方法答案就在于CompletionKey和OVERLAPPED参数

CompletionKey被保存在完成端口设备表中是和设备句柄对应我们可以将和设备句柄相关数据保存到CompletionKey中或者将CompletionKey表示为结构指针这样就可以传递更加丰富内容这些内容只能在开始关联完成端口和设备句柄时候做因此不能在以后动态改变

OVERLAPPED参数是在每次ReadFile这样支持重叠I/0时传递给完成端口我们可以看到如果我们不是对文件设备做操作该结构成员变量就对我们几乎毫无作用我们需要附加信息可以创建自己结构然后将OVERLAPPED结构变量作为我们结构变量个成员然后传递第个成员变量地址给ReadFile类型匹配当然可以通过编译当GetQueuedCompletionStatus返回时我们可以获取到第个成员变量地址然后个简单强制转换我们就可以把它当作完整自定义结构指针使用这样就可以传递很多附加数据了太好了!只有点要注意如果跨线程传递请注意将数据分配到堆上并且接收端应该将数据用完后释放我们通常需要将ReadFile这样异步所需要缓冲区放到我们自定义结构中这样当GetQueuedCompletionStatus被返回时我们自定义结构缓冲区变量中就存放了I/0操作数据



CompletionKey和OVERLAPPED参数都可以通过GetQueuedCompletionStatus获得

线程安全退出

       很多线程为了不止执行异步数据处理需要使用如下语句

while (true)

{

       .

       GetQueuedCompletionStatus(...);



              

}

那么如何退出呢答案就在于上面曾提到PostQueudCompletionStatus我们可以用它发送个自定义包含了OVERLAPPED成员变量结构地址里面包含个状态变量当状态变量为退出标志时线程就执行清除动作然后退出
Tags:  vc完成端口 完成端口模型 delphi完成端口 完成端口

延伸阅读

最新评论

发表评论