SOCKET编程登峰造极之完成端口

上传人:飞*** 文档编号:53091177 上传时间:2018-08-28 格式:PDF 页数:13 大小:14.56KB
返回 下载 相关 举报
SOCKET编程登峰造极之完成端口_第1页
第1页 / 共13页
SOCKET编程登峰造极之完成端口_第2页
第2页 / 共13页
SOCKET编程登峰造极之完成端口_第3页
第3页 / 共13页
SOCKET编程登峰造极之完成端口_第4页
第4页 / 共13页
SOCKET编程登峰造极之完成端口_第5页
第5页 / 共13页
点击查看更多>>
资源描述

《SOCKET编程登峰造极之完成端口》由会员分享,可在线阅读,更多相关《SOCKET编程登峰造极之完成端口(13页珍藏版)》请在金锄头文库上搜索。

1、SOCKET 编程登峰造极之完成端口(上) 一、什么是完成端口?完成端口 -是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0 情况下,当然重叠I/O 不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0 等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU 的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。二、完成端口的内部机制1)创建完成端口完成端口是一个内核对象,使用时他总是要和至少一个有效的设备句柄进行关联,完成端口是一个复杂的内核对象,创建它的函数是:显示代码打印1 HANDLE CreateIoComple

2、tionPort( 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(INV ALID_HANDLE_VALUE

3、,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 函数也可以一次性的既

4、创建完成端口对象,又关联到一个有效的设备句柄b) CompletionKey是一个可以自己定义的参数,我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用,最好要保证结构里面的内存不是分配在栈上,除非你有十分的把握内存会保留到你要使用的那一刻。c)NumberOfConcurrentThreads 通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0,这样系统会根据CPU 的个数来自动确定。创建和关联的动作完成后,系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口,就会有多个对应的设备列表。如果设备句柄被关闭,则表中自动删除该

5、纪录。2)完成端口线程的工作原理完成端口可以帮助我们管理线程池,但是线程池中的线程需要我们使用_beginthreadex 来创建,凭什么通知完成端口管理我们的新线程呢?答案在函数GetQueuedCompletionStatus 。该函数原型:显示代码打印1 BOOL GetQueuedCompletionStatus( 2 IN HANDLE CompletionPort, 3 OUT LPDWORD lpNumberOfBytesTransferred, 4 OUT PULONG_PTR lpCompletionKey, 5 OUT LPOVERLAPPED *lpOverlapped,

6、 6 IN DWORD dwMilliseconds 7 ); 这个函数试图从指定的完成端口的I/0 完成队列中抽取纪录。只有当重叠I/O 动作完成的时候,完成队列中才有纪录。凡是调用这个函数的线程将被放入到完成端口的等待线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。完成端口的I/0完成队列中存放了当重叠I/0完成的结果- 一条纪录,该纪录拥有四个字段,前三项就对应GetQueuedCompletionStatus函 数 的2、 3、 4 参 数 , 最 后 一 个 字 段 是 错 误 信 息dwError 。 我 们 也 可 以 通 过 调 用PostQueudComp

7、letionStatus 模拟完成了一个重叠I/0 操作。当 I/0 完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单,只是保存了这些线程的ID 。完成端口会按照后进先出的原则将一个线程队列的 ID 放入到释放线程列表中,同时该线程将从等待GetQueuedCompletionStatus 函数返回的睡眠状态中变为可调度状态等待 CPU 的调度。基本上情况就是如此,所以我们的线程要想成为完成端口管理的线程,就必须要调用GetQueuedCompletionStatus 函数。

8、出于性能的优化,实际上完成端口还维护了一个暂停线程列表,具体细节可以参考 Windows 高级编程指南 ,我们现在知道的知识,已经足够了。- - 3)线程间数据传递线程间传递数据最常用的办法是在_beginthreadex 函数中将参数传递给线程函数,或者使用全局变量。但是完成端口还有自己的传递数据的方法,答案就在于CompletionKey 和 OVERLAPPED 参数。CompletionKey被保存在完成端口的设备表中,是和设备句柄一一对应的,我们可以将与设备句柄相关的数据保存到CompletionKey 中,或者将CompletionKey 表示为结构指针,这样就可以传递更加丰富的内

9、容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此不能在以后动态改变。OVERLAPPED参数是在每次调用ReadFile 这样的支持重叠I/0 的函数时传递给完成端口的。我们可以看到,如果我们不是对文件设备做操作,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可以创建自己的结构,然后将OVERLAPPED结构变量作为我们结构变量的第一个成员,然后传递第一个成员变量的地址给ReadFile 函数。因为类型匹配,当然可以通过编译。当GetQueuedCompletionStatus 函数返回时,我们可以获取到第一个成员变量的地址,然后一个简单的强制转换,我们就可以把它当作完整

10、的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好了!只有一点要注意, 如果跨线程传递, 请注意将数据分配到堆上,并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中,这样当GetQueuedCompletionStatus 被返回时,我们的自定义结构的缓冲区变量中就存放了I/0 操作的数据。CompletionKey 和 OVERLAPPED参数,都可以通过GetQueuedCompletionStatus 函数获得。4)线程的安全退出很多线程为了不止一次的执行异步数据处理,需要使用如下语句显示代码打印1 while (

11、true) 2 3 .。 。 。 。 。 。4 GetQueuedCompletionStatus(.); 5 。 。 。 。 。 。6 那么如何退出呢,答案就在于上面曾提到的PostQueudCompletionStatus 函数,我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址,里面包含一个状态变量,当状态变量为退出标志时,线程就执行清除动作然后退出。SOCKET 编程登峰造极之完成端口(下) 写了一下午,终于写完了这个“完成端口”。到今天为止,写完了Overlapped IO Event 、Overlapped IO completion Routine和 com

12、pletion Port 。一路写过来的确学到了不少东西,也清楚地看到到微软在遇到问题并解决问题的方法;不得不承认,微软还是很强的。呵呵这也让我明白一件事:遇到困难,不要望而却步;只要你勇于探索,一切都将是那么简单。(听起来有点自恋的感觉_)“完成端口” 模型是迄今为止最为复杂的一种I/O 模型。然而, 假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT 和 Windows 2000 操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增

13、多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT 或 Windows 2000 开发高性能的服务器应用,同时希望为大量套接字I/O 请求提供服务(Web 服务器便是这方面的典型例子) ,那么 I/O 完成端口模型便是最佳选择!我们基本上按下述步骤行事:1) 创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程。2) 判断系统内到底安装了多少个处理器。3) 创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O 请求提供服务。在这个简单的例子中,我们为每个处理器都只

14、创建一个工作者线程。这是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用 CreateThread 函数时,必须同时提供一个工作者例程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。4) 准备好一个监听套接字,在端口1234 上监听进入的连接请求。5) 使用 accept函数,接受进入的连接请求。6) 创建一个数据结构,同时在结构中存入接受的套接字句柄。7) 调用 CreateIoCompletionPort ,将自 accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey

15、) 参数,将单句柄数据结构传递给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 bufferDA TA_BUFSIZE; 015 WSABUF DataBuf; 016 DWORD BytesS

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号