/* 管理活动句柄 主要为了:避免原来的通信队列方法在消息中传递句柄的缺陷(1、句柄可能失效的处理,2、根据句柄号查找句柄的低效(包括同一活动句柄上反复到来消息导致的重复工作) ); 基本思路:1、使用bit置位表示句柄活动,借用ace中的n&~(n-1)方法快速查找已置位 2、使用多层置位进一步加快扫描活动句柄的速度 3、句柄与使用的句柄的对象紧密绑定,整个生命周期完全一致 4、在对象内部保存数组位置,frame内使用hash计算内部存放位置,以便能够使用ID号迅速找到句柄; by dzj, 09.05.28 */ #include "manframe.h" #include "../apppoolobjs.h" #include "../pkgproc/pkgbuild.h" unsigned int BrocastMsg::g_broedID = 0;//递增,用于生成各广播包的selfID; unsigned int CManFrame::g_IncID;//这里没有使用interlockedincreament或automic_inc,保险起见,就简单在一个线程里分配这些就可以了,当然,加入ManFrame时,同样的ID值会导致临界区中hash到同一位置,此时也会导致重新申请一个ID号,从而不会发生ID号冲突的现象。 IFrameEle::IFrameEle() { OnFrameEleCreated(); }; IFrameEle::~IFrameEle() { OnFrameEleDestoryed();//删除清理操作; } ///自身被创建时执行的初始化操作,用于池对象的构造; void IFrameEle::OnFrameEleCreated() { m_delIssueTime = GetTickCount(); m_bDbgIsClose = false; m_pManFrame = NULL; InitInvalidSelfID(); } ///自身被销毁时执行的清理操作; void IFrameEle::OnFrameEleDestoryed() { m_bDbgIsClose = true; if ( NULL == m_pManFrame ) { NewLog( LOG_LEV_INFO, "~IFrameEle析构,m_pManFrame空" ); return; } NewLog( LOG_LEV_INFO, "~IFrameEle析构" ); m_pManFrame->DelFromManFrame( this ); m_pManFrame = NULL; } ///3个加入点,1、开始监听前;2、监听到新连接,将新连接加入epoll前;3、发起新连接前; /// 如果加入失败,调用者负责将本元素删去; bool IFrameEle::AddSelfToFrame( CManFrame* pManFrame ) { if ( NULL == pManFrame ) { return false; } m_pManFrame = pManFrame; if ( !m_pManFrame->AddToManFrame( this ) ) { NewLog( LOG_LEV_ERROR, "元素指针加入框架失败,将泄漏" ); return false; } return true; } ///关闭自身; void IFrameEle::SelfClose() { if ( m_bDbgIsClose ) { NewLog( LOG_LEV_ERROR, "SelfClose, explore deleted ele!" ); } if ( m_bIsToDelSelf ) { NewLog( LOG_LEV_DEBUG, "重复调用SelfClose,距离上次SelfClose%d", GetTickCount()-m_delIssueTime ); return;//之前已置删除自身标记; } m_bIsNeedActiveExplore = true;//bug23(参见'todo.doc'):此活动标记可能漏掉(虽然之后的SetActiveFlag会确保被遍历,但此标记可能在InnerClose之前被manloop清掉,导致ActiveExplore本元素时直接返回) unsigned int posInfo = GetPosInfo();//防止接下来InnerClose之后立刻被框架删去从而引发错误读(//bug 22修改,具体见'todo.doc', by dzj, 09.06.12.); CManFrame* pTmpFrame = m_pManFrame;//防止接下来InnerClose之后立刻被框架删去从而引发错误读(//bug 22修改,具体见'todo.doc', by dzj, 09.06.12.); if ( NULL == pTmpFrame ) { NewLog( LOG_LEV_ERROR, "SetNeedActiveExplore, pTmpFrame空" ); return; } NewLog( LOG_LEV_DEBUG, "长时间断连debug,SelfClose,准备关闭位置%d对应元素", posInfo ); ///关闭socket,并重新初始化; InnerClose(); m_delIssueTime = GetTickCount();//删除发起时刻; m_bIsToDelSelf = true;//bug23(参见'todo.doc'):此活动标记可能漏掉(虽然之后的SetActiveFlag会确保被遍历,但此标记可能在InnerClose之前被manloop清掉,导致ActiveExplore本元素时直接返回) //这是删除元素的唯一标记,接着本元素很可能被删除,因此这之后不可再访问自身相关元素; pTmpFrame->SetActiveFlag( posInfo );//这一句可能使得后续占用posInfo位置的某个新元素被扫描,但m_bIsNeedActiveExplore可以防止此情形发生,而同时m_bIsToDelSelf则可以防止删除事件因为m_bIsNeedActiveExplore而被漏掉; return; }; ///将自身标为活动,以备相应线程explore; bool IFrameEle::SetNeedActiveExplore() { if ( m_bDbgIsClose ) { NewLog( LOG_LEV_ERROR, "SetNeedActiveExplore, explore deleted ele!" ); } if ( NULL == m_pManFrame ) { NewLog( LOG_LEV_ERROR, "SetNeedActiveExplore, m_pManFrame空" ); return false; } m_bIsNeedActiveExplore = true;//与SelfClose中的相应代码比较,此处由于本元素肯定有效,因此不会出现bug23,; m_pManFrame->SetActiveFlag( GetPosInfo() );//可能manloop也正在遍历本元素,如果遍历已经在开始做事,则此活动标记会设置并导致下一次遍历,如果遍历还未开始做事,则此活动标记会马上被清掉,本次标记期望的工作会在本次遍历中立刻得到执行; return true; }; ///发起广播消息,该消息广播给框架中除监听端口外的所有元素 void IFrameEle::IssueBrocastMsg( NewMsgToPut* pToSendMsg ) { if ( NULL == m_pManFrame ) { return; } m_pManFrame->AddOneBrocastMsg( pToSendMsg ); return; } ///执行发送框架广播消息; void IFrameEle::ExecSendBroMsg( NewMsgToPut* pToSendMsg ) { if ( NULL == pToSendMsg ) { return; } #ifdef USE_CRYPT CrdInsSendMsg( pToSendMsg );//加密情形下,会在CrdInsSendMsg内部拷贝pToSendMsg,因此这里不需要分配新内存; #else //USE_CRYPT NewMsgToPut* pNewMsg = g_MsgToPutPool->DsRetrieve( MsgToPutNO ); memcpy( pNewMsg, pToSendMsg, sizeof(*pNewMsg) );//复制一份NewMsgToPut作为自身的消息发送副本,原本会被框架删去; CrdInsSendMsg( pNewMsg ); #endif //USE_CRYPT return; }; BrocastMsg::~BrocastMsg() { if ( NULL != pToBro ) { //每个发送者都得到一份该消息的副本,原始消息在这里删去; g_MsgToPutPool->DsRelease( MsgToPutNO, pToBro ); } return; } ///根据ID号找到管理的内部元素指针; IFrameEle* CManFrame::FindFrameEle( unsigned int inFrameID ) { unsigned int hashValue = 0; if ( !InnerNumHash( inFrameID, hashValue ) )//计算hash值(待存放位置) { NewLog( LOG_LEV_ERROR, "FindFrameEle,hash失败,值:%d", inFrameID ); return NULL;//不可能; } if ( hashValue >= m_innerArrSize ) { NewLog( LOG_LEV_ERROR, "FindFrameEle,hashValue(%d) >= m_innerArrSize(%d)", hashValue, m_innerArrSize ); return NULL;//不可能,因为InnerNumHash函数内部使用了m_innerArrSize进行计算; } if ( NULL == m_innerArr[hashValue] ) { //原位置空,可以放置新元素; NewLog( LOG_LEV_INFO, "FindFrameEle,找不到指定的元素%d", inFrameID ); return NULL; } if ( m_innerArr[hashValue]->GetSelfFrameID() != inFrameID ) { //NewLog( LOG_LEV_INFO, "FindFrameEle,指定ID号已被重用,原位置被其它元素占用" ); return NULL; } //原位置非空; return m_innerArr[hashValue]; } ///添加欲管理的元素; bool CManFrame::AddToManFrame( IFrameEle* pToAdd ) { if ( NULL == pToAdd ) { NewLog( LOG_LEV_ERROR, "AddToManFrame,输入指针空" ); return false; } //以下将元素加入管理; DsNewMutex guard(m_AddDelLock); do { unsigned int toAddID = ++g_IncID;//pToAdd->GetSelfID(); unsigned int hashValue = 0; if ( !InnerNumHash( toAddID, hashValue ) )//计算hash值(待存放位置) { NewLog( LOG_LEV_ERROR, "AddToManFrame,hash失败,值:%d", toAddID ); continue;//不可能; } if ( hashValue >= m_innerArrSize ) { NewLog( LOG_LEV_ERROR, "AddToManFrame,hashValue(%d) >= m_innerArrSize(%d)", hashValue, m_innerArrSize ); continue;//不可能,因为InnerNumHash函数内部使用了m_innerArrSize进行计算; } if ( NULL == m_innerArr[hashValue] ) { //原位置空,可以放置新元素; if ( !ClearActiveFlag( hashValue ) )//新加入的元素为非活动; { NewLog( LOG_LEV_ERROR, "AddToManFrame,清元素%d的活动集标志失败!", toAddID ); } ++m_InnerValidEleNum; NewLog( LOG_LEV_DEBUG, "AddToManFrame,元素%d加入管理框架,位置%d,框架元素数%d!", toAddID, hashValue, m_InnerValidEleNum ); pToAdd->SetFrameEleID( toAddID );//本来可以直接使用hashValue加快根据ID号找玩家指针的速度,但这样做的话,可能会碰到ID号相同但实际上玩家不同的情况,给管理带来麻烦。 pToAdd->SetPosInfo( hashValue ); pToAdd->SetBroedID( m_curBroID );//广播包支持,设置已广播号为最新广播号; m_innerArr[hashValue] = pToAdd; SetPosEleValid( hashValue );//记录此位置元素有效; return true; } //原位置非空; if ( pToAdd == m_innerArr[hashValue] ) { NewLog( LOG_LEV_ERROR, "AddToManFrame,重复添加同一元素%d", toAddID ); return true;//仍然返回真; } NewLog( LOG_LEV_INFO, "AddToManFrame,出现了一次hash冲突,输入值:%d,hash值:%d", toAddID, hashValue ); //再次选择一个新的ID号(hash值); } while (true); return false;//hash冲突,此时调用本函数者应为pToAdd重新选择一个ID号; } ///删除欲管理的元素,只能由自身主线程(执行ExploreActiveEles线程)执行,以免与ExploreActiveEles冲突,否则有可能主线程在遍历的时候本函数将元素置空; bool CManFrame::DelFromManFrame( IFrameEle* pToDel ) { if ( NULL == pToDel ) { NewLog( LOG_LEV_ERROR, "DelFromManFrame,输入元素空" ); return false; } unsigned int toDelID = pToDel->GetSelfFrameID(); unsigned int hashValue = pToDel->GetPosInfo(); if ( hashValue >= m_innerArrSize ) { NewLog( LOG_LEV_ERROR, "DelFromManFrame,hashValue(%d) >= m_innerArrSize(%d)", hashValue, m_innerArrSize ); return false;//不可能,因为InnerNumHash函数内部使用了m_innerArrSize进行计算; } if ( true ) //尽量短地使用锁; { //以下将元素从管理中删除; DsNewMutex guard(m_AddDelLock); if ( NULL == m_innerArr[hashValue] ) { NewLog( LOG_LEV_ERROR, "DelFromManFrame,欲删除的元素%d不存在!", toDelID ); return false;//欲删元素不存在 } if ( pToDel != m_innerArr[hashValue] ) { NewLog( LOG_LEV_ERROR, "DelFromManFrame,欲删除的元素%d与删除指针不符!", toDelID ); return false; } if ( !ClearActiveFlag( hashValue ) ) { NewLog( LOG_LEV_ERROR, "DelFromManFrame,清元素%d的活动集标志失败!", toDelID ); } //校验通过,置元素空; --m_InnerValidEleNum; NewLog( LOG_LEV_INFO, "DelFromManFrame,元素%d从管理框架删去,位置%d,框架元素数%d!", toDelID, hashValue, m_InnerValidEleNum ); ClearPosEleValid( hashValue );//清此位置的元素有效标志; m_innerArr[hashValue] = NULL; //相应活动位也应同时置空; } return true; } ///遍历已被置为活动的元素,遍历后将相应元素重置为非活动(整个框架保证:由于线程竞争,有可能已经非活动的又被遍历,但绝不会出现活动的未被遍历) bool CManFrame::ExploreActiveEles() { //注意:有可能当前欲遍历句柄实际已无效,具体参见ClearActiveFlag中的说明; if ( 0 == m_ActbitLev0.GetAtomicVal() ) { //没有一个活动的元素; return false; } I32 actbitLev0 = m_ActbitLev0.GetAtomicVal(); do { //第一层遍历; I32 tmpLev0 = ( actbitLev0 & ~(actbitLev0-1) ); if ( 0 == tmpLev0 ) { break; } I32 pos1 = GetLogValAsm( tmpLev0 ); if ( (pos1<0) || (pos1>=32) ) { break; } //本函数与SetActiveFlag并发执行,尽量防止重复检测已遍历位,绝不遗漏需遍历位 // 第一层将要遍历位清0,由于马上要遍历该位,因此即使在清0过程到来了新的置位也不会遗漏, // 当然这样做有可能导致下次重复检测本次已检测过的置位,但这也是没办法的事,宁可错检,不能漏检,下同; // 另外,本处bit清位也没有使用原子操作,因此有可能出现刚刚置位,但被这里又清位的情况,但由于这里清位后马上会 // 对位对应句柄进行扫描,因此不会丢失置位,虽然同样地会有已置位已被扫描的情形,但没有大影响。 actbitLev0 &= (~(0x00000001 << pos1));//第一层将要遍历位清0; m_ActbitLev0.ClearBit( pos1 ); I32 seqno1 = pos1 * 32 * 32;//在数组中的序号; I32 actbitLev1 = m_ActbitLev1[pos1].GetAtomicVal(); do { //第二层遍历; I32 tmpLev1 = ( actbitLev1 & (~(actbitLev1-1)) ); if ( 0 == tmpLev1 ) { break; } I32 pos2 = GetLogValAsm( tmpLev1 ); if ( (pos2<0) || (pos2>=32) ) { break; } //第二层将要遍历位清0,由于马上要遍历该位,因此即使在清0过程到来了新的置位也不会遗漏, // 当然这样做有可能导致下次重复检测本次已检测过的置位,但这也是没办法的事,宁可错检,不能漏检,下同; // 另外,本处bit清位也没有使用原子操作,因此有可能出现刚刚置位,但被这里又清位的情况,但由于这里清位后马上会 // 对位对应句柄进行扫描,因此不会丢失置位,虽然同样地会有已置位已被扫描的情形,但没有大影响。 actbitLev1 &= (~(0x00000001 << pos2));//第二层已遍历位清0; m_ActbitLev1[pos1].ClearBit( pos2 ); I32 seqno2 = seqno1 + (pos2 * 32); I32 actbitLev2 = m_ActbitLev2[pos1][pos2].GetAtomicVal(); do { //第三层遍历; I32 tmpLev2 = ( actbitLev2 & ~(actbitLev2-1) ); if ( 0 == tmpLev2 ) { break; } I32 pos3 = GetLogValAsm( tmpLev2 ); if ( (pos3<0) || (pos3>=32) ) { break; } //第三层将要遍历位清0,由于马上要遍历该位,因此即使在清0过程到来了新的置位也不会遗漏, // 当然这样做有可能导致下次重复检测本次已检测过的置位,但这也是没办法的事,宁可错检,不能漏检,下同; // 另外,本处bit清位也没有使用原子操作,因此有可能出现刚刚置位,但被这里又清位的情况,但由于这里清位后马上会 // 对位对应句柄进行扫描,因此不会丢失置位,虽然同样地会有已置位已被扫描的情形,但没有大影响。 actbitLev2 &= (~(0x00000001<= (I32)m_innerArrSize ) { NewLog( LOG_LEV_ERROR, "ExploreActiveEles,非法的位置:%d", seqno3 );//不可能; break; } if ( NULL != m_innerArr[seqno3] ) { bool isExpOK = m_innerArr[seqno3]->OnBaseActiveBeExplored();//遍历活动位置元素; if ( !isExpOK ) { NewLog( LOG_LEV_INFO, "ExploreActiveEles,遍历位置:%d失败,可能相应元素已无效,应删去此元素", seqno3 ); //析构函数中会执行此操作,ClearActiveFlag( seqno3 ); //delete m_innerArr[seqno3]; m_innerArr[seqno3]->DestorySelfIns(); m_innerArr[seqno3] = NULL;//除非之前已先关闭,并设置自身需要遍历中删去,否则不可删,因为有可能epoll线程正在使用此socket; NewLog( LOG_LEV_INFO, "删去位置%d元素结束", seqno3 ); } } else { //注意:有可能当前欲遍历句柄实际已无效,具体参见ClearActiveFlag中的说明; // 也有可能其实该置位事件检测在上次遍历时已进行,但置位标记由于竞态条件又被置为1,参见前条注释(宁可错检,不能漏检): NewLog( LOG_LEV_WARNING, "ExploreActiveEles,欲遍历的位置:%d,指针空", seqno3 );//不可能; } } while ( 0 != actbitLev2 ); } while ( 0 != actbitLev1 ); } while ( 0 != actbitLev0 );//第一层遍历; return true; } ///遍历所有有效元素,目前方法:加大遍历频率,每次遍历更少部分元素; bool CManFrame::ExploreValidEles() { static unsigned int exppos = 0;//本次遍历起始位置; static unsigned int expStTime = GetTickCount();//测量遍历用时 BrocastRefreshProc();//广播包支持,遍历前先清理淘汰旧的广播包; exppos += 1; exppos = (exppos= expnumper ) { return true;//已达到本次遍历需要的遍历数 } curexpnum += 32; exppos += 32;//只要进了这一层,就会把这一小组的所有可能活动元素遍历一遍; beexped32 = m_ValidPos[lev0bit][lev1bit]; if ( 0 == beexped32 ) { //对应的32个位置都无有效元素 continue; } //依次处理curexped32中的32位,如果某位有效,则遍历对应位置元素; do { I32 tmpbit1 = ( beexped32 & (~(beexped32-1)) ); if ( 0 == tmpbit1 ) { break; } I32 bitpos = GetLogValAsm( tmpbit1 ); if ( (bitpos<0) || (bitpos>=32) ) { NewLog( LOG_LEV_ERROR, "ExploreValidEles,计算%d的最低有效bit位失败", tmpbit1 );//不可能; break; } beexped32 &= (~(0x00000001<OnBaseValidBeExplored() ) { NewLog( LOG_LEV_INFO, "ExploreValidEles,遍历位置:%d失败,可能相应元素已无效,应删去此元素", validno3 ); //析构函数中会执行此操作,ClearActiveFlag( validno3 ); //delete m_innerArr[validno3]; m_innerArr[validno3]->DestorySelfIns(); m_innerArr[validno3] = NULL; NewLog( LOG_LEV_INFO, "删去位置%d元素结束", validno3 ); } else { //元素有效遍历正常,继续执行一次广播包检查; FrameEleCheckBrocast( m_innerArr[validno3] );//广播包支持; } }while ( 0 != beexped32 ); } //for ( ; lev1bit<32; ++ lev1bit ) } //for ( ; lev0bit<32; ++ lev0bit ) static unsigned int expEndTime = GetTickCount();//测量遍历用时 unsigned int expUsedTime = expEndTime - expStTime; static unsigned int maxExpTime = 0; if ( expUsedTime > maxExpTime ) { maxExpTime = expUsedTime; NewLog( LOG_LEV_INFO, "遍历有效元素最大用时,tick:%d", maxExpTime ); } return true; }