这段时间琢磨了一下Unity3D网络游戏开发中的网络消息处理。网络游戏的服务端一般都是自主开发的,所以对应的网络消息处理也要自己开发。客户端/服务端之间的消息传到目前使用JSON和Google.ProtocolBuffers是两种常见的做法。打开炉石的代码看了看它的处理方式,感觉代码写的还是很好的,把它的思路分析一下,与大家分享。

整体机制描述

我们想要达到的目标大概是这样的:
  • 有N个网络消息,每个消息对应一个Proto中的message描述;
  • 每个消息对应一个数字ID;
  • 底层在收到消息是,将其解析成为Google.ProtocolBuffers.IMessage对象,这个对象的具体类型应该是前面那个message生成的代码;
  • 发送消息就简单了,因为知道其类型,可以直接执行序列化;

炉石使用Google.ProtocolBuffers类库,可以看这里:http://www.nuget.org/packages/Google.ProtocolBuffers/

消息发送

发送的机制很简单,首先使用ProtocolBuffer生成的message类构造一个消息对象,例如:ConnectAPI.SendPing()
public static void SendPing()
{Ping.Builder body = Ping.CreateBuilder();QueueGamePacket(0x73, body);s_lastGameServerPacketSentTime = DateTime.Now;
}

底层会构造一个“PegasusPacket”数据包对象,添加到发送队列之中,这个数据包对象主要包含3部分:消息ID,消息大小,具体消息数据。详见PegasusPacket.Encode()函数:

public override byte[] Encode()
{if (!(this.Body is IMessageLite)){return null;}IMessageLite body = (IMessageLite) this.Body;this.Size = body.SerializedSize;byte[] destinationArray = new byte[8 + this.Size];Array.Copy(BitConverter.GetBytes(this.Type), 0, destinationArray, 0, 4);Array.Copy(BitConverter.GetBytes(this.Size), 0, destinationArray, 4, 4);body.WriteTo(CodedOutputStream.CreateInstance(destinationArray, 8, this.Size));return destinationArray;
}

消息接收与解析

接下来我们重点看一下消息的接收与解析机制。首先因为TCP是流式的,所以底层应该检测数据包头,并收集到一个完整的数据包,然后再发送到上层解析,这部分逻辑是在”ClientConnection<PacketType>.BytesReceived()“中实现的。当收到完整数据包时,会在主线程中触发”OnPacketCompleted“事件,实际上会调用到”ConnectAPI.PacketReceived()“,其内部主要是调用了”ConnectAPI.QueuePacketReceived()“,这个函数负责将TCP层接收到的byte[]解析成对应的IMessage对象。
重点来了!由于网络层发过来的数据包,只包含一个消息ID,那么客户端就需要解决从ID找到相应的消息Type的问题。想象中无非有两种方式去做:1是手动记录每个ID对应的Type;2是搞一个中间的对应关系的类,附加上自定义的Attribute,然后在使用反射机制自动收集这些类,其实和前者也差不多。炉石采用了第一种方式。整体机制是这样的:
  • 客户端每个消息对应一个PacketDecoder的派生类对象;
  • ConnectAPI类使用一个字典,用来保存<消息ID,Decoder对象>之间的对应关系:ConnectAPI.s_packetDecoders:SortedDictionary<Int32,ConnectAPI.PacketDecoder>;
  • 如果每个消息都要写一个Decoder,而其内部代码由完全一致,岂不是很蛋疼?!好吧,我们用模板来实现,详见后续分析;
  • 在ConnectAPI.ConnectInit()初始化的时候,创建Decoder对象,并保存到上述dict之中,类似这样:
     s_packetDecoders.Add(0x74, new DefaultProtobufPacketDecoder<Pong, Pong.Builder>());
  • 最后在上述的收到完整数据包的函数中,根据数据包中记录的消息ID,去查找Decoder,然后调用其方法得到具体的消息对象,类似这样:
     if (s_packetDecoders.TryGetValue(packet.Type, out decoder)){PegasusPacket item = decoder.HandlePacket(packet);if (item != null){queue.Enqueue(item);}}else{Debug.LogError("Could not find a packet decoder for a packet of type " + packet.Type);}

最后我们看一下,Decoder模板的实现技巧。首先消息解析的具体操作是有Google.ProtocolBuffers生成的代码去实现的,所以具体操作流程是完全一致的,这些写到基类的的静态模板函数中:

public abstract class PacketDecoder
{// Methodspublic abstract PegasusPacket HandlePacket(PegasusPacket p);public static PegasusPacket HandleProtoBuf<TMessage, TBuilder>(PegasusPacket p) where TMessage: IMessageLite<TMessage, TBuilder> where TBuilder: IBuilderLite<TMessage, TBuilder>, new(){byte[] body = (byte[]) p.Body;TBuilder local2 = default(TBuilder);TBuilder local = (local2 == null) ? Activator.CreateInstance<TBuilder>() : default(TBuilder);p.Body = local.MergeFrom(body).Build();return p;}
}

其次,使用一个模板派生类,实现HandlePacket()这个虚函数,主要的目的只是把TMessage和TBuilder这两个类型传给那个静态函数而已:

public class DefaultProtobufPacketDecoder<TMessage, TBuilder> : ConnectAPI.PacketDecoder where TMessage: IMessageLite<TMessage, TBuilder> where TBuilder: IBuilderLite<TMessage, TBuilder>, new()
{// Methodspublic override PegasusPacket HandlePacket(PegasusPacket p){return ConnectAPI.PacketDecoder.HandleProtoBuf<TMessage, TBuilder>(p);}
}

OK,炉石是使用使用ProtocolBuffers来处理网络消息的机制就是这样,是不是已经很清晰啦!

《炉石传说》架构设计赏析(7):使用Google.ProtocolBuffers处理网络消息相关推荐

  1. 《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息

    这一次,琢磨了一下Unity3D网络游戏发展的网络信息处理.服务器的网络游戏一般都是自主研发,因此,相应的网络消息处理应该培养自己.client/现在使用的邮件服务器之间的价差JSON和Google. ...

  2. 《炉石传说》架构设计赏析(4):Asset管理

    话说,经过这段时间的学习和摸索,对于Unity3D的开发思路已经基本清晰了.唯独还剩下一个AssetBundle机制还没有搞透,这个涉及到前期项目的资源规划.资源管理代码的写法,以及自动更新机制的实现 ...

  3. 《炉石传说》架构设计赏析(1):游戏启动流程

    今年的Unity Awards两项大奖颁给了暴雪的<炉石传说>,这真是对Unity一个再好不过的宣传了--你看,暴雪都开始用Unity了.大家都知道,目前Unity发布的游戏大多都没有对程 ...

  4. 《炉石传说》架构设计赏析(6):卡牌 技能数据的运行时组织

    前一篇文章我们看到了<炉石传说>的核心卡牌数据的存储,今天我们继续探索卡牌&技能. 主要的类 通过之前的分析,卡牌&技能涉及到几个类体系:Entity,Actor,Card ...

  5. c语言炉石传说算法设计,CCF-CSP题解 201609-3 炉石传说

    模拟. 注意随从的编号在\(summon\)和\(attack\)随从死亡时都可能改变. #include using namespace std; struct tNode { int attack ...

  6. c语言炉石传说算法设计,FZU Problem 2232 炉石传说(匈牙利算法)

     Problem Description GG学长虽然并不打炉石传说,但是由于题面需要他便学会了打炉石传说.但是传统的炉石传说对于刚入门的GG学长来说有点复杂,所以他决定自己开发一个简化版的炉石传说. ...

  7. c语言炉石传说算法设计,炉石传说:下棋吃鸡还扣分?设计师:现在已经改回去了...

    原标题:炉石传说:下棋吃鸡还扣分?设计师:现在已经改回去了 随着16.0补丁的上线,很多酒馆战棋的高分玩家发现了一件非常反常的事情.那就是补丁更新之后,赢一局不仅不加分而且会扣分(前四名),甚至于吃鸡 ...

  8. 《炉石传说》架构设计赏析(2):Scene管理

    今天我们主要分析一下炉石这款游戏中一共有哪些Scene,他们各自负责什么,以及它内部的逻辑.UI的处理方式. 在正式开始之前,我来对前文中提到的Scene切换再做一些补充分析.前文中我们看到Scene ...

  9. 《炉石传说》架构设计赏析(5):卡牌 技能的静态数据组织

    经过前面几次的尝试,我们对炉石的代码已经不陌生了.除了网络机制还没有了解以外,本机的逻辑已经比较熟悉了. 接下来继续向暴雪最NB的技能系统进发,我们的目标是: 分析技能的静态数据描述: 分析技能的运行 ...

最新文章

  1. 整合Web应用与Axis2
  2. Android View 的onDraw 和 draw 一定会调用吗?
  3. .NET通过RFC读取SAP数据
  4. django 获取外键对应数据的方式
  5. mysql主从复制运维_Mysql主从复制配置
  6. ‘仿微信发表朋友圈’项目中登录功能的业务逻辑
  7. 深入理解Java ClassLoader及在 JavaAgent 中的应用
  8. linux下tar包安装sudo命令,ubuntu12.04LTS安装gv-412-Linux-x86.tar.gz方法
  9. android 480p分辨率,[RK3399][Android7.1] HDMI显示屏(副屏)调试记录小结
  10. jquery ajax 调用webservice以及跨域问题
  11. 微信公众平台开发者原理图解
  12. CSS选择器的优先级计算
  13. PHP开发之留言板项目的一般流程,PHP留言板小项目(大作业)
  14. vue-element-admin-master 在线教育 - 【4】你用 POI,我已经用 EasyExcel 了+课程科目管理
  15. Hibernate(6)——映射类型
  16. 二维绕任意点旋转_旋转变换(一)旋转矩阵
  17. U盘/移动硬盘 有写保护怎么解除【未解决】
  18. react如何请求amr文件流接口-优化版
  19. 运行剑灵与服务器断开,剑灵手游程序错误 和服务器断开解决方法
  20. 联想电脑尺寸在哪里看_图文教你如何查看thinkpad的型号_查看thinkpad型号的方法-系统城...

热门文章

  1. 功能演示:使用Java加密和解密Excel文件
  2. 关于大数据中的NOSQL
  3. 【Python3 爬虫学习笔记】用PySpider爬取虎嗅网并进行文章分析
  4. VS2022“clr“和“zw“命令行选项不兼容
  5. 【解决】小程序|微信公众号授权给第三方平台时报“没有绑定公众号”
  6. 北斗B1I测距码的产生以及matlab程序,FPGA程序
  7. Threejs中的Shadow Mapping(阴影贴图)
  8. 索佳电子水准数据传输软件_索佳全站仪SET数据编辑通讯程序
  9. 万事开头难,坚持就是胜利!
  10. Qt入门学习之美化与样式设置