客户端与服务器收发消息及问题查找排除的一些方法
一、 客户端与服务器收发消息的流程
订协议:即客户端与服务器先商定好所要用的OP_CODE和数据结构。
明确数据收发以及使用的规则。(这一点一定要双方完全明确,也是写这篇wiki的原因,正是因为没有完全设计好使用规则所以改bug时遇到很大的麻烦。)
写好opcode对应的调用函数handleXXX()(至于原理下面讲)。绑定方法:
_handlers[SMSG_HUNDREDS_TABLE_WIN_GOLD_SORT_RSP].handler = &GameSession::handleHundredsRankList;
消息收发以及使用的原理过程:
首先介绍一下客户端获取服务器数据的几种情况:
客户端主动请求,服务器返回消息;
在设定的某些游戏规则开始时服务器自动向客户端发送消息;
首先当客户端打开或进入某场景的时候先向服务器发送一个消息请求,首次获取消息,这一次消息请求一定要做,以获取最初的消息数据;我们目前已封装好的是在GameSession里面,调用方法是Game::sharedGame()->GetSession()->sendRequestXXX();即客户端主动向服务器发送消息请求,具体实现可以在源码中看一下这几个函数,sendRequestXXX()函数实现消息请求。
那么什么是GameSession呢,这个类是用来管理网络连接、设置服务器地址、端口号以及收发包的(如sendRequestXXX()),有兴趣的可以自己看一下源码。
此时客户端已经向服务器发送消息请求了,服务器收到请求后一般会向客户端返回一个消息,会什么说一般呢,那是因为服务器是可能不给你返回消息的,这就是为什么说应该事先要跟服务器定好收发规则原因,双发不约定好就会给后面的调试带来很大的困难。
发送消息请求后,客户端有一个消息监听器不断的监听来自网络层的消息,当收到消息后,就将消息加入到网络消息队列,然后将网络消息中的opcode拿到,连同消息以及对应的handleXXX()一起打包加入到另一个消息队列中,这是我们后面要用的消息队列。
对于消息使用机制:客户端有一个update()函数不断地检查上面的消息队列,对于每个消息,会根据他的opcode调用对应的handleXXX()函数,这个handle函数是干什么的呢,他是用来解析包和消息分发的,解析包即将受到的消息包按所定的协议中的数据结构解析到相应的数据结构中,解析数据包的写法可以参照源码中已有的方法。然后再将消息分发出去,分发消息的工作是由InvokeServerResponseDelegate(uint32 opcode, void* responseObject)做的,他主要实现通过遍历delegate代理的map数组,通过查找opcode找到对应的delegatelist链的头指针,再由头指针在这个list中遍历一遍,list中存放的是需要使用该消息的类,然后由该类调用自己的反应函数,我们的一般写为OnServerResponde(),在OnServerResponde()里面我们对消息做出相应的处理,即使用该消息。以上就是客户端收发消息的基本机制:发送请求----监听消息----加入消息队列----分发消息----处理消息。
二、关于消息收发出现问题时的排查步骤
消息接收分为几个步骤,即客户端是否发送请求、服务器是否发送消息、客户端是否收到消息、客户端是否分发了消息、是否有消息处理函数以及其是否被正确调用。客户端是否发送请求:例如Game::sharedGame()->GetSession()->sendRequestXXX();
服务器是否发送消息:需要跟服务器确认。
客户端是否收到消息:查看是否注册了handle函数,即
handlers[SMSG_HUNDREDS_TABLE_CHAMPION_TIME_RESP ].handler = &GameSession::handleHundredsChampionshipTime;
其中handleHundredsChampionshipTime的实现为:
void GameSession::handleHundredsChampionshipTime(INetPacket &packet)
{
HundredsChampionshipTimeResp resp;
resp.fromPacket(packet);
InvokeServerResponseDelegate(SMSG_HUNDREDS_TABLE_CHAMPION_TIME_RESP , &resp);
}
客户端是否分发了消息:即InvokeServerResponseDelegate(opcode, &resp)是否执行,其中InvokeServerResponseDelegate实现机制为:
void GameSession::InvokeServerResponseDelegate( uint32 opcode, void* responseObject )
{
std::map<unsigned int, std::list<ServerResponseDelegate*>*>::iterator itr = m_responseDelegates->find(opcode);
if (itr != m_responseDelegates->end())
{
std::list<ServerResponseDelegate*>*& delegateList = itr->second;
for(std::list<ServerResponseDelegate*>::iterator it = delegateList->begin(); it != delegateList->end();)
{
ServerResponseDelegate* delegate = *it;
++it;
delegate->onServerResponse(opcode, responseObject);
}
}
}
是否有消息处理函数以及其是否被正确调用:即InvokeServerResponseDelegate是否调用执行了onServerResponse(opcode, responseObject)消息响应函数。onServerResponse中实现根据传入的opcode选择对应的case处理消息,一般我们是对消息进行更新,先拿到消息,显示与否我们再根据需要再进行处理。onServerResponse实现的一个例子:
void HundredsRankList::onServerResponse(unsigned int op_code, void* responseObject)
{
switch (op_code)
{
case SMSG_HUNDREDS_TABLE_WIN_GOLD_SORT_RSP:
{
m_vecSortObj = (*static_cast<HundredsTableSortRsp*>(responseObject)).objs;
if ( !m_bInitedRank )
{
//updateList();
updateRankList();
if (m_pMenuGroup->getSelectedIndex() == 0 && !m_vecSortObj.empty())
{
setRootIsVisible(RankAndChampionListVisible);
}
m_bInitedRank = true;
}
}
break;
case SMSG_HUNDREDS_TABLE_CHAMPION_TIME_RESP:
{
m_pChampionshipTimeResp = (*static_cast<HundredsChampionshipTimeResp*>(responseObject));
updateChampionTimeInfo();
if (m_pMenuGroup->getSelectedIndex() == 1)
{
if (m_vectChampionObj.empty())
{
setRootIsVisible(ChampionTimeVisible);
}
else
{
updateChampionList();
setRootIsVisible(RankAndChampionListVisible);
m_bInitedChampion = true;
}
}
}
break;
default:
break;
}
}