完成端口之个人理解
来源:互联网 发布:淘宝不上传身份证清关, 编辑:程序博客网 时间:2024/04/30 09:27
下文只是对完成端口的简单介绍,有些是自己的理解,可能不太正确。详细内容可以查看《Windows核心编程》
完成端口(简称IOCP)是最为复杂的Windows内核对象,同时也是最有效的异步I/O模型。
IOCP完成端口可以绑定一个文件句柄(HANDLE),以对其进行异步I/O。系统负责具体的I/O操作,当完成之后由I/O系统把完成消息(completion packet )通过函数(自动调用)PostQueuedCompletionStatus()传送到完成端口的I/O完成队列中,从而唤醒完成端口对应的工作线程(自己创建)。
关于完成端口的线程池:
工作线程由用户自己创建,并且所有的工作线程应该执行同一个回调函数;
工作线程负责 完成端口在得到I/O系统的完成通知后 的具体处理;
用户创建的工作线程组成完成端口的线程池;
主要函数介绍:
(1) CreateIoCompletionPort()函数
功能:这个函数完成两个不同的任务:1、创建一个完成端口对象;2、将文件句柄关联到I/O完成端口对象。
一般可分成两个小函数对CreateIoCompletionPort()函数调用进行抽象。
函数原型:
HANDLE WINAPI CreateIoCompletionPort( __in HANDLE FileHandle, __in HANDLE ExistingCompletionPort, __in ULONG_PTR CompletionKey, __in DWORD NumberOfConcurrentThreads);
在创建完成端口时,前面的三个参数为固定值:INVALID_HANDLE_VALUE,NULL,0
最后一个参数NumberOfConcurrentThreads表示同时最多有多少线程处于可运行状态。一般选择默认值0,就是允许并发执行的线程数量等于主机的CPU数量,从而可以避免在线程之间切换的开销;
返回值:如果成功,则返回值为完成端口对应的句柄;否则,返回NULL;
在绑定文件句柄到完成端口时,对于参数CompletionKey,系统不会管到底是一个什么样的值,由用户自己负责(可以传递一个数据结构的指针给它,以唯一标识文件句柄)
(2) GetQueuedCompletionStatus()函数
内核机制:在绑定文件句柄到完成端口后,系统会自动为完成端口监听对应文件句柄的I/O操作。当I/O完成之后,系统给完成端口的I/O完成队列中添加一个成员,包括I/O传输的字节数、重叠结构、CompletionKey等信息。
该函数是阻塞函数,它会使完成端口以先入先出的顺序(所以是"Queued")输出一个I/O完成队列。如果完成端口对应的I/O完成队列为空,则调用该函数的线程会处于休眠状态,直到完成端口接收到I/O系统的完成消息(I/O完成队列不为空)时激活,并把信息存储在相应的内存中。
函数原型:
BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, __out LPDWORD lpNumberOfBytes, __out PULONG_PTR lpCompletionKey, __out LPOVERLAPPED* lpOverlapped, __in DWORD dwMilliseconds);
获取I/O的内容的方法(非常重要):
这里介绍比较巧妙和隐蔽的一个方法,在书上看到的,我自己想的话可能想不出 ^_^
方法一:首先需要注意到GetQueuedCompletionStatus()函数中的LPOVERLAPPED * lpOverlapped参数,实际是一个指针,所以我们也可以传递其他类型的指针给它,以获取我们想要的信息。实际上OVERLAPPED结构体只存储一些简单的信息,因此我们可以设计一个结构体,使它包含其他的一些信息,如下面的例子:
//扩展重叠结构typedef struct _PER_IO_DATA{OVERLAPPED ol;//重叠结构char buf[BUFFER_SIZE];//数据缓冲区int nOperationType;//操作类型#define OP_READ 1#define OP_WRITE 2#define OP_ACCEPT 3}PER_IO_DATA, * PPER_IO_DATA;
需要注意:
重叠结构一定要放在该结构体的第一个位置,这样OVERLAPPED结构体和PER_IO_DATA结构体的首地址相同,方便进行转换。因为GetQueuedCompletionStatus()函数在获取到的I/O完成队列中存储的是OVERLAPPED结构体的地址,需要进行一个OVERLAPPED结构体到PER_IO_DATA结构体的转换(确实是设计的很巧妙),以获取I/O的内容。
下面是具体步骤:
首先,申请一个全局的内存地址(必须使用GlobalAlloc()函数,否则出错)给上面介绍的扩展的重叠结构,在提交I/O申请的时候使用该扩展结构体的ol作为重叠结构,并传递该扩展结构的Buffer给I/O。这样,把存储的信息放在扩展结构体中;
在工作线程中获取该结构体的地址(由于OVERLAPPED结构体是在PER_IO_DATA结构体的首位,所以这两个的地址是相同的!),并将其转为PER_IO_DATA结构体,访问其中的buf变量就可以获取到I/O接收或发送到的消息。
方法二:
放在作为CompletionKey对应的结构体中。这个相对扩展OVERLAPPED结构体更简单,而且不需要把OVERLAPPED结构体放在扩展结构体的首位。
具体的使用步骤与方法一相同。
注意事项:
怕注意不到,单列出来说。
为了获取文档句柄对应的I/O信息,必须申请全局的内存地址,使用GlobalAlloc()函数
HGLOBAL WINAPI GlobalAlloc( __in UINT uFlags, __in SIZE_T dwBytes);
其中,参数uFlags指定分配内存的类型,此处取值uFlags=GPTR,以分配固定位置、且全部清零的内存块。
完成端口的使用:
主线程:
(1) 创建一个完成端口;
(2) 创建工作线程(一个或多个),并把完成端口作为参数传递给工作线程;
(3) 把文件句柄绑定到完成端口;
(4) 提交文件句柄的I/O到系统(需要使用OVERLAPPED结构体);
工作线程:
(1) 获取完成端口;
(2) 获得完成端口的I/O完成队列,并获取I/O消息以进行相应的消息处理;
(3) 如果需要重复的进行I/O,则继续提交I/O到系统;
示例:
IOCP也主要用于网络通信方面,把套接字绑定到完成端口上,并为之创建线程,以负责完成端口在获取到I/O完成消息之后的消息处理。
下面以一个简单的例子(该例子是Windows网络与通信程序设计中的例子)。
实现功能:服务器端使用完成端口进行接收来自客户端发送过来的TCP消息,并进行显示。
具体步骤:
1:创建一个完成端口CreateIoCompletionPort();
2:创建一个线程A;
3:A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果,这个函数是个阻塞函数。
4:主线程循环里调用accept等待客户端连接上来。
5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort()关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用以提交I/O操作,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。
6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。
7:WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。
8:A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。
9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。
/*IOCPDemo.cpp文件 调试通过*//*注意,使用CompletionKey来存储接收到的信息*/#include "WSAInit.h"#include <stdio.h>#include <windows.h>// 初始化Winsock库CWSAInit theSock;#define BUFFER_SIZE 1024#define OP_READ 1#define OP_WRITE 2#define OP_ACCEPT 3// per-handle数据typedef struct _PER_HANDLE_DATA {SOCKET s;// 对应的套节字句柄 sockaddr_in addr;// 客户方地址char buf[BUFFER_SIZE];// 数据缓冲区int nOperationType;// 操作类型}PER_HANDLE_DATA, *PPER_HANDLE_DATA;//工作线程,负责I/O完成之后的消息处理DWORD WINAPI ServerThread(LPVOID lpParam){// 得到完成端口对象句柄HANDLE hCompletion = (HANDLE)lpParam;DWORD dwTrans;PPER_HANDLE_DATA pPerHandle;OVERLAPPED *pOverlapped;while(TRUE){// 在关联到此完成端口的所有套节字上等待I/O完成BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, &dwTrans, (PULONG_PTR)&pPerHandle, &pOverlapped, WSA_INFINITE);if(!bOK)// 在此套节字上有错误发生{::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pOverlapped);continue;}// 套节字被对方关闭if(dwTrans == 0 && (pPerHandle->nOperationType == OP_READ || pPerHandle->nOperationType == OP_WRITE)) {::closesocket(pPerHandle->s);::GlobalFree(pPerHandle);::GlobalFree(pOverlapped);continue;}// 通过per-I/O数据中的nOperationType域查看什么I/O请求完成了switch(pPerHandle->nOperationType){case OP_READ:// 完成一个接收请求{pPerHandle->buf[dwTrans] = '\0';printf(pPerHandle-> buf);// 继续投递接收I/O请求WSABUF buf;buf.buf = pPerHandle->buf;buf.len = BUFFER_SIZE;pPerHandle->nOperationType = OP_READ;DWORD nFlags = 0; ::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, pOverlapped, NULL); }break;case OP_WRITE:// 本例中没有投递这些类型的I/O请求case OP_ACCEPT:break;}//end of switch}//end of whilereturn 0;}void main(){int nPort = 4567;// 创建完成端口对象,创建工作线程处理完成端口对象中事件HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);//创建工作线程::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0); // 创建监听套节字,绑定到本地地址,开始监听SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN si;si.sin_family = AF_INET;si.sin_port = ::ntohs(nPort);si.sin_addr.S_un.S_addr = INADDR_ANY;::bind(sListen, (sockaddr*)&si, sizeof(si));::listen(sListen, 5);// 循环处理到来的连接while(TRUE){// 等待接受未决的连接请求SOCKADDR_IN saRemote;int nRemoteLen = sizeof(saRemote);SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);// 接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象PPER_HANDLE_DATA pPerHandle =(PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));pPerHandle->s = sNew;memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);pPerHandle->nOperationType = OP_READ;::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (ULONG_PTR)pPerHandle, 0);// 投递一个接收请求OVERLAPPED *pol = (OVERLAPPED *)::GlobalAlloc(GPTR, sizeof(OVERLAPPED));WSABUF buf;buf.buf = pPerHandle->buf;buf.len = BUFFER_SIZE;DWORD dwRecv;DWORD dwFlags = 0;::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, pol, NULL);}}
归根到底概括完成端口模型一句话:
我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了,那么就在完成端口那里排成一个队列)。我们在另外一个线程里从完成端口不断地取出IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。
- 完成端口之个人理解
- 完成端口之个人理解
- 完成端口深入理解
- socket 五种模型理解之三---------完成端口A
- 我理解的完成端口
- 完成端口的简单理解
- 完成端口的简单理解
- win32线程之完成端口
- socket通讯之完成端口
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- 理解完成端口(IO completion port)
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- 理解I/O Completion Port(完成端口)
- COM和ATL学习方法
- Sql Server 事务隔离级别的查看及更改
- gnuplot(5):简单数据绘图
- C/C++读取标准输入行
- 黄金价格受哪些因素影响?
- 完成端口之个人理解
- 如今还有人在憎恨比尔盖茨吗?
- 设置Sql Server禁止执行时间长的Sql语句执行
- 整理出来的一个windows关机、锁定、重启、注销 API调用 .
- 如何选择有实力的国际现货黄金(伦敦金)交易平台?
- 实战培训初级班第三次课——我的课前答案
- 急!急!急!烦请Java大神帮助小弟解除动态代理的某些问题!
- Oracle中NULL的定义及其相关处理办法
- 工具链准备好,调试jlink