IOCP问题总结(recv阻塞/死锁/线程堆栈溢出)

来源:互联网 发布:五子棋c语言源代码 编辑:程序博客网 时间:2024/04/29 14:40
//群发,异常时不关闭socket,(关闭会出现异常(死锁?!),而不关闭最多客户端少收一些数据而已),再者,这里在关闭,而SocketArray_->GetActiveItem(i)又在获取。。。
//会发生类似这样的死锁:AcceptThread的RecvData要进入锁,但OnRecvComplete却阻塞在recv处,为什么会阻塞在这里?可能是CloseSocket是,破坏了某些数据,如重叠
//结构等socket需要的结构数据。后来发现是因为SocketArray_中的数据引起?!明天再看看。为了方便定位问题,先将RSThread改成单线程,经过一系列VS/windbg调试总结
//如下:
//1.****** 单线程,群发除自己以外的其它客户端,而后立刻关闭被发消息的客户端,发现阻塞在recv处,究其原因:关闭其socket之前一瞬间,此客户端发送一条消息,
//******** 此消息放入了服务器队IO列中,随后此客户端其重叠资源和ActiveItem也释放,若此时正好有一客户端连接进来,被分配和刚才被释放socket同样的句柄,并且
//******** ActiveItem资源也和刚才被释放的一样,此时GetQueuedCompletionStatus响应,按照一般流程到recv时,recv会复制出缓冲区中的数据然后往下执行,但是
//******** 在这里,当关闭socket之时,此socket对应缓冲区的数据就被清了,这时recv函数将阻塞(原本此处的recv是非阻塞的!!后来发现这句话是错误的,使用socket或
//******** WSAsocket创建的socket默认是阻塞的,只是GetQueuedCompletionStatus每次都有数据,所以不会出错而已,可以使用ioctlsocket使之非阻塞.)!解决的办法是:暂时不释放ActiveItem资源,当bRecving_和bSending_
//******** 同时false即发送接收都完成时再释放,即可避免此种情况。当然为了解决这种情况,当时在程序中还引入了dwRoundIndex_变量,但却不能完全解决此问题,而只能
//******** 降低此种情况出现的概率而已


//2.****** 多线程,解决1的问题后,将RSThread改为多线程,其它条件跟1一样时,发现程序基本卡死,只能接收客户端的连接,而不能收发消息,windbg调试发现四个RSThread
//******** 线程卡在CloseSocket函数的CLockMgr RLockMgr(&RecvLock_);即形成死锁,调试分析了很久,发现原因:A进入OnRecvComplete后立刻进入A的RecvLock_锁,运行到
//******** OnNetMessage的CloseSocket(对除自己以外的客户端执行关闭,假如是B)时,即将进入B的RecvLock_,而之前的一瞬间,另一个线程相应了B的GetQueuedCompletionStatus
//******** B进入OnRecvComplete拥有B的RecvLock_,A一直等待B释放此锁,但是B运行到跟A同样的地方时,又有另外一个线程响应了C的GetQueuedCompletionStatus...
//******** 就这样,四个RSThread都卡在了同一个地方,导致死锁的发生!解决问题的办法是:不要在拥有锁同时直接或间接的递归调用本函数,具体点,在这里的处理办法是
//******** 在A进入自己的某个锁后不能要求其进入B的某个锁,即由B在自己的类中(无A锁)调用CloseSocket(仅有B锁)进行关闭,而不能再A的类中(有A锁),调用B的CloseSocket(有B锁),当然全局只使用一把锁也不会出现以上问题


//3.****** 找到上述问题后将"pTCPSocketTmp->GetIndex()==dwIndex"改成"pTCPSocketTmp->GetIndex()!=dwIndex"运行,出现新的问题,众多客户端连接进来一会儿后,服务端崩溃
//******** 使用windbg查看堆栈信息发现线程A在Attach函数的RecvLock_时崩溃,而其余线程中有个线程B的堆栈信息居然显示不出来,出现
//******** "WARNING: Stack unwind information not available. Following frames may be wrong."类似这样的错误,B线程堆栈溢出?进而导致A在那里崩溃?用vs调试或者直接打开
//******** 应用程序进行通信,都会崩溃,而用windbg直接调试(不是附加方式)时服务端却未崩溃!!经过苦思冥想推测原因,然后通过调试验证推测,最后终于找到了问题!!!
//******** 原因:处理第一个包CloseSocket时正好bSending_和bRecving_都为false,所以释放了所有数据,而也正好(就是这么巧!)这次recv的总数据包含两个以上包,在第一个包处理完后
//******** 数据已经全部释放,因此bRecvLen_被置为0,因此memmove(szRecvBuf_,szRecvBuf_+dwSizeTmp,dwRecvLen_-dwSizeTmp);由于dwRecvLen_<dwSizeTmp,此两数相减得到一个很大
//******** 的正数,导致操作的数据超过szRecvBuf_的数据范围--溢出!进而导致线程堆栈溢出!且可能刚好破坏了另一个线程中的数据(包含锁的数据),最终程序崩溃在上述提到的锁的地方。
//******** 解决办法是:(1)、不要在OnRecvComplete接收函数中发送数据失败时立刻关闭/释放自身资源(socket/重叠结构等),可以待OnRecvComplete函数运行完成后再关闭,列入将关闭

//******** 消息放入队列中等方法。(2)、在memmove处进行条件判断,确保dwRecvLen_>=dwSizeTmp再往下执行(3)、在CloseSocket时return false,OnRecvComplete会捕获异常!!


最近想使用环形缓冲,于是在接收数据的时候使用了两次recv操作,而第二次可能没数据。以前的代码recv默认是阻塞的,但每次来必定有数据,因此线程不会卡死在recv出,而现在只好使用非阻塞recv,在使用ioctlsocket使socket为非阻塞后,却发现CPU瞬间上升到50%左右,并且没任何客户端连接,而以前CPU几乎为0,看来非阻塞socket时,系统对io的轮询多么频繁!!

0 0
原创粉丝点击