IOCP中的socket错误和资源释放处理方法


本文作者:sodme, SeaWave
本文出处:http://blog.csdn.net/sodme

                        http://blog.csdn.net/SeaWave/article/details/747863


声明:本文可以不经作者同意任意转载、复制、传播,但任何对本文的引用均须保留本文的作者、出处及本行声明信息!谢谢!

前言:  
错误处理和socket释放, 是IOCP编程中的一大难点. 本文试图就IOCP设计中经常遇到的这个难题展开论述并寻找其解决方案, 事实上, 文中所述的解决方式不仅仅适用于IOCP, 它同样适用于EPOLL等多种服务器编程的网络模型中, 前提是: 领会这种处理方式的实质.

正文:
在使用IOCP开发时, 大家经常遇到的一个难题是与socket相关的缓冲区释放不当带来的错误, 这种错误通常是由于多次对同一个指针执行了delete操作引起的. 比如, 当在执行wsasend或wsarecv返回了非pending的错误信息时, 我们就要对此错误进行处理, 通常情况下, 我们会想到执行这两步操作: 
a. 释放此次操作使用的缓冲区数据(如果不释放可能造成内存泄漏); 
b. 关闭当前操作所使用的socket. 
而另一方面, 我们可能也会在get函数(GetQueuedCompletionStatus)的处理中, 当get函数返回值为FALSE时也作这两步相同的操作.  此时, 就会造成对同一缓冲区的重复释放, 问题由此产生.

解决的方法, 可以有这几种: 
1. 对数据缓冲区使用引用计数机制; 
2. 在clientsock的对象设计机制上使释放操作线性化. 
关于这两种方法, 任何一种如果要详细说清, 可能篇幅都会比较长, 笔者并无耐心和精力将每一个细节都一一道来, 在此仅选第2种方案的关键步骤和核心思想来与大家分享.

由前面对问题的描述可以看出, 造成多次释放的原因可能是在执行收发操作和GET函数返回值为FALSE时, 我们重复执行了释放操作. 很自然地, 我们会想到,  能不能把这两次释放合并成一次释放,  这样不就没问题了吗?  yes,  这个思路是没问题的.  但要想让这个思路能变成现实,  需要在设计机制上对这个思路进行一定的支持.

首先,  我们假设, 是在get函数返回时统一进行相应的释放和关闭操作.

如果在执行wsasend操作时, 发生了非pending错误(io操作正在进行中), 而此时我们如果不释放资源, 那至少得让IOCP在GET返回时得知这个错误和发生错误时的缓冲区指针. 通知IOCP的方式, 是使用post函数(PostQueuedCompletionStatus)向IOCP抛一个特殊标志的消息, 这个特殊标志可以通过get函数的第二个参数, 即: 传送字节数来表示, 可以选择任何一个不可能出现的值, 比如任何一个跟它的初始值不相等的负数.  当然, 如果你通过单句柄数据或单IO数据来传递也是可以的. 而发生错误的这个缓冲区指针, 我们是必须要通过单句柄数据或单IO数据来传递的. 但是, 从整个缓冲区的管理机制上来说, 我不推荐这样的离散缓冲区机制, 我的建议是: 把收发缓冲区或数据队列与相应的clientsocket对象相绑定, 释放操作写在该对象的析构函数里, 这样当释放clientsocket对象时就释放了这些缓冲区.

ok, 这样一来, 在get函数里, 有三种情况需要执行释放逻辑:
1. get的返回值为FALSE;
2. 传送字节数为0;
3. 接收到刚才我们post的那个错误类型消息.

把释放操作全放在get函数里以后, 对释放操作的处理, 就比较统一了. 当然, 为了实现真正的线性化和元子化, 在释放操作的最终执行逻辑上, 还需要对释放代码加锁以实现线程互斥(当然, 这是在你开了多个工作者线程的情况下).


-------------------------------------------承上启下----------------------------------------------------

还涉及到其他业务逻辑线程的问题。
比方说,我们通常(就象文中也提到的)会将ClientSocket与接收缓冲区绑定到一个会话对象中(为简化起见,发送数据往往没有用overlapped机制,而是直接用一个异步send,也就不需要发送缓冲区),而这个对象可能被除IOCP工作线程以外的其他线程也用到,比方说一个事件队列的处理线程(我们会在收到数据的时候生成事件对象置入队例中,以类似于Command模式的方法来处理,而这些事件对象会引用到会话对象),或者,也许有某个容器会存放这些会话对象的一个引用,用于定时发送心跳包、会话计数、检索等等,这个时候,会话对象的销毁就不是那么简单的了,换句话说,仅靠“将销毁工作统一到执行GetQueuedCompletionStatus的函数里“是不够的。
在这种情况下,文中提到的第1种“采用引用计数”的方法就比较优雅了,在我的很多实际应用中,都是将会话对象设计为“可引用计数”的,不暴露它的析构函数,而是当引用计数减到0的时候,自动销毁,这样就保证“仅当没有任何人使用它的时候才会释放它”。
利用C++的模板,可以十分方便地模拟出自动引用计数的安全指针:

001: /************************************************************************
 002:     引用计数基类、及引用计数指针模板类
 003:         ----NoSound QQ2591570 可随意复制、改动、使用、拍砖,概不追究!
 004: ************************************************************************/
 005: #ifndef _REFCOUNTED_INCLUDED_
 006: #define _REFCOUNTED_INCLUDED_
 007: 
 008: #include <cassert>
 009: #ifdef _MT
 010: #include <Windows.h>
 011: #endif
 012: 
 013: class RefCountable {
 014: public:
 015:     int addRef(void) {
 016:         #ifdef _MT
 017:         return ::InterlockedIncrement(&refCount_);
 018:         #else
 019:         return ++refCount_;
 020:         #endif
 021:     }
 022: 
 023:     int decRef(void) {
 024:         int r =
 025:             #ifdef _MT
 026:             ::InterlockedDecrement(&refCount_);
 027:             #else
 028:             --refCount_;
 029:             #endif
 030:         assert(r>=0);
 031:         if (0==r)
 032:             delete this;
 033:         return r;
 034:     }
 035: 
 036:     int getRefCount(void) const { return refCount_; }
 037: 
 038: protected:
 039:     RefCountable(void) : refCount_(0) {}
 040:     virtual ~RefCountable(void) { assert(0==refCount_); }
 041: 
 042: private:
 043:     #ifdef _MT
 044:     long
 045:     #else
 046:     int
 047:     #endif
 048:     refCount_;
 049:     RefCountable(const RefCountable &);
 050:     RefCountable & operator = (const RefCountable &);
 051: };
 052: 
 053: template<class T>
 054: class RefCountedPtr {
 055: public:
 056:     RefCountedPtr(void) : ptr_(0) {}
 057:     RefCountedPtr(T *ptr) : ptr_(ptr) {
 058:         if (ptr_)
 059:             ptr_->addRef();
 060:     }
 061:     RefCountedPtr(const RefCountedPtr<T> &sour) : ptr_(sour.ptr_) {
 062:         if (ptr_)
 063:             ptr_->addRef();
 064:     }
 065:     RefCountedPtr & operator = (const RefCountedPtr<T> &right) {
 066:         if (this!=&right) {
 067:             if (0!=ptr_)
 068:                 ptr_->decRef();
 069:             ptr_ = right.ptr_;
 070:             if (ptr_)
 071:                 ptr_->addRef();
 072:         }
 073:         return *this;
 074:     }
 075:     ~RefCountedPtr(void) {
 076:         if (0!=ptr_)
 077:             ptr_->decRef();
 078:     }
 079: 
 080:     T & operator*() const { return *ptr_; }
 081:     T * operator->() const { return (&**this); }
 082:     
 083:     friend bool operator == (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 084:         return (left.ptr_ == right.ptr_);
 085:     }
 086:     friend bool operator != (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 087:         return (left.ptr_ != right.ptr_);
 088:     }
 089:     friend bool operator < (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 090:         return (left.ptr_ < right.ptr_);
 091:     }
 092:     friend bool operator > (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 093:         return (left.ptr_ > right.ptr_);
 094:     }
 095:     friend bool operator <= (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 096:         return (left.ptr_ <= right.ptr_);
 097:     }
 098:     friend bool operator >= (const RefCountedPtr<T> &left, const RefCountedPtr<T> &right) {
 099:         return (left.ptr_ >= right.ptr_);
 100:     }
 101: 
 102:     bool isNull() const { return 0==ptr_; }
 103:     bool isValid() const { return 0!=ptr_; }
 104: 
 105:     // 返回所控制的对象指针
 106:     T * get(void) const { return ptr_; }
 107: 
 108:     //取得对另一指针的控制权
 109:     void reset(T * ptr=0) {
 110:         if (0!=ptr)
 111:             ptr->addRef();
 112:         if (0!=ptr_)
 113:             ptr_->decRef();
 114:         ptr_ = ptr;
 115:     }
 116: 
 117: private:
 118:     T    *ptr_;
 119: };
 120: 
 121: #endif // ifndef _REFCOUNTED_INCLUDED




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值