完成端口之个人理解
来源:互联网 发布:ubuntu 设置ntp客户端 编辑:程序博客网 时间:2024/04/30 13:22
完成端口(简称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 while
- return 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(完成端口)
- Redis-在.NET中的使用
- MySQL查询的执行过程
- C++四种强制转换
- BlocklyGame分析 一
- 如何设置Ubuntu 自动待机时间
- 完成端口之个人理解
- 冒泡排序
- Nginx与tomcat组合的简单使用
- 网易新闻排行榜爬虫
- Android wifi模块
- C++设计模式二--OberverPattern(观察者模式)
- 向量的表示、投影、变换、协方差矩阵及PCA
- (二)hadoop配置虚拟机环境
- AndroidM及以上,接听电话之后5S 灭屏