Windows完成端口编程

《windows核心编程》读书笔记——完成端口 完成端口是windows为了处理不同设备之间并发运行时运行速度差异而设计的一种异步数据处理的机制。完成端口是《windows核心编程》中的一个重点内容,最近我花了三天时间读完核心编程里面关于完成端口的章节,并了解微软相关的技术资料文档,感觉完成端口的设计和实现可以在一定程度上加快windows的服务速度,用不用完成端口对程序逻辑没什么影响,但对于需要速度的服务来说,这就至关重要了。由于源码不公开的 阅读详情
 
目录
一 基本概念
二 OVERLAPPED数据结构
三 完成端口的内部机制
创建完成端口
完成端口线程的工作原理
线程间数据传递
线程的安全退出

一 基本概念
       设备---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的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。

二 OVERLAPPED数据结构
typedef struct _OVERLAPPED {

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

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

    union {
        struct {
            DWORD Offset;//和OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始
            DWORD OffsetHigh;//操作,如果不是对文件I/O来操作,则必须设定为0
        };
        PVOID Pointer;

    };

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

} OVERLAPPED, *LPOVERLAPPED;

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

OVERLAPPED Overlapped;

Overlapped.Offset=345;

Overlapped.OffsetHigh=0;

Overlapped.hEvent=0;

//
假定其他参数都已经被初始化

ReadFile(hFile,buffer,sizeof(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值。

三完成端口的内部机制
创建完成端口

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

HANDLE CreateIoCompletionPort(

    IN HANDLE FileHandle,

    IN HANDLE ExistingCompletionPort,

    IN ULONG_PTR CompletionKey,

    IN DWORD NumberOfConcurrentThreads

    );

通常创建工作分两步:

第一步,创建一个新的完成端口内核对象,可以使用下面的函数:

       HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)

{

          return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);

};

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

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

{

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

          return h==hCompPort;

};

说明

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

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

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

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

完成端口线程的工作原理

       
完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数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完成的结果---- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。
当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID。完成端口会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。

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

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

线程间数据传递

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

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

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

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

线程的安全退出

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

while (true)

{

       .
。。。。。。

       GetQueuedCompletionStatus(...);
              
。。。。。。

}

那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。 
《Windows核心编程》之“完成端口” 《Windows核心编程》第10章开头部分一再强调:“IO Completion Port”是“构建高性能、可升缩的应用程序”的最佳设施之一,它不仅适用于处理设备IO,也适用于其它越来越多的应用场景,比如:Job内核对象、Socket编程等。故此,我单独用一篇博客来讲述“IO Completion Port”的应用场景、原理、用法和示例。 一、缘起 1,线程通信 对于一... 阅读详情

相关推荐

Windows核心编程(十一)I/O完成端口

1、串行模型:一个线程等待一个客户(通常是通过网络)发出请求,当请求到达时,线程会被唤醒并对客户请求进行处理。 并发模型:一个线程等待一个客户请求,并创建一个新的线程来处理请求。 2、完成端口背后的理论是并发运行的线程数量必须有一个上限。由于太多的线程将会导致系统花费很大的代价在各个线程cpu上下文进行切换。使用并发模型与创建进程相比开销要低很多,但是也需要为每个客户请求创建一个新的线程。这开

woshibendangao的专栏 1791

关于windows完成端口(IOCP)的一些理解

       本人很多年前接触完成端口以来,期间学习和练习了很多次,本以为自己真正地理解了其原理,最近在看网狐的服务器端源码时又再一次拾起完成端口的知识,结果发现以前理解的其实很多偏差,有些理解的甚至都是错误的。网络上关于windows完成端口的介绍举不胜举,但大多数都是介绍怎么做,而不是为告诉读者为什么这么做。看了很多遍小猪的讲解:http://blog.youkuaiyun.com/piggyxp/art...

左雪菲的专栏 1万+

Windows完成端口(Completion Port)编程小结

完成端口中ConnectEx的问题:完成端口用于客户端时一定要注意,创建的SOCKET要先随便绑定一个端口(默认0自动分配也可),然后再扔到完成端口中去,之后才可以ConnectEx,否则(不绑定),ConnectEx会出现10022错误。同时,getpeername似乎对ConnectEx不支持,也可能有其他细节设置我暂时不知道,返回的远程地址是无效的(仅对于完成端口用于服务器accept回

陈敏的博客 6906

Windows 完成端口编程

Windows 完成端口编程本文为转载, 原文地址: http://xingzhesun.blogbus.com/logs/3925649.htmlTable of Contents1 基本概念2 OVERLAPPED数据结构3 完成端口的内部机制3.1 创建完成端口3.2 完成端口线程的工作原理

guolihui112的专栏 2658

Windows 核心编程之10 IO完成端口

WAIT_IO_COMPLETION

软件开发 1733

直播间人气协议算法:从批量操作到数据接口的自动化实现

本文深入解析直播间人气协议算法的实现原理与技术细节,涵盖批量账号管理、IP代理方案、用户行为模拟算法及平台数据接口调用策略。通过逆向工程分析通信协议,结合算法设计实现自动化流量提升,同时探讨风控规避与系统性能优化方案,为开发者提供合规的技术实践参考。

weixin_30505225的博客 517

windows io完成端口实例

#pragma once #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib") #define DATA_BUFSIZE 512 enum { READ, WRITE }; typedef struct io_operation_data { OVERLAPPED overlapp...

qq_25160759的博客 469

文石NoteX3国行版安装 Google Play经验分享

文石NoteX3国行版安装 Google Play经验分享。

2301_80693261的博客 7596

Windows完成端口编程(转载)

Windows完成端口编程目录一 基本概念二 OVERLAPPED数据结构三 完成端口的内部机制创建完成端口完成端口线程的工作原理线程间数据传递线程的安全退出一 基本概念      设备---windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多

tjb_1216的专栏 2172

宏基因组分析|完整分析流程梳理

今天先从最基础的全流程框架说起,无论是刚接触的萌新,还是想查漏补缺的同行,都能找到有用的信息。

weixin_70911870的博客 3164

自制非接触式静电探测仪:从振动电容原理到微弱信号检测实践

微弱信号检测是电子测量领域的核心挑战之一,尤其在需要避免物理接触或防止干扰的场景中。其基本原理在于将难以直接获取的微小物理量(如电流、电压)通过传感器转换为电信号,再经过放大、滤波等处理进行提取。锁相放大技术是解决这一问题的关键手段,它利用参考信号与目标信号的同步性,从强噪声背景中恢复出有效信息,极大提升了信噪比和测量灵敏度。这项技术的工程价值在于实现了对纳安级电流、微伏级电压等高难度参数的可靠测量。在静电防护、材料表面电位分析、生物电信号采集等应用场景中,非接触式测量需求广泛。本文聚焦的基于振动电容原理的

weixin_32349093的博客 273
上一篇: indy阻塞模式
下一篇: Winsock完成端口模型-Delphi代码
Devillyd
博客等级 码龄23年 58粉丝 101原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值