采用用同步编程的方式实现跨进程异步获取数据

转载请注明:http://blog.csdn.net/herm_lib/article/details/11686483

异步获取数据,我这边采用了一点点小技巧,这样异步获取数据的操作,变成和同步操作一样自然。

这个我个人得静下心来,好好地梳理一下,过个半个月遇到类似的需求,我自己都忘记是怎么处理了。处理方式比较有意思。

遇到一些常见的异步操作的业务逻辑:

[1] 角色登录请求,发现内存中没有该角色数据,从远程的缓存或者DB读取该角色数据。

[2] 获取某个角色信息,发现这个角色信息目前不在线,这时候,也从远程的缓存或者db读取该角色数据。

[3] 判断这个家伙是否是另外一个玩家的好友,sns类游戏中,这类逻辑超多。

我们先把直接做法,简单列出来,用来和另外一种比较优雅的方式做比较。

术语定义:

Juge_Friend_Req

判断是否是好友的请求,发向好友服务器。

Juge_Friend_Res

从好友服务器回来。

Add_Energy_Req

从Client发过来的加能量的请求。

以[3]为例子,当然,好友服务器和主逻辑服务器是位于两个进程中,如果一个进程中,那没什么好说的。比如,给好友加能量,借好友宠物上场战斗。

  1. void AddEnergyReq(roleId1, roleId2)
  2. {
  3. // 向好友服务器发出判断是否是好友的请求
  4. JudgeFriendReq(roleId1, roleId2, kFriendBehaviorTypeAddEnergy);
  5. }
  6. void BorrowPetReq(roleId1, roleId2, petId)
  7. {
  8. JudgeFriendReq(roleId1, roleId2, kFriendBehaviorTypeBorrowPet);
  9. }
  10. // 从好友服务器回来判断是否是好友的回应
  11. void JudgeFriendRes(msg)
  12. {
  13. switch (msg.operType)
  14. {
  15. case kFriendBehaviorTypeAddEnergy:
  16. AddEnergy(msg.xxx);
  17. break;
  18. case kFriendBehaviorTypeBorrowPet:
  19. BorrowPet(msg.xxx);
  20. break;
  21. }
  22. }


从好友服务器回来的回应,以后有和好友判断相关的逻辑,会继续加新的业务分支。

这代码比较恶心,我们目标是,判断好友的回应回来后,我们的流程能继续回到各自请求的处理逻辑里去!从我们写代码的角度,就好像没有发生过异步请求。

我就记录一下,我们这边的处理方法。针对Add_Energe_Req的处理:

[1] 发出Judge_Friend_Req的时,把请求消息的内容,作为透传数据,发给好友服务器,然后就还回来,[4]用到。

[2] 引入一个附加参数,void* param,每个请求,各自转化自己的数据类型(这个描述不清楚)。

[3] 好友服务器回来消息,将消息分发给JudgeFriendRes的时候,设置上面那个param。

[4] 在Judge_Friend_Req时,有Add_Energe_Req的数据,想办法再一次分发Add_Energe_Req,重走一回AddEnergeReq流程。

发出好友请求和处理逻辑,两者是在一起的。

  1. void AddEnergyReq(msgReq, const Param* param)
  2. {
  3. if (!param) // param如果是nullptr, 还没有发过判断好友请求
  4. // 向好友服务器发出判断是否是好友的请求
  5. JudgeFriendReq(msgReq.roleId1, msgReq.roleId2, msgReq);
  6. else
  7. {
  8. bool isFriend = (bool)param;
  9. if (isFriend)
  10. AddEnergy(...);
  11. }
  12. }

发判断好友请求时,msgReq设置成透传;根据param是否是nullptr来判定是否已经发过Judge_Friend_Req。

如果借用宠物的逻辑,和这个类似,各自写自己的逻辑。

Judge_Friend_Res回来时,做的工作。先在网络消息的分发的时候,做一些手脚,设置附加参数。

  1. void MsgDispatch(buf, len)
  2. {
  3. ssMsg = ParseSSMsg(buf, len);
  4. DispatchSS(ssMsg, param); // 走到JudgeFriendRes
  5. csMsg = ssMsg.msgReq;
  6. DispatchCS(csMsg, param); // 走到AddEnergyReq
  7. }

在JugdeFriendRes里,设置param

  1. void JudgeFriendRes(ssMsg, param)
  2. {
  3. param = ssMsg.isFriend;
  4. }

设置这个参数后,DispatchCS用透传的数据,让流程继续回到AddEnergyReq。当第2次走到AddEnergyReq时,param != nullptr,直接判定是否是好友,接下来做该做的事情。

服务器管理多个玩家账号[记作Player], 每个Player维护多个角色列表,角色列表包含简单的角色信息(不是完整的,完整的在Role里面)。

处理Client的获取简单角色信息列表的请求。

  1. void GetRoleListReq(msgBody, csContext)
  2. {
  3. Player* player = PlayerPoolS::Instance()->Get(csContext->uin);
  4. if (!player) // player未从herm cached加载或者player还未存在
  5. GetPlayerFromHC(csContext);
  6. else
  7. ReturnRoleList(player, csContext);
  8. }

先从PlayerPool里看看有没有对应uin的Player,没有,从远程异步获取;否则,就直接给Client发回应。

比较有意思的地方出现了,看看这个异步请求发出后,如何恢复原来的流程的。

  1. void GetPlayerFromHC(csContext)
  2. {
  3. // 从远程的cahce或者db获取player
  4. }
  5. bool GetPlayerFromHCRes(hcMsg, void* param)
  6. {
  7. // uin 从param中获取,是GetPlayerFromHC透传过来的
  8. Player* player = PlayerPoolS::Instance()->Get(uin);
  9. if (!player)
  10. player = PlayerPoolS::Instance()->Create(uin);
  11. if (hcMsg->len == 0) // DB中没有记录
  12. return true;
  13. player->Load(hcMsg...);
  14. return true;
  15. }

GetPlayerFromHCRes处理回应。

从db这边返回来的结果,可以真正地判断Player是未从 db加载还是不存在了。

先创建Player对象,如果db不存在的,什么都不用做;db中存在,就把db中数据加载进来。另外一个独立的循环,会把新的或者变化的Player更新到db。

负责协议消息分发的模块,利用GetPlayerFromHC传进来的csContext,重新走一次GetRoleListReq()。

当第二次走GetRoleListReq()时候,Player有了,将会走ReturnRoleList()流程。