这一次,琢磨了一下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. 推荐一个第三方Qt库的集合

    https://inqlude.org/ Stable libraries | Development versions | Unreleased | Commercial | All attica ...

  2. hdu 4708 Rotation Lock Puzzle 2013年ICPC热身赛A题 旋转矩阵

    题意:给出一个n*n的矩阵,旋转每一圈数字,求出对角线可能的最大值,以及转到最大时的最小距离. 只要分析每一层就可以了,本来想用地址传递二维数组,发现行不通,改了一下就行了. 这里有个坑,比如: 1 ...

  3. 450A - Jzzhu and Children 找规律也能够模拟

    挺水的一道题.规律性非常强,在数组中找出最大的数max,用max/m计算出倍数t,然后再把数组中的书都减去t*m,之后就把数组从后遍历找出第一个大于零的即可了 #include<iostream ...

  4. 大话项目管理工具之Confluence篇

    前言 前面的两篇文章,讲的都是代码层次上的项目管理工具.这篇文章打算介绍一下关于知识管理的工具,当然,它也属于项目管理中的一部分.说到知识管理,你想到了什么呢?织网?找节点?不错,这是个人的知识管理. ...

  5. python 时间戳 datetime string 转换

    import datetime import time **datetime转时间戳** In [1]: now = datetime.datetime.now() In [2]: time.mkti ...

  6. linux 配置 mail server

    一.配置yum安装工具 ①  进入yum目录 [root@bj ~]# cd /etc/yum.repos.d ②  配置yum.repo [root@bj yum.repos.d]# cprhel- ...

  7. WM_PAINT产生原因有2种(用户操作和API)——WM_PAINT和WM_ERASEBKGND产生时的先后顺序不一定(四段讨论)

    1. 当WM_PAINT不是由InvalidateRect产生时,即由最大化,最小化等产生时,或者移动产生(移动有时只会产生WM_ERASEBKGND消息)系统先发送WM_ERASEBKGND消息,再 ...

  8. Next-Key Locks

    Next-Key Locks 一个next-key lock 是 一个record lock 在index record 和 一个区间锁 在一个区间在index record之前 InnoDB 执行 ...

  9. 王垠:Lisp 已死,Lisp 万岁!

    王垠:Lisp 已死,Lisp 万岁!_IT新闻_博客园 王垠:Lisp 已死,Lisp 万岁!

  10. [C++]C++中的运行时类型检测

    Date:2014-1-3 Summary: 使用C++中的运行时类型检测.(文章重点在于记录本人的使用情况,并非深层讨论RTTI) Contents:写习惯C#的我,在C++依然存在哪些.NET的惯 ...