这一次,琢磨了一下Unity3D网络游戏发展的网络信息处理。服务器的网络游戏一般都是自主研发,因此,相应的网络消息处理应该培养自己。client/现在使用的邮件服务器之间的价差JSON和Google.ProtocolBuffers有两种常见的方法。平炉码看其处理。代码写的还是非常好的,把它的思路分析一下。与大家分享。

总体机制描写叙述

我们想要达到的目标大概是这种:
  • 有N个网络消息,每一个消息相应一个Proto中的message描写叙述;
  • 每一个消息相应一个数字ID;
  • 底层在收到消息是,将其解析成为Google.ProtocolBuffers.IMessage对象,这个对象的详细类型应该是前面那个message生成的代码;
  • 发送消息就简单了,由于知道其类型,能够直接运行序列化。

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

消息发送

发送的机制非常easy,首先使用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。那么client就须要解决从ID找到相应的消息Type的问题。

想象中无非有两种方式去做: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处理网络消息的更多相关文章

  1. 《炉石传说》建筑设计欣赏(6):卡&amp;在执行数据时,组织能力

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

  2. [原创]webapp/css3实战,制作一个《炉石传说》宣传页

    在移动网页,尤其是webapp中常需要用到大量的css3动画,来获得良好交互体验 我之前帮朋友做了一个,可惜没帮上忙现在和大家分享一下 目标是要做一个<炉石传说>游戏的介绍宣传页面,文字内 ...

  3. 炉石传说 C# 开发笔记(6月底小结)

    炉石传说的开发,已经有30个工作日了. 关于法术的定义方法,有过一次重大的变更:法术效果是整个炉石的核心,正是因为丰富的法术效果,才造就了炉石的可玩性. 原来构思的时候,对于法术效果没有充分的理解,所 ...

  4. 炉石传说 C# 开发笔记 (续)

    炉石传说山寨的工作一直在进行着,在开发过程中深深体会到,对于业务的理解和整个程序的架构的整理远比开发难得多. 在开发过程中,如果你的模型不合理,不准确,很有可能造成代码的混乱,冗余,难以维护和扩展性比 ...

  5. 炉石传说 C# 开发笔记

    最近在大连的同事强力推荐我玩 炉石传说,一个卡牌游戏.加上五一放一个很长很长的假期,为了磨练自己,决定尝试开发一个C#的炉石传说. 这件事情有人已经干过了,开发了一个网页版的炉石,但是貌似不能玩... ...

  6. 炉石传说__multiset

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

  7. fzu Problem - 2232 炉石传说(二分匹配)

    题目链接:http://acm.fzu.edu.cn/problem.php?pid=2232 Description GG学长虽然并不打炉石传说,但是由于题面需要他便学会了打炉石传说.但是传统的炉石 ...

  8. islands打炉石传说<DP>

    islands最近在完一款游戏"炉石传说",又名"魔兽英雄传".炉石传说是一款卡牌类对战的游戏.游戏是2人对战,总的来说,里面的卡牌分成2类,一类是法术牌,另一 ...

  9. CCF2016093炉石传说(C语言版)

    问题描述 <炉石传说:魔兽英雄传>(Hearthstone: Heroes of Warcraft,简称炉石传说)是暴雪娱乐开发的一款集换式卡牌游戏(如下图所示).游戏在一个战斗棋盘上进行 ...

随机推荐

  1. Python 学习入门(23)—— 进程

    本文介绍Python的os包中有查询和修改进程信息的函数,Python的这些工具符合Linux系统的相关概念,所以可以帮助理解Linux体系. 1. 进程信息 os包中相关函数如下: uname()  ...

  2. JavaScript + CSS3 实现的海报画廊特效

    原文:JavaScript + CSS3 实现的海报画廊特效 这是慕课网上<CSS3+JS 实现超炫的散列画廊特效>的源代码,我修改了一些 bug 和调优了一些细节,并把学习过程中并不了解 ...

  3. android打包apk时混淆遇到的问题

    android打包apk的时候一般会选择混淆,而在eclipse中常使用的是proguard来混淆.有很多时候引用了第三方包的时候会导致打包不成功,或者打包成功不能运行的情况. 首先看看正常的prog ...

  4. OpenCv调用摄像头拍照代码

    近期在研究OpenCv对摄像头的调用.现将代码贴出,供大家批评指正. 1.申明 #include"./opencv2/opencv.hpp" #ifdef _DEBUG #prag ...

  5. 普联的路由器TL-WR842N和TL-WR845N还有 TL-WR847N哪一个更好,我是家用

    http://zhidao.baidu.com/question/525244706.html?fr=ala&device=mobile&ssid=0&from=844b&am ...

  6. 步步为营Hibernate全攻略(四)剪不断理还乱之:复合主键 && 组合映射

    一:复合主键 复合主键即两个或多个字段联合起来作为主键,它的通常做法是将主键相关字段抽取出来放到一个单独的类中,但是这样的类是有要求的: 1.      必须实现序列化接口 2.      必须覆盖e ...

  7. MariaDb数据库管理系统学习(二)使用HeidiSQL数据库图形化界面管理工具

    HeidiSQL 是一款用于简单化的 MySQL server和数据库管理的图形化界面.该软件同意你浏览你的数据库,管理表,浏览和编辑记录,管理用户权限等等.此外,你能够从文本文件导入数据,执行 SQ ...

  8. Selenium来抓取动态加载的页面

    一般的爬虫都是直接使用http协议,下载指定url的html内容,并对内容进行分析和抽取.在我写的爬虫框架webmagic里也使用了HttpClient来完成这样的任务. 但是有些页面是通过js以及a ...

  9. 安装Oracle JDK 7.0与8.0 for Mac OS X后Eclipse启动报错的解决之道

    启动 Eclipse 时,直接报错The JVM shared library "/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Cont ...

  10. 使用notepad运行python

    Notepad++ 是一个开源的文本编辑器,功能强大而且使用方便,一般情况下,Notepad++作为代码查看器,很方便,但是每次要运行的时候,总是需要用右键打开其他的IDE来编译和运行,总有些不方便. ...