玩家下线(GS部分)
玩家下线,之前一直感觉这个过程有点复杂 else if (stat == link_stat::link_disconnected || stat == link_stat::link_connect_failed ) { GameChannel* pDisconnectGC = m_vecChannel[rPkt.channel_id]; if(pDisconnectGC) { //如果进入了地图,保存人物信息时会调用push_freeQueue + 滞空m_Channels[channel_id], pDisconnectGC->OnDisconnect();//下线的一些收尾工作 //如果未进地图就下线,直接断开;不用保存角色详细数据,可直接放入释放队列中 if(!pDisconnectGC->m_pMap) { //PushFreeQueue(pDisconnectGC); //m_vecChannel[rPkt.channel_id] = NULL; AutoFreeGC(pDisconnectGC); } } m_LiveMgr.Remove(rPkt.channel_id);//将livemgr的这个位置清零 } 下面一步一步看这个过程 bool GameChannel::OnDisconnect() { //m_isDisconnect = true; OnOffLine(); return true; } void GameChannel::OnOffLine() { switch (m_eGameState) { case eGameState_Login://登录状态 ClearLoginRcd();//清除登录状态:主要是删除已删除的角色,删除在线用户 break; case eGameState_EnterMap://进入地图状态 ClearMapRcd();//从map下线,这个获取后面看 ClearLoginRcd();//清除登录状态 break; case eGameState_InChangeMap: //...正在切图就下线了,是不是该延时下线呢? m_eGameState = eGameState_DelayDisconnect; case eGameState_OnlyConnect: //未登陆,空连接 case eGameState_Disconnect: //已离线 return; default: break; } m_eGameState = eGameState_Disconnect;//将状态切换成离线状态 } void GameServer::AutoFreeGC(GameChannel* pGameChannel) { m_vecChannel[pGameChannel->m_nChannelId] = NULL; //channel里面有个m_uDBGetAskRefCount,由于判断数据库是否有返回,加入数据库很慢的时候玩家下线你把这个内存释放了,等数据库返回的时候就访问了野指针 if (pGameChannel->IsNoDBAsk())//没有数据库访问,可以安全的删除指针了 { delete pGameChannel;//删除玩家指针 pGameChannel = nullptr; } else { PushFreeQueue(pGameChannel);//如果还有数据库访问,就将其放到释放队列中延迟释放 } } void GameServer::PushFreeQueue(GameChannel* gc) { FreeChannel fch; fch.m_pGameChannel = gc; fch.m_uTime = GetTickCount(); m_FreeQueue.push(fch); } //那这个队列到底什么时候释放的呢? 在主线程里面初始化了一个定时器 void GameServer::InitTimer() { I_TimerFactory* pTimeFactory = NEW(TimerFactory); SetPlug("TimerFactory", pTimeFactory); m_FreeQueueTimer.reset(pTimeFactory->createTimer()); m_FreeQueueTimer->regTimer(std::bind(&GameServer::FreeQueueTimer, this)); m_FreeQueueTimer->setInterval(30 * 1000);//(30 * 1000); m_FreeQueueTimer->start(); } //之前出现过,函数回调的时候出现错误,当时就是这个问题回调的时候玩家已经下线了,channel已经删除了 /** * 方案一:不直接释放,延迟1分钟,保证数据库不再返回时再释放。(数据库太忙情况不安全,停用) * 方案二:不直接释放,看GC标志是否无数据请求。 */ void GameServer::FreeQueueTimer() { if(!m_FreeQueue.size()) return ; int now = GetTickCount(); for (;;)//每次释放完或者到有不能释放的为止 { if(!m_FreeQueue.size()) return ; FreeChannel tmpFreeChannel = m_FreeQueue.front(); //if(now - tmpFreeChannel.m_uTime > 60 * 1000) if (tmpFreeChannel.m_pGameChannel->IsNoDBAsk())//等到数据库没有访问了再释放 { delete tmpFreeChannel.m_pGameChannel; m_FreeQueue.pop(); } else { break; } } }