《炉石传说》建筑设计欣赏(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/

消息发送

发送的机制非常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
{// 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来处理网络消息的机制就是这样,是不是已经非常清晰啦!





版权声明:本文博主原创文章,博客,未经同意不得转载。

转载于:https://www.cnblogs.com/blfshiye/p/4876797.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/574258.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

tarjan算法c语言,tarjan算法板子 - osc_e45irv7l的个人空间 - OSCHINA - 中文开源技术交流社区...

无向图概念时间戳\(dfn[x]\),在深度优先遍历中&#xff0c;按照每个节点第一次被访问的顺序&#xff0c;依次做整数标记追溯值\(low[x]\),通过非搜索边能到达的最小时间戳割边判定法则无向边\((x,y)\)是割边/桥&#xff0c;当且仅当存在x的一个子节点满足\(dfn[x] < low[y]\…

流和文件

流&#xff1a;流是数据的传输方式&#xff1b;C程序处理一个流而不是直接处理文件。你声明一个FILE *fp &#xff0c;并把fopen(某个文件)返回的值赋予fp这两个动作就相当于建立了一个水龙头&#xff0c;当你用getc(fp)之类的输入函数读取文件字符时就相当于拧开了水龙头&…

小球进盒子C语言,N个小球放进M个盒子算法-Go语言中文社区

N个小球放入M个盒子共有多少种方法&#xff0c;并输出的算法设计&#xff1a;算法思路1 &#xff1a;暴力填充盒子每个小球都可能放入M个盒子的任意一个&#xff0c;所以直接根据小球个数做递归即可,然后将存储放入hash中排重//TODO算法思路2 &#xff1a;递归填充盒子即&#…

r语言c5.0要求因子输出,R语言中因子的创建与使用

原标题&#xff1a;R语言中因子的创建与使用因子在R语言中可以用来表示名义型变量或有序变量。名义变量一般表示类别&#xff0c;如性别&#xff0c;种族等等。有序变量是有一定排序顺序的变量&#xff0c;如职称&#xff0c;年级等等。在R语言中&#xff0c;名义变量和有序变量…

WinForm中使用AnyCAD三维控件 の 初始化

在WinForm中可以方便的集成AnyCAD.Net三维控件&#xff0c;只需要以下几部即可完成。 一、添加DLL程序集 AnyCAD.Foundation.Net.dll AnyCAD.Presentation.Net.dll AnyCAD.Exchange.Net.dll 二、初始化控件 1.首先创建一个窗体 2.在窗体上放置一个Panel用来放置三维控件 3.初始…

linux中nodejs后台运行工具forever

forever让nodejs应用后台执行 命令如下&#xff1a; forever start ./bin/www nodejs一般是当成一条用户命令执行的&#xff0c;当用户断开客户连接&#xff0c;运用也就停了&#xff0c;很烦人。如何让nodejs应用当成服务&#xff0c;在后台执行呢&#xff1f; 最简单的办法(不…

android启动其他app的服务器,Android中通过外部程序启动App的三种方法

这篇文章主要介绍了Android中通过外部程序启动App的三种方法,本文讲解了直接通过包名、通过自定义的Action、通过Scheme三种方法,并分别给出操作代码,需要的朋友可以参考下第一种&#xff1a;直接通过包名&#xff1a;复制代码 代码如下:Intent LaunchIntent getPackageManage…

linux date 天之前,linux date命令前后几天的推导

linux date使用(前后几天时间推导)在Linux系统 中&#xff0c;可以采用如下方法&#xff1a;1) 取之前的时间&#xff1a;date -d "a day ago" %Y%m%d 取出前1天的系统时间date -d "2 days ago" %Y%m%d 取出前2天的系统时间2) 取之后的时间&#xff1a;dat…

各种封装——封装getClass

因为用Class获取元素时&#xff0c;有兼容性问题&#xff0c;需要分情况获取 显示判断是否可以用getElementsByClassName&#xff0c;若是不介意用就要先获取全部的元素&#xff0c;在用正则判断 function getClass(oParent,sClass){if(oParent.getElementsByClassName){return…

android提示程序正在执行,Android中获取正在运行的进程(一)

关于android中应用程序正在运行的进程有下面几种&#xff1a;1-包含services的进程&#xff0c;2-不包含services的进程&#xff0c;3-杀死应用时有些进程被init回收(类似于僵尸进程)&#xff0c;ppid变为1&#xff0c;无法通过android 应用层代码获得的进程。这些进程可能是应…

jta 知识

JTA知识&#xff08;转载原文地址&#xff1a;http://blog.csdn.net/it_man/article/details/7230215&#xff09; Java Transaction API&#xff0c;译为Java事务API。JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JTA主要用于分布式…

android l usb调试,你居然还不会手机usb调试?5个方法,让你轻松学会设置!

原标题&#xff1a;你居然还不会手机usb调试&#xff1f;5个方法&#xff0c;让你轻松学会设置&#xff01;最近有位朋友将手机连接电脑的时候&#xff0c;遇到了提示打开手机的USB调试模式&#xff0c;却不知道怎么操作。其实&#xff0c;不同手机的usb调试打开的方式都会有所…

深入沟通的重要性——《大道至简》第四章读后感

我们都知道&#xff0c;一个新发明如果不被人们所需要&#xff0c;那只是一个失败品。一篇文章的观点如果不被人们所接受&#xff0c;那么就算文笔再好&#xff0c;也只是一张废纸。同样的&#xff0c;一个与客户需求不符的程序&#xff0c;哪怕它的架构再好&#xff0c;都只是…

鸿蒙os芯片,没有了芯片,华为能靠鸿蒙OS系统打出差异化吗?

原标题&#xff1a;没有了芯片&#xff0c;华为能靠鸿蒙OS系统打出差异化吗&#xff1f;华为今天要和大家说的手机产品是华为&#xff0c;在目前的市场中&#xff0c;华为手机的影响力是非常大的&#xff0c;并且获得了十分不错的销量&#xff0c;甚至在最近一段时间销量超过三…

html5 canvas 不兼容safari浏览器_HTML5简介

HTML5 是继 HTML4.01 和 XHTML1.0 之后的超文本标记语言的最新版本。它是由一群自由思想者组成的团队设计出来&#xff0c;并最终实现多媒体支持、交互性、更加智能的表单&#xff0c;以及更好的语义化标记。HTML5 并不仅仅是 HTML 规范的最新版本&#xff0c;而是一系列用来制…

html5置顶标签css样式,html5 header标签 html header css布局教程 /header

在HTML5版本之前习惯使用div标签布局网页&#xff0c;在HTML5在DIV标签基础上新增header标签元素。也叫“”头部标签。以前我们在div css布局中常常把网页大致分为头部、内容、底部。对于大结构我们常常使用div里加id进行布局。而头部常常使用正应为大家公认html布局中对“head…

DML数据操作语言练习

--创建表T_HQ_BM2 --create table t_hq_bm2 as select * from t_hq_bm; commit;--添加行内容 --insert into t_hq_bm2 values (107,研发部,147258369); commit;--删除部门编码为107的行 --delete t_hq_bm2 where bumenbm 107; commit;--添加行内容 --insert into t_hq_bm2 (bu…

第八届育才杯机器人比赛_赛场、名单公布!南海区第八届“献血者杯”羽毛球公开赛“羽”你相约本周六...

主办单位&#xff1a;南海区献血办、南海血站协办单位&#xff1a;南海区羽毛球协会为进一步扩大无偿献血宣传&#xff0c;感恩南海区献血者&#xff0c;活跃无偿献血者的文体生活&#xff0c;打造一个南海区无偿献血者交流的平台&#xff0c;共同营造“运动、健康、献血、快乐…

2021届安徽高考成绩查询,安徽2021年高考成绩什么时候公布

据安徽省教育招生考试院透露&#xff0c;安徽预计将于6月23日公布各批次录取分数线、考生成绩。2021年安徽高考成绩查询入口2021年高考录取方法1、普通高校的招生录取工作在教育部和省高校招生委员会的领导下&#xff0c;由省考试院组织实施。2、省高校招生委员会根据当年高校在…

BroadcastReceiver 广播机制详解

BroadcastReceiver也就是“广播接收者”的意思&#xff0c;顾名思义&#xff0c;它就是用来接收来自系统和应用中的广播。 在Android系统中&#xff0c;广播体现在方方面面&#xff0c;例如当开机完成后系统会产生一条广播&#xff0c;接收到这条广播就能实现开机启动服务的功能…