《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息
这一次,琢磨了一下Unity3D网络游戏发展的网络信息处理。服务器的网络游戏一般都是自主研发,因此,相应的网络消息处理应该培养自己。client/现在使用的邮件服务器之间的价差JSON和Google.ProtocolBuffers有两种常见的方法。平炉码看其处理。代码写的还是非常好的,把它的思路分析一下。与大家分享。
总体机制描写叙述
- 有N个网络消息,每一个消息相应一个Proto中的message描写叙述;
- 每一个消息相应一个数字ID;
- 底层在收到消息是,将其解析成为Google.ProtocolBuffers.IMessage对象,这个对象的详细类型应该是前面那个message生成的代码;
- 发送消息就简单了,由于知道其类型,能够直接运行序列化。
炉石使用Google.ProtocolBuffers类库,能够看这里:http://www.nuget.org/packages/Google.ProtocolBuffers/
消息发送
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;
}
消息接收与解析
当收到完整数据包时。会在主线程中触发”OnPacketCompleted“事件,实际上会调用到”ConnectAPI.PacketReceived()“,其内部主要是调用了”ConnectAPI.QueuePacketReceived()“,这个函数负责将TCP层接收到的byte[]解析成相应的IMessage对象。
想象中无非有两种方式去做:1是手动记录每一个ID相应的Type;2是搞一个中间的相应关系的类,附加上自己定义的Attribute,然后在使用反射机制自己主动收集这些类,事实上和前者也差点儿相同。
炉石採用了第一种方式。总体机制是这种:
- client每一个消息相应一个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
{
// Methods
public 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()
{
// Methods
public override PegasusPacket HandlePacket(PegasusPacket p)
{
return ConnectAPI.PacketDecoder.HandleProtoBuf<TMessage, TBuilder>(p);
}
}
OK,炉石是使用使用ProtocolBuffers来处理网络消息的机制就是这样,是不是已经非常清晰啦!
版权声明:本文博主原创文章,博客,未经同意不得转载。
《炉石传说》建筑设计欣赏(7):采用Google.ProtocolBuffers处理网络消息的更多相关文章
- 《炉石传说》建筑设计欣赏(6):卡&在执行数据时,组织能力
上一篇文章我们看到了<炉石传说>核心存储卡的数据,今天,我们不断探索卡&身手. 基本的类 通过之前的分析,卡牌&技能涉及到几个类体系:Entity.Actor.Card.S ...
- [原创]webapp/css3实战,制作一个《炉石传说》宣传页
在移动网页,尤其是webapp中常需要用到大量的css3动画,来获得良好交互体验 我之前帮朋友做了一个,可惜没帮上忙现在和大家分享一下 目标是要做一个<炉石传说>游戏的介绍宣传页面,文字内 ...
- 炉石传说 C# 开发笔记(6月底小结)
炉石传说的开发,已经有30个工作日了. 关于法术的定义方法,有过一次重大的变更:法术效果是整个炉石的核心,正是因为丰富的法术效果,才造就了炉石的可玩性. 原来构思的时候,对于法术效果没有充分的理解,所 ...
- 炉石传说 C# 开发笔记 (续)
炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比 ...
- 炉石传说 C# 开发笔记
最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏.加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说. 这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩... ...
- 炉石传说__multiset
炉石传说 Problem Description GG学长虽然并不打炉石传说,但是由于题面需要他便学会了打炉石传说.但是传统的炉石传说对于刚入门的GG学长来说有点复杂,所以他决定自己开发一个简化版 ...
- fzu Problem - 2232 炉石传说(二分匹配)
题目链接:http://acm.fzu.edu.cn/problem.php?pid=2232 Description GG学长虽然并不打炉石传说,但是由于题面需要他便学会了打炉石传说.但是传统的炉石 ...
- islands打炉石传说<DP>
islands最近在完一款游戏"炉石传说",又名"魔兽英雄传".炉石传说是一款卡牌类对战的游戏.游戏是2人对战,总的来说,里面的卡牌分成2类,一类是法术牌,另一 ...
- CCF2016093炉石传说(C语言版)
问题描述 <炉石传说:魔兽英雄传>(Hearthstone: Heroes of Warcraft,简称炉石传说)是暴雪娱乐开发的一款集换式卡牌游戏(如下图所示).游戏在一个战斗棋盘上进行 ...
随机推荐
- 一场刺激的游戏——很文艺的山东省第四届ACM赛总结(菜鸟版)
人生就像一个个节点,节点中或许有成功,失败,满足,遗憾,但是只要它是不可复制的,在日后,便是美好. ...
- 微信公众平台应用开发框架sophia设计不足(1)
设计一个小框架考虑的东西真不少,每一样都不easy: 1.既要解决当前技术的不足: 2.又要方便他人使用(基本的目的). 3.同一时候又要设计得优雅.easy扩展. sophia一開始设计用来支持智能 ...
- Android中获取IMEI码
Imei = ((TelephonyManager) getSystemService(TELEPHONY_SERVICE)) .getDeviceId(); 1.加入权限 在manifest.xml ...
- linux-sfdisk 使用方法
功能说明:硬盘分区工具程序. 语 法:sfdisk [-?Tvx][-d <硬盘>][-g <硬盘>][-l <硬盘>][-s <分区>][-V < ...
- java中super()和this()浅析
<span style="font-size:18px;">本质:这两个都是调用构造方法的方法.</span> 在java中,super()是在当前类的构造 ...
- 学习日记之命令模式和Effective C++
命令模式(Command): 讲一个请求封装为一个对象.从而使你可用不同的请求对客户进行參数化.对请求队列或记录请求日志.以及支持可撤销的操作. 命令模式长处: (1),它能较easy地设计一个命令队 ...
- 图解:如何U盘装Win7系统(傻瓜式装机) + 分区步骤图解(用WIN7自带管理工具)
原地址:http://wenku.baidu.com/link?url=wV2Pfw2IM21u2KmtAcNweSZRwpXRuKAVAS29dS4aWGEpMtFdDlzZvixCgsvBxIm- ...
- ubuntu/linux mint 创建proc文件的三种方法(四)
在做内核驱动开发的时候,能够使用/proc下的文件,获取对应的信息,以便调试. 大多数/proc下的文件是仅仅读的,但为了演示样例的完整性,都提供了写方法. 方法一:使用create_proc_ent ...
- Exception in thread "main" java.lang.IllegalArgumentException
1.错误叙述性说明 Exception in thread "main" java.lang.IllegalArgumentException: Cannot format giv ...
- Web Api 2(Cors)Ajax跨域访问
支持Ajax跨域访问ASP.NET Web Api 2(Cors)的简单示例教程演示 随着深入使用ASP.NET Web Api,我们可能会在项目中考虑将前端的业务分得更细.比如前端项目使用Ang ...