Posted on 2009-01-31 06:53
S.l.e!ep.¢% 阅读(633)
评论(0) 编辑 收藏 引用 所属分类:
VC
编写大容量和健壮的服务器系列—处理IOCP资源释放
2007-08-21 15:24
邓立波 深圳,2007-8
作者联系方式
:
email: libodeng@gmail.com
msn: libodeng@gmail.com
tel: 13510275799
版权/著作权所有 (C) 2007 邓立波 保留所有权利
警告:未经作者许可,任何人或组织不得转载,公开发布,拷贝,传播本文献的全部或部分
1 问题定义
一般的,我们需要在连接关闭(包括主动或被动关闭)时释放以下资源:
(1) 一个或多个接收/发送缓存,一个信令缓存(用作拼包,可能一次WSARecv仅收
到半个信令,这时需要一个缓存先保存这半个信令,继续发出WSARecv 调用,直
到接收到一个完整信令)。
(2) 调用closesocket释放socket资源。
主要的问题是资源何时释放,以及该在哪儿释放。
2 缓存如何同socket绑定
方式一
发送缓存绑定到发送操作的OVERLAPPED中(一般通过扩展OVERLAPPED结构添加一个缓存指针变量),接受缓存和信令缓存绑定到接受操作的OVERLAPPED中,如果还有一些资源需要
在IOCP处理发送/接收之外的地方访问
,你可以再动态分配一个的结构体,将其指针同CompletionKey邦定
方式二
使用一个Hash表,对每个连接定义一个连接上下文,按socket值索引,把一些资源,例如信令缓存指针置于hash中的上下文。
方式一优点是速度快(其实也快不了多少),但却存在资源在哪儿释放,何时释放的问题。
对此,网络上有两篇文章讨论过:
很惭愧,我无法理解前一篇文章中作者所假设的应用环境,因此我无法判定是否这样真能解决问题,但至少我不放心真的这样做,即使作者是对的,这种“独特”的机制也会让我对服务器的健壮性感到不安。后一文章使用引用计数的方式理论上是可行的,这个引用计数用于跟踪你所分配的结构体的动态释放,因此在所有引用到这个结构体的地方都得做引用计数处理(increment reference或者decrement reference),更要命的是上层应用也可能需要对这个结构体保存一个引用(一般是保存结构体的一个指针,即CompletionKey),因为服务器有时需要主动发送数据到client端。这显然比较麻烦,原本模块内部的复杂性被扩散到上层代码。一点疏急就可能就导致资源泄漏或重复释放,或者访问已经释放的资源而发生内存访问异常,一旦这种bug出现将很难跟踪调试。作为一个基础模块的编写者,
设计时就要考虑这一点,无论谁写的代码,好的结构都可以起到强有力的约束作用,如果不是必要,我建议你不要用这种方式
。最后有一点需要注意的是,如果你没有资源需要在IOCP处理发送/接收之外的地方访问,你就不要去分配一个结构体,以免增加额外的麻烦。
第二种方式自然更不会遇到重复资源释放的问题(你可以在任何地方关闭连接,并释放相关资源),因为你释放资源时,同时删除hash表中的连接上下文,所以在释放之前应该检查连接上下文是否存在,如果不存在,则表示资源已被释放。需要注意的是对本次操作的发送/接收缓存应该立刻释放,同时这些缓存指针不必保存到上下文,而是绑定到相应的OVERLAPPED。这种方式还有一个优点,可以跟踪所有连接(例如逐一扫描所有连接,处理一些如心跳包之类的事情)。当然你可能担心hash表毕竟对速度有影响,我们举个例子,你的IOCP一秒种需要处理10万个IO,则需要10万次hash操作,我想说的是 10万次/一秒的hash其实对现在的CPU是非常轻松的,不用担心它成为瓶颈。这种方式相对实现非常简单,出现bug也相对易于跟踪,对于服务器的设计而言,我一直固执的坚持,稳定性始终是第一的。