




已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
SOCKET编程登峰造极之完成端口(上)一、什么是完成端口?完成端口-是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。 二、完成端口的内部机制1)创建完成端口完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:显示代码打印1 HANDLE CreateIoCompletionPort( 2 IN HANDLE FileHandle, 3 IN HANDLE ExistingCompletionPort, 4 IN ULONG_PTR CompletionKey, 5 IN DWORD NumberOfConcurrentThreads 6 ); 通常创建工作分两步: 第一步,创建一个新的完成端口内核对象,可以使用下面的函数:显示代码打印1 HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads) 2 3 return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads); 4 ; 第二步,将刚创建的完成端口和一个有效的设备句柄关联起来,可以使用下面的函数:显示代码打印1 bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey) 2 3 HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0); 4 return h=hCompPort; 5 ; 说明a)CreateIoCompletionPort函数也可以一次性的既创建完成端口对象,又关联到一个有效的设备句柄b)CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。c)NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU的个数来自动确定。创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该纪录。 2)完成端口线程的工作原理完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus。该函数原型:显示代码打印1 BOOL GetQueuedCompletionStatus( 2 IN HANDLE CompletionPort, 3 OUT LPDWORD lpNumberOfBytesTransferred, 4 OUT PULONG_PTR lpCompletionKey, 5 OUT LPOVERLAPPED *lpOverlapped, 6 IN DWORD dwMilliseconds 7 ); 这个函数试图从指定的完成端口的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)线程间数据传递线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。CompletionKey和OVERLAPPED参数,都可以通过GetQueuedCompletionStatus函数获得。 4)线程的安全退出很多线程为了不止一次的执行异步数据处理,需要使用如下语句显示代码打印1 while (true) 2 3 .。 4 GetQueuedCompletionStatus(.); 5 。 6 那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。SOCKET编程登峰造极之完成端口(下)写了一下午,终于写完了这个“完成端口”。到今天为止,写完了Overlapped IO Event、Overlapped IO completion Routine和completion Port。一路写过来的确学到了不少东西,也清楚地看到到微软在遇到问题并解决问题的方法;不得不承认,微软还是很强的。呵呵这也让我明白一件事:遇到困难,不要望而却步;只要你勇于探索,一切都将是那么简单。(听起来有点自恋的感觉_)“完成端口”模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!我们基本上按下述步骤行事:1) 创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。2) 判断系统内到底安装了多少个处理器。3) 创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务。在这个简单的例子中,我们为每个处理器都只创建一个工作者线程。这是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用CreateThread函数时,必须同时提供一个工作者例程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。4) 准备好一个监听套接字,在端口1234上监听进入的连接请求。5) 使用accept函数,接受进入的连接请求。6) 创建一个数据结构,同时在结构中存入接受的套接字句柄。7) 调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort。8) 开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3)指定的工作者例程中,体验到这一点。9) 重复步骤5) 8),直至服务器中止。源码-显示代码打印001 #pragma comment(lib,ws2_32.lib) 002 #include 003 #include 004 / 005 /仅供测试软件用 006 #include Protocol.h 007 008 #define DATA_BUFSIZE 1024 / 接收缓冲区大小 009 typedef enum IOSEND,IORECV,IOQUIT IO_TYPE; 010 typedef struct _SOCKET_INFORMATION 011 OVERLAPPED Overlapped; 012 SOCKET Socket; 013 IO_TYPE IoType; 014 char bufferDATA_BUFSIZE; 015 WSABUF DataBuf; 016 DWORD BytesSEND; 017 DWORD BytesRECV; 018 SOCKET_INFORMATION, * LPSOCKET_INFORMATION; 019 DWORD Flags = 0, 020 Bytes = 0; 021 DWORD WINAPI WorkThread(LPVOID CompletionPortID); 022 DWORD WINAPI AcceptThread(LPVOID lpParameter) 023 024 WSADATA wsaData; 025 HANDLE hCompPort; 026 DWORD ThreadID; 027 DWORD Ret; 028 if (Ret = WSAStartup(0x0202, &wsaData) != 0) 029 030 printf(WSAStartup failed with error %dn, Ret); 031 return FALSE; 032 033 if (hCompPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0) = NULL) 034 035 printf( CreateIoCompletionPort failed with error: %dn, GetLastError(); 036 return FALSE; 037 038 /根据CPU个数来创建线程,以达到最佳性能 039 SYSTEM_INFO SystemInfo; 040 GetSystemInfo(&SystemInfo); 041 for(unsigned int i=0; iSocket = accept(ListenSocket,(SOCKADDR*)&ClientAddr, &addr_length) != INVALID_SOCKET) 065 066 printf(accept ip:%s port:%dn,inet_ntoa(ClientAddr.sin_addr),ClientAddr.sin_port); 067 /相关参数初始化 068 memset(&SI-Overlapped,0,sizeof(WSAOVERLAPPED); 069 memset(SI-buffer, 0, DATA_BUFSIZE); 070 SI-DataBuf.buf = SI-buffer; 071 SI-DataBuf.len = DATA_BUFSIZE; 072 SI-BytesRECV = 0; 073 SI-BytesSEND = 0; 074 SI-IoType = IORECV; 075 / 076 /仅供测试软件用 077 HeaderMessage recvMsg; 078 if (recv(SI-Socket, (char*)&recvMsg, sizeof(recvMsg), 0) Socket, hCompPort, (DWORD)SI, 0) = NULL) 083 084 printf(CreateIoCompletionPort failed with error %dn, GetLastError(); 085 return FALSE; 086 087 /发出一个重叠IO请求 088 if(WSARecv(SI-Socket, &SI-DataBuf, 1, &Bytes, &Flags, &SI-Overlapped, NULL) = SOCKET_ERROR) 089 090 if(WSAGetLastError() != WSA_IO_PENDING) 091 092 printf(disconnectn); 093 closesocket(SI-Socket); 094 delete SI; 095 continue; 096 097 098 099 100 101 return FALSE; 102 103 DWORD WINAPI WorkThread(LPVOID CompletionPortID) 104 105 HANDLE hCompPort = (HANDLE)CompletionPortID; 106 while (TRUE) 107 108 DWORD BytesTransferred = 0; 109 LPSOCKET_INFORMATION SI = NULL; 110 LPWSAOVERLAPPED Overlapped = NULL; 111 /线程进入线程池,等待被唤醒 112 if (GetQueuedCompletionStatus(hCompPort, &BytesTransferred, (LPDWORD)&SI, &Overlapped, INFINITE) 113 114 if (0 = BytesTransferred & IOQUIT != SI-IoType) 115 116 printf(disconnectn); 117 closesocket(SI-Socket); 118 delete SI; 119 continue; 120 121 switch(SI-IoType) 122 123 case IORECV: 124 125 /目前的功能是将接收到的数据原封不动的返回 126 SI-DataBuf.len = BytesTransferred; 127 SI-BytesRECV = BytesTransferred; 128 SI-IoType = IOSEND; 129 if (WSASend(SI-Socket, &SI-DataBuf, 1, &Bytes, Flags, &SI-Overlapped, NULL) = SOCKET_ERROR) 130 131 if(WSAGetLastError() != WSA_IO_PENDING) 132 133 printf(disconnectn); 134 closesocket(SI-Socket); 135 delete SI; 136 continue; 137 138 139 break; 140 141 case IOSEND: 142 143 SI-BytesSEND += BytesTransferred; 144 /返回是否彻底,若未发完,接着发 145 if (SI-BytesSEND BytesRECV) 146 147 SI-DataBuf.buf += BytesTransferred; 148 SI-DataBuf.len -= BytesTransferred; 149 SI-IoType = IOSEND; 150 if (WSASend(SI-Socket, &SI-DataBuf, 1, &Bytes, Flags, &SI-Overlapped, NULL) = SOCKET_ERROR) 151 152 if(WSAGetLastError() != WSA_IO_PENDING) 153 154 printf(disconnectn); 155 closesocket(SI-Socket); 156 delete SI; 157 continue; 158 159 160 161 else if (SI-BytesSEND SI-BytesRECV) 162 163 printf(BytesSEND:%d BytesRECV:%dn,SI-BytesSEND,SI-BytesRECV); 164 memset(SI-buffer, 0, DATA_BUFSIZE); 165 SI-BytesRECV = 0; 166 SI-Byt
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026届甘肃省白银市平川区中恒学校化学高三上期末综合测试模拟试题含解析
- 信息技术学业讲解
- 西方医学与精神科护理学
- 小升初古诗词选择题专项练习(含答案)
- 食品超微粉碎技术
- 软件项目验收汇报
- 兽医临床诊断技术
- 小学生关联词讲解
- 2026届四川省绵阳市三台中学化学高一上期末复习检测模拟试题含解析
- 如何制作图文讲解
- 《广联达培训教程》课件
- 减少门诊投诉PDCA课件
- 职业暴露与防护41p
- 医疗废物处理登记表
- 二手房屋买卖物品交接清单
- 左手流程-右手人才-章义伍
- 桥梁安全事故案例警示
- 智慧树创意学经济答案-2018创意学经济期末答案
- YY 0054-2023血液透析设备
- 黄冈市临床重点专科申报-模板-副本
- SB/T 10460-2008商用电开水器
评论
0/150
提交评论