IOCP 原理 代码
名词解释IOCP简介四-Read

hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
该语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对那个 端口进行标定(引用)。
函数功能:注意该函数实际用于两个明显有别的目的: 1. 用于创建一个完成端口对象。 2. 将一个句柄同完成端口关联到一起。
IOCP简介
IOCP的开发
1、CreateIoCompletionPort
最开始创建一个完成端口时,唯一感兴趣的参数便是 NumberOfConcurrentThreads(并发线程的数量);前面三个参数都会被忽略。 NumberOfConcurrentThreads参数的特殊之处在于,它定义了在一个完成端口上, 同时允许执行的线程数量。理想情况下,我们希望每个处理器各自负责一个线程的运 行,为完成端口提供服务,避免过于频繁的线程“场景”切换。若将该参数设为0Байду номын сангаас 表明系统内安装了多少个处理器,便允许同时运行多少个线程!可用下述代码创建一 个I/O完成端口:
IOCP简介
IOCP的应用
可以看出完成端口是到目前为止最为复杂的输入输出模式。然而,当一个 应用不得不同时处理大量的socket时,它也提供了使系统性能达到最佳的可 能性。只有在被迫面对几百甚至几千个并发的socket、你又希望在添加CPU 后可以获得更好的scale时,才被派上战场。关于完成端口,最重要的是记住 这一点:如果你为winnt/2000开发处理大量socket I/O 请求的高性能服 务,它是你的最佳选择 IOCP不仅仅在通信socket上,同时也可以用于其他方面,例如读写文件, 比如把文件句柄关联到完成端口上,产生一定量的工作器线程,读取文件不同 的部分实际读取数据是系统内部处理,只是读取完了通知一下,并且把相关I O数据填充到结构体中
iocp 编程

IOCP编程什么是IOCPIOCP(Input/Output Completion Ports)是一种高效的异步I/O模型,它在Windows操作系统中提供了对网络编程的支持。
通过使用IOCP,我们可以实现高性能、可伸缩性强的网络应用程序。
在传统的同步I/O模型中,当一个线程在等待数据时,它会被阻塞,直到数据到达。
而在异步I/O模型中,线程不会被阻塞,它可以继续执行其他任务。
IOCP就是基于这种异步I/O模型实现的。
IOCP的工作原理使用IOCP进行编程主要涉及以下几个核心概念:端口(Port)、完成包(Completion Packet)、套接字(Socket)和重叠操作(Overlapped Operation)。
•端口:一个端口代表一个I/O设备或者一个文件。
每个端口都有一个关联的完成端口。
•完成包:完成包是指一个I/O操作完成时所生成的信息块。
它包含了完成的状态、相关参数和返回值等信息。
•套接字:套接字是网络编程中用于进行通信的抽象概念。
•重叠操作:重叠操作是指一次I/O操作请求,在请求发出之后,线程就可以继续执行其他任务了。
IOCP主要通过以下几个步骤来实现异步I/O:1.创建一个完成端口(Completion Port)。
2.创建一个或多个工作者线程(Worker Thread),这些线程用于处理I/O操作。
3.将套接字关联到完成端口上,使得该套接字上的I/O操作能够被异步处理。
4.当有I/O操作完成时,系统会将相关的完成包放入完成队列中。
5.工作者线程从完成队列中获取完成包,并进行相应的处理。
IOCP的优势和适用场景相比于传统的同步阻塞模型,IOCP具有以下几个优势:1.高性能:IOCP能够充分利用CPU资源,提高程序的并发处理能力。
它通过异步I/O模型,使得线程在等待数据时不被阻塞,可以继续执行其他任务,从而充分利用了CPU资源。
2.可伸缩性:IOCP可以轻松地扩展到支持大量的并发连接。
C++Socket编程—socket网络模型之IOCP

C++Socket编程—socket⽹络模型之IOCP⽹络模型—IOCP模型⼀. 什么是IOCP?什么是IOCP模型?IOCP模型有什么作⽤?1) IOCP(I/O Completion Port),常称I/O完成端⼝。
2) IOCP模型属于⼀种通讯模型,适⽤于(能控制并发执⾏的)⾼负载服务器的⼀个技术,适⽤于⼤型项⽬,处理⾼并发问题。
3) 通俗⼀点说,就是⽤于⾼效处理很多很多的客户端进⾏数据交换的⼀个模型。
4) 或者可以说,就是能异步I/O操作的模型。
⼆. IOCP ⼯作机制尽管select、WSAA、WSAE这些socket通信模型可以让我们不⽤开更多的线程来处理每⼀连接,但它们收发数据时仍然要调⽤Recv和Send,Recv和Send实际上仍然会与操作系统底层进⾏交互,仍然会进⼊内核,所以还是会有效率上的损失。
IOCP怎么解决这个问题呢?IOCP有⼀个队列,当你要发数据时,收数据和连接时,都交由IOCP队列处理,不会与操作系统底层交互。
发送数据时,先将缓冲区和长度封好,这个请求会发送到IOCP队列,IOCP内部会帮你把请求发出去。
收数据时,收数据的请求丢掉IOCP队列,IOCP会将收到的数据填⼊指定的缓冲区⾥边,当数据收好后会通知你来收数据。
建⽴连接时,IOCP帮你把连接建⽴好,告诉你新的连接已经来了。
开发者使⽤IOCP时⽆需关注数据收、发、连接,只需关注处理数据三. IOCP的存在理由(IOCP的优点)及技术相关有哪些?IOCP是⽤于⾼效处理很多很多的客户端进⾏数据交换的⼀个模型,那么,它具体的优点有些什么呢?它到底⽤到了哪些技术了呢?在Windows环境下⼜如何去使⽤这些技术来编程呢?它主要使⽤上哪些API函数呢?1) 使⽤IOCP模型编程的优点①帮助维持重复使⽤的内存池。
(与重叠I/O技术有关)②去除删除线程创建/终结负担。
③利于管理,分配线程,控制并发,最⼩化的线程上下⽂切换。
简述iocp模型的原理和工作过程

简述iocp模型的原理和工作过程IOCP模型的原理和工作过程如下:原理:IOCP模型的核心原理是利用操作系统提供的异步I/O和内核级事件通知机制。
异步I/O使得应用程序可以在等待I/O完成时继续处理其他任务,而内核级事件通知机制可以使得操作系统在I/O完成后主动通知应用程序。
通过将I/O操作的处理放在操作系统层面,IOCP模型能够实现高并发、高吞吐量的网络通信。
工作过程:1.创建IOCP对象:应用程序首先创建一个IOCP对象,用于和操作系统进行通信。
2.绑定套接字:应用程序将要进行异步I/O操作的套接字与IOCP对象进行关联。
3.接受连接:应用程序使用套接字进行监听,并且接受到客户端连接请求后,将连接套接字与IOCP对象进行关联,从而使得这个连接套接字能够参与IOCP模型的异步I/O操作。
4.发送和接收数据:应用程序通过调用操作系统提供的异步I/O操作函数,发起网络数据的发送和接收操作。
在发送和接收操作完成之前,应用程序可以继续处理其他任务。
5.等待通知:在发送和接收操作完成之后,应用程序会调用一个等待通知的函数,将自己挂起,等待操作系统的通知。
6.I/O完成通知:当操作系统中发生I/O操作完成的事件时,IOCP对象会通知应用程序,并将I/O操作的结果返回给应用程序。
7.处理完成的I/O操作:应用程序在收到I/O完成的通知后,可以根据返回的结果进行相应的处理。
通常会将I/O操作的结果放入一个队列中,以便后续的处理。
8.处理队列中的完成操作:应用程序会不断地从队列中取出已完成的I/O操作,并进行相应的处理。
处理完成的操作后,应用程序可以继续发起新的I/O操作,从而实现不断地进行网络通信。
总结:IOCP模型利用操作系统提供的异步I/O和内核级事件通知机制,将网络通信的I/O操作交给操作系统来处理,从而实现了高并发、高吞吐量的网络通信。
应用程序通过调用操作系统提供的异步I/O函数来发起发送和接收数据的操作,在操作完成前可以继续处理其他任务。
IOCP

完成IO使用总结IOCP(I/O Completion Port,I/O完成端口)是性能最好的一种I/O模型。
它是应用程序使用线程池处理异步I/O请求的一种机制。
在处理多个并发的异步I/O请求时,以往的模型都是在接收请求是创建一个线程来应答请求。
这样就有很多的线程并行地运行在系统中。
而这些线程都是可运行的,Windows内核花费大量的时间在进行线程的上下文切换,并没有多少时间花在线程运行上。
再加上创建新线程的开销比较大,所以造成了效率的低下。
调用的步骤如下:抽象出一个完成端口大概的处理流程:1:创建一个完成端口。
2:创建一个线程A。
3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是个阻塞函数。
4:主线程循环里调用accept等待客户端连接上来。
5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort 关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。
6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。
7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。
8:A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。
9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。
归根到底概括完成端口模型一句话:我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO 都完成了,那么就在完成端口那里排成一个队列)。
iocp_2_重叠异步IO

IOCP(完成端口)是什么以下就是IOCP的模型图,能看懂么?IOCP模型(图1.1)IOCP是什么IOCP模型相对于其它模型(select,。
),确实有点复杂。
它虽然就那么几个I/O操作函数,但它们都有一大串的参数,返回值的多样性,大大的加大了理解难度。
但是如果你理解了它,掌握了难点重点,也就那么一回事。
可以从以下几方理解什么是IOCP。
一、IOCP的实现步骤。
二、IOCP的核心--重叠异步I/O。
三、如何提交I/O请求。
四、如何取得I/O返回的操作结果,并作处理。
五、IOCP的一个完整例子。
< 二>重叠异步I/O重叠异步I/O要点:1.驱动原理2.VOERLAPPED 结构的作用3.OVERLAPPED 结构的扩展和应用1.重叠异步I/O的驱动原理IOCP的核心就是重叠异步I/O。
首先理解它的工作原理,什么是重叠异步I/O呢?重叠异步I/O的原理是:客户(应用程序)向I/O管理器提交I/O操作(读或写数据)请求,然后去忙其它事。
I/O操作完成之后,I/O管理器再通知客户。
同样,IOCP就是socket向I/O管理器提交各种请求,然后等待并取得I/O管理器返回的结果,如图1.1 所示。
IOCP就像你去银行办理业务,先去取个号码,然后等待叫号。
在拿到号码到被叫号期间,你可以干什么都行(IOCP和叫号还是有点区别的,就是IOCP会保留结果直到被取走,而叫号在叫到你的号码时,你不在,那么你的号码就作废了)。
也可以这么理解重叠异步I/O,就是在一头提交I/O请求(可以一次或多次—叫重叠),然后再另一头取得I/O操作的结果。
这两个操作是不同时、不连续的,这就是异步的原因。
2.VOERLAPPED 结构的作用提交I/O请求的socket函数有:ConnectEx、AcceptEx、WSASend、WSARecv。
取得I/O操作返回结果的函数有:GetQueuedCompletionStatus,GetOverlappedResult 等。
iocp流程

iocp流程I/O Completion Port (IOCP) 是 Windows 系统提供的一种高效的 I/O 模型,可以大大提高网络编程的效率和性能。
在分析 IOCP 流程之前,首先要了解 IOCP 的一些基本原理和用法。
IOCP 基本原理IOCP 的基本原理是通过 CreateIoCompletionPort 函数创建一个 I/O 完成端口句柄,将相关的操作(如异步 I/O 操作)关联到该句柄上,并通过 GetQueuedCompletionStatus 函数等待 I/O 完成事件的发生,当事件发生后,系统会自动调用已经与完成端口关联的回调函数来处理该事件。
IOCP 使用步骤下面是使用 IOCP 模型进行网络编程的基本步骤:1. 创建 I/O 完成端口句柄使用 CreateIoCompletionPort 函数创建一个 I/O 完成端口句柄,并指定线程池大小(ThreadCount),通常设置为处理器数量的两倍或三倍,具体根据实际情况进行调整,以提高并发处理能力。
2. 创建套接字并绑定到 I/O 完成端口使用 socket 函数创建一个网络套接字,并将其绑定到 I/O 完成端口句柄上,以便在套接字上的 I/O 操作完成时能够自动通知 I/O 完成端口。
3. 提交 I/O 操作使用 WSARecv 和 WSASend 等函数提交 I/O 操作,将 I/O 操作相关的参数(如套接字句柄、缓冲区、长度等)传递给函数,函数会马上返回,操作会在后台异步执行。
4. 获取 I/O 完成事件使用 GetQueuedCompletionStatus 函数从 I/O 完成端口句柄上获取I/O 完成事件,并将相关的参数(如套接字句柄、缓冲区、长度等)传递给回调函数,回调函数会根据事件类型进行相应的处理(如接收数据或发送数据)。
5. 处理 I/O 完成事件在回调函数中,根据写操作或是读操作的不同,调用相应的函数(如WSASend 或 WSARecv)处理 I/O 完成事件,并提交下一轮 I/O 操作,以便继续异步执行。
IOCP完全解析

DWORD Flags = 0; // 单 I/O 操作数据 LPPER_IO_DATA PerIoData = NULL; PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA)); ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); PerIoData->DataBuf.len = 1024; PerIoData->DataBuf.buf = PerIoData->buffer; PerIoData->OperationType = 0; // read WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &(PerIoData->Overlapped), NULL); } /**/////////////////////////////////////////////////////////////////////////// return nRetCode; } /**/////////////////////////////////////////////////////////////////////////// DWORD WINAPI ServerWorkerThread(LPVOID lpParam) { HANDLE CompletionPort = (HANDLE)lpParam; DWORD BytesTransferred; LPOVERLAPPED lpOverlapped; LPPER_HANDLE_DATA PerHandleData = NULL; LPPER_IO_DATA PerIoData = NULL; DWORD SendBytes; DWORD RecvBytes; DWORD Flags; BOOL bRet = FALSE; while (TRUE) { bRet = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (PULONG_PTR) &PerHandleData,
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Windows I/O完成端口2009-10-30 10:51WINDOWS完成端口编程1、基本概念2、WINDOWS完成端口的特点3、完成端口(Completion Ports )相关数据结构和创建4、完成端口线程的工作原理5、Windows完成端口的实例代码WINDOWS完成端口编程摘要:开发网络程序从来都不是一件容易的事情,尽管只需要遵守很少的一些规则:创建socket,发起连接,接受连接,发送和接收数据,等等。
真正的困难在于:让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。
利用Windows完成端口进行重叠I/O的技术,可以很方便地在Windows平台上开发出支持大量连接的网络服务程序。
本文介绍在Windows平台上使用完成端口模型开发的基本原理,同时给出实际的例子。
本文主要关注C/S结构的服务器端程序,因为一般来说,开发一个大容量、具有可扩展性的winsock程序就是指服务程序。
1、基本概念设备---指windows操作系统上允许通信的任何东西,比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。
绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile 等,所以我们不能看到**File函数就只想到文件设备。
与设备通信有两种方式,同步方式和异步方式:同步方式下,当调用ReadFile这类函数时,函数会等待系统执行完所要求的工作,然后才返回;异步方式下,ReadFile这类函数会直接返回,系统自己去完成对设备的操作,然后以某种方式通知完成操作。
重叠I/O----顾名思义,就是当你调用了某个函数(比如ReadFile)就立刻返回接着做自己的其他动作的时候,系统同时也在对I/0设备进行你所请求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。
所以,重叠I/O是在异步方式下使用I/O设备的。
重叠I/O需要使用的一个非常重要的数据结构:OVERLAPPED。
2、WINDOWS完成端口的特点Win32重叠I/O(Overlapped I/O)机制允许发起一个操作,并在操作完成之后接收信息。
对于那种需要很长时间才能完成的操作来说,重叠IO机制尤其有用,因为发起重叠操作的线程在重叠请求发出后就可以自由地做别的事情了。
在WinNT和Win2000上,提供的真正可扩展的I/O模型就是使用完成端口(Completion Port)的重叠I/O。
完成端口---是一种WINDOWS内核对象。
完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非得使用完成端口不可,同样设备内核对象、事件对象、告警I/0等也可使用。
但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活地决定线程个数,而且可以减少线程调度的次数从而提高性能。
其实类似于WSAAsyncSelect和select函数的机制更容易兼容Unix,但是难以实现我们想要的“扩展性”。
而且windows完成端口机制在操作系统的内部已经作了优化,从而具备了更高的效率。
所以,我们选择完成端口开始我们的服务器程序开发。
1)发起操作不一定完成:系统会在完成的时候通知你,通过用户在完成端口上的等待,处理操作的结果。
所以要有检查完成端口和取操作结果的线程。
在完成端口上守候的线程系统有优化,除非在执行的线程发生阻塞,不会有新的线程被激活,以此来减少线程切换造成的性能代价。
所以如果程序中没有太多的阻塞操作,就没有必要启动太多的线程,使用CPU数量的两倍,一般这么多线程就够了。
2)操作与相关数据的绑定方式:在提交数据的时候用户对数据打上相应的标记,记录操作的类型,在用户处理操作结果的时候,通过检查自己打的标记和系统的操作结果进行相应的处理。
3)操作返回的方式:一般操作完成后要通知程序进行后续处理。
但写操作可以不通知用户,此时如果用户写操作不能马上完成,写操作的相关数据会被暂存到非交换缓冲区中,在操作完成的时候,系统会自动释放缓冲区,此时发起完写操作,使用的内存就可以释放了。
但如果占用非交换缓冲太多会使系统停止响应。
3、完成端口(Completion Ports )相关数据结构和创建其实可以把完成端口看成系统维护的一个队列,操作系统把重叠IO操作完成的事件通知放到该队列里,由于是暴露“操作完成”的事件通知,所以命名为“完成端口”(Completion Ports)。
一个socket被创建后,就可以在任何时刻和一个完成端口联系起来。
OVERLAPPED数据结构typedef struct _OVERLAPPED {ULONG_PTR Internal; //被系统内部赋值,用来表示系统状态ULONG_PTR InternalHigh; //被系统内部赋值,表示传输的字节数union {struct {DWORD Offset; //与OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始操作DWORD OffsetHigh; //如果不是对文件I/O来操作,则Offset必须设定为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值。
一般来说,一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。
工作线程的数量依赖于程序的具体需要。
但是在理想的情况下,应该对应一个CPU 创建一个线程。
因为在完成端口理想模型中,每个线程都可以从系统获得一个“原子”性的时间片,轮番运行并检查完成端口,线程的切换是额外的开销。
但在实际开发的时候,还要考虑这些线程是否牵涉到其他堵塞操作的情况。
如果某线程进行堵塞操作,系统则将其挂起,让别的线程获得运行时间。
因此,如果有这样的情况,可以多创建几个线程来尽量利用时间。
创建完成端口的函数完成端口是一个内核对象,使用时它总是要和至少一个有效的设备句柄相关联,完成端口是一个复杂的内核对象,创建它的函数是:HANDLE CreateIoCompletionPort(IN HANDLE FileHandle,IN HANDLE ExistingCompletionPort,IN ULONG_PTR CompletionKey,IN DWORD NumberOfConcurrentThreads);通常创建工作分两步:第一步,创建一个新的完成端口内核对象,可以使用下面的函数:HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads){returnCreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThrea ds);};第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey){HANDLEh=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);return h==hCompPort;};说明如下:1)CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄。
2)CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。
3)NumberOfConcurrentThreads用来指定要允许同时运行的的线程的最大个数,通常我们指定为0,这样系统会根据CPU的个数来自动确定。
4)创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。
如果你有多个完成端口,就会有多个对应的设备列表。
如果设备句柄被关闭,则表中该纪录会被自动删除。
4、完成端口线程的工作原理1)完成端口管理线程池完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们自己使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。
该函数原型:BOOL GetQueuedCompletionStatus(IN HANDLE CompletionPort,OUT LPDWORD lpNumberOfBytesTransferred,OUT PULONG_PTR lpCompletionKey,OUT LPOVERLAPPED *lpOverlapped,IN DWORD dwMilliseconds);这个函数试图从指定的完成端口的I/0完成队列中提取纪录。
只有当重叠I/O 动作完成的时候,完成队列中才有纪录。
凡是调用这个函数的线程将会被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。