Windows 完成端口编程
本文为转载, 原文地址:http://xingzhesun.blogbus.com/logs/3925649.html
1基本概念
设备—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的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。
2OVERLAPPED数据结构
- typedefstruct_OVERLAPPED
- {
- ULONG_PTRInternal;
- ULONG_PTRInternalHigh;
- union{
- struct
- {
- DWORDOffset;
- DWORDOffsetHigh;
- };
- PVOIDPointer;
- };
- HANDLEhEvent;
- }OVERLAPPED,*LPOVERLAPPED;
下面是异步方式使用ReadFile的一个例子
- OVERLAPPEDOverlapped;
- Overlapped.Offset=345;
- Overlapped.OffsetHigh=0;
- Overlapped.hEvent=0;
- ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
这样就完成了异步方式读文件的操作,然后ReadFile函数返回,由操作系统做自己的事情吧
下面介绍几个与OVERLAPPED结构相关的函数 等待重叠I/0操作完成的函数
- BOOLGetOverlappedResult(
- ANDLEhFile,
- LPOVERLAPPEDlpOverlapped,
- LPDWORDlpcbTransfer,
- BOOLfWait
- );
宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成,该宏对OVERLAPPED结构的 Internal 成员进行了测试,查看是否等于STATUS_PENDING值。
3完成端口的内部机制
3.1创建完成端口
完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:
- HANDLECreateIoCompletionPort(
- INHANDLEFileHandle,
- INHANDLEExistingCompletionPort,
- INULONG_PTRCompletionKey,
- INDWORDNumberOfConcurrentThreads);
通常创建工作分两步:
第一步,创建一个新的完成端口内核对象,可以使用下面的函数:
- HANDLECreateNewCompletionPort(DWORDdwNumberOfThreads)
- {
- returnCreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
- };
第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:
- boolAssicoateDeviceWithCompletionPort(HANDLEhCompPort,HANDLEhDevice,DWORDdwCompKey)
- {
- HANDLEh=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
- returnh==hCompPort;
- };
说明
CreateIoCompletionPort
函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄
CompletionKey
是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
NumberOfConcurrentThreads
通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。
创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。
3.2完成端口线程的工作原理
完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。 该函数原型:
- BOOLGetQueuedCompletionStatus(
- INHANDLECompletionPort,
- OUTLPDWORDlpNumberOfBytesTransferred,
- OUTPULONG_PTRlpCompletionKey,
- OUTLPOVERLAPPED*lpOverlapped,
- INDWORDdwMilliseconds
- );
这个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。
完成端口的I/0完成队列中存放了当重叠I/0完成的结果— 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。
基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考《Windows高级编程指南》,我们现在知道的知识,已经足够了。
3.3线程间数据传递
线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。
CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。
OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当*GetQueuedCompletionStatus* 被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。
CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。
3.4线程的安全退出
很多线程为了不止一次的执行异步数据处理,需要使用如下语句
- while(true)
- {
- ....
- GetQueuedCompletionStatus(...);
- }
那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。
分享到:
相关推荐
WINDOWS完成端口编程 1、基本概念 2、WINDOWS完成端口的特点 3、完成端口(Completion Ports )相关数据结构和创建 4、完成端口线程的工作原理 5、Windows完成端口的实例代码
Windows_IO完成端口编程 Windows_IO完成端口编程
windows socket网络编程之iocp完成端口模型的例子
windows TCP/IP 网络编程(七)5种windows网络模型(5)完成端口
Windows系统编程之异步IO和完成端口.
一个IOCP的示例,封装了IO完成端口的IO模型。可以用做学习参考,也可以直接使用其中封装的类。-a IOCP example, the complete package, IO port IO model. Learning can be used as reference, which can be used ...
windows编程下的完成端口标准模板,移植性很高,希望能帮助大家!
VC++WINDOWS编程,可用、实用,简单的表述出完成端口模型。可以直接用
想学习windows网络编程技术,这篇奇文一定不能错过哦!
详细讲解WINDOWS下的网络编程技术,包括各种模型,如完成端口模型等,也涉及MFC下的网络编程技术
windows TCP/IP 网络编程(七)5种windows网络模型(5)完成端口 思维导图
很好的学习资料,Socket编程、...《手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解》By PiggyXP 资源包内附文章内容和源码。 原文地址: http://blog.csdn.net/piggyxp/article/details/6922277
Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解...
本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件的...
本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件的...
本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件的...
本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件的...
Delphi完成端口模型 原文出处 《Windows网络编程技术》第8章 完成端口模型 由于原书附的是C代码,我把其翻译成Delphi代码。
网络编程丛书,Windows网络编程(第2版)-源代码,本书全套源码,人民邮电出版社,本书主编杨秋黎。