回合制游戏网络通信协议及心跳机制调研

回合制策略游戏

回合制策略游戏是策略游戏的一种子类型,所有的玩家轮流自己的回合,只有自己的回合,才能够进行操纵。早期的战略由于硬件运算能力有限,在考量游戏乐趣的情况下,多半采取这种型式。

主要分类
战棋类游戏
SLG:角色扮演因素较少,战斗以整体策略为主。
SRPG:角色扮演因素为主,战斗为回合制,通常己方人员较少,特别依靠培养系统铸造的强人。
半即时制
使用行动点数系统,行动点数基于角色行动需要而设定的耗时系统或者行动速度的差异。半即时回合制是按照人物角色的速度来决定行动的先后顺序,速度快的角色可能一个回合可以攻击多次,是回合制游戏的发展趋势。

游戏后台网络通讯

网络通信本身是非常复杂的事情,目前的开发环境已经提供了相对来讲简单得多的编程接口,但是网络程序还是需要处理很多的问题。
两种处理方式:一种是跟游戏服务器耦合带一起,游戏服务器既处理问落接入相关的逻辑,也处理游戏逻辑。一种是把网络通信部分剥离住来,向游戏服务器提供一种以消息为单位的、非阻塞的、有Qos能力的中间服务,游戏服务器看不到网络的细节。
我们选择第二种好处,是基于这样的考虑,首先简化了游戏服务器的处理逻辑,降低了编程的难度。更易于提升后台整体的处理性能,不同部分可以独立的优化,因为它可以不断的优化和在不同项目里面去继承的。
这是非常简单的一张示意图,我们会有一个界面,在客户端和服务器端给它不同的接口,会有不同的形式,看不到网络的问题,而且都是非阻塞的形式,面向消息的服务,类似于有保障的、可持续的服务。

游戏通信协议

协议分两大类:文本协议和二进制协议,这是两个非常典型的例子,一是UDP,非常高效。一是文本协议,都是文字。文本协议直观,版本兼容性好,但是效率低。其实对于游戏来讲,我们最好能够做到不同的版本都可以玩,不强调所有的客户端都去升级,这对运营商有非常强的周期,这对运营商来说有很大的挑战。版本兼容性问题其实是我们在通信协议的时候,都需要重点考虑的。二进制协议效率高,但是不直观,版本兼容性处理相对复杂。
Web相关的游戏根据与浏览器交互的方式可能采用文本协议,基于效率原因,C/S类型的游戏通常采用二进制协议。

游戏服务器是时钟消息和网络消息驱动的,大部分代码都是接受消息,或者接纳,其实有很多的解码、编码、代码占了相当大的比例。我们可以把协议做一个区分,就变成一个消息的协议描述,然后生成工具,然后得到网络协议处理代码。

从游戏来讲,所有的在线游戏通常使用数据库来存储用户数据。通常MMO使用关系型数据库来存储数据,后面主要针对MMO进行存储方式的讨论。会有两种方式:一种是把游戏的每一个数据对象的属性看成一个单独字段,遵循RDBMS的要求来设计数据库表和索引,尽量符合3NF。以MMO为例,有帐号表、角色基本信息表、物品表、装备表等等,这是一种方式。
还有一种方式更具体,角色的列表类数据尽量采用blob来存储而不是另一个表。原则是这些列表数据只被角色自身所拥有,就是这个玩家所拥有,其他玩家不会拥有个数据,它的生命周期跟玩家是一致的,不存在其他的交叉拥有情况,技能、物品、装备、任务、好友等等都属于这种情况。
优点是存储表结构简单,通常几张表就可以玩一个游戏,不超过10个。存取交互简单,角色登录或者推出时通常只需要存取一到二条记录。同一个角色的数据易于保持一致,易于多版本数据共存。我们把这些数据存到数据库的时候,会把编码存到数据库里面。所以在数据库里面做完的数据可能会不一样,不过不会影响,它会共存。
这种方式也会有缺点,数据维护工具、客服工具实现相对复杂,需要提供特殊的API来操作数据。如果手上工具是通用的,可能比以前要直白一点。某些类型的统计相对要麻烦一些,有些常用的数据,比如说角色的等级,在这方面可以用一些方式解决你的问题。
举个例子,在MMOG这块,存储角色的概要信息,包括名字、基本属性等,用于显示角色列表和防止重名。还有存储角色的详细信息,存储帐号的仓库信息,存储公会的信息。
新趋势的影响,就是Nosql数据库,性能高,存在好的开源实现,游戏的数据访问多为唯一键访问,很少复杂的Query,符合Nosql数据库的特点。后面在游戏应用上,可能也会涉及到。

网络同步

网络同步面临的主要问题:第一如何减少网络波动对同步的影响;第二如何减少外挂对同步的破坏。如果没有外挂,网络几大问题没有服务器去运行。这两个问题单独都好解决,但是在一起比较难解决。我们解决这两个问题,会遵循几个原则:第一网络条件好的玩家获得好的体验;第二网络条件差的玩家尽可能获得好一些的体验,但不能拖累其他玩家的体验;第三外挂不能在网络同步方面获得持续的好处。对外挂方面,玩一个游戏是一个人,或者说非人类不清楚,所以说外挂不能在网络同步获得持续的好处。
为了解决问题我们有一些基本方法:首先要探测玩家的网络质量;第二在玩家机器与服务器之间进行时钟同步;第三基于游戏特点,设计合理的同步机制。像竞技类的游戏,都是根据它的某些特点决定的,这是需要我们权衡考虑的。这里强调一点,在外挂获得好处,跟玩家体验时间做一个折中,你要保证外挂持续得到源源不断的好处,这样外挂就会上去。对于探测、时钟同步都需要控制好。

TCP / UDP

UDP:用户数据报协议:主要用在实时性要求比较高的以及对质量相对较弱的地方.但是面对现在高质量的线路不会容易丢包,除非是一些拥塞条件下,如流媒体;
TCP:传输控制协议:是面连接的那么运行环境必然要求其可靠性不可丢包,有良好的拥塞控制机制如 http ftp telnet等;

TCPUDP
发送安全送达
接收与建立连接是(三次握手)
数据大小无限制
可靠性可靠
速度慢(三次握手才能完成连接)
应用流媒体

“心跳”机制

1.原理

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保链接的有效性的机制。

发包方:可以是客户端也可以是服务端,看哪边实现方便合理。一般是客户端

在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项(SO_KEEPALIVE)。系统默认是设置的是2小时的心跳频率,探测次数为5次,如果要使用TCP的KeepAlive保活机制,需要手工开启KeepAlive功能并设置合理的KeepAlive参数。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。心跳包一般来说都是在逻辑层发送空的包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。只需要send或者recv一下,如果结果为零,则为掉线。

TCP的KeepAlive保活的缺陷
TCP协议自身先天就有KeepAlive机制,因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功能,另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接.

TCP仍然需要心跳包
在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理,重新连接,这个自然是要由逻辑层根据需求去做了。总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒

   心跳机制的原理很简单:客户端每隔N秒向服务端发送一个心跳消息,服务端收到心跳消息后,回复同样的心跳消息给客户端。如果服务端或客户端在M秒(M>N)内都没有收到包括心跳消息在内的任何消息,即心跳超时,我们就认为目标TCP连接已经断开了。
   由于不同的应用程序对感知TCP掉线的灵敏度不一样,所以,N和M的值就可以设定的不一样。灵敏度要求越高,N和M就要越小;灵敏度要求越低,N和M就可以越大。而要求灵敏度越高,也是有代价的,那就是需要更频繁地发送心跳消息,如果有几千个连接同时频繁地发送心跳消息,那么其所消耗的资源也是不能忽略的。
   当然,网络环境(如延迟的大小)的好坏,也对会对N和M的值的设定产生影响,比如,网络延迟较大,那么N与M之间的差值也应该越大(比如,M是N的3倍)。否则,可能会产生误判 -- 即TCP连接没有断开,只是因为网络延迟大才及时没收到心跳消息,我们却认为连接已经断开了。
2.心跳检测步骤

1.客户端每隔一个时间间隔发生一个探测包给服务器
2.客户端发包时启动一个超时定时器
3.服务器端接收到检测包,应该回应一个包
4.如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5.如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

3.应用层发送心跳包的技术

由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:
服务器在一个Timer事件中定时向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应,如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用。
缺陷:代码较多,稍显复杂

4.要关闭掉线的TCP连接

无论是普通掉线(立即感知)还是心跳超时掉线(非立即感知),都需要关闭对应的TCP连接以释放系统资源。

5.UDP与"心跳"

由于UDP是无连接的协议,所以,当使用UDP引擎时,几乎肯定是需要配备心跳机制的,使用心跳消息确认客户端还在线,以保证服务端不会过早释放对应的Session或长期保留已失效的Session.

心跳包和轮询的区别

心跳包和轮询看起来类似, 都是客户端主动联系服务器, 但是区别很大:

轮询是为了获取数据, 而心跳是为了保活TCP连接。
轮询得越频繁, 获取数据就越及时, 心跳的频繁与否和数据是否及时没有直接关系
轮询比心跳能耗更高, 因为一次轮询需要经过TCP三次握手, 四次挥手, 单次心跳不需要建立和拆除TCP连接.

客户端如何快速感知自己掉线?

在某些客户端电脑上,比如拔掉网线,或断开wifi,程序可能需要几秒到几分钟才能感知到自己掉线,不同的电脑这个感受的时间不一样。那么如何才能让客户端尽可能快地得到掉线通知了?
可以利用socket写超时的机制,像下面这样做:
(1)将Socket发送缓冲区的大小设置为0。 对应IRapidPassiveEngine的Advanced属性的SocketSendBuffSize属性。
(2)设置写超时为一个较小的值,如30秒。 对应IRapidPassiveEngine的Advanced属性的WriteTimeoutInSecs属性。
这样,结合上面的心跳发送机制(如每隔5秒发送一个心跳),则当网络断开后,在发送心跳消息,最多再过30秒,程序就会得到掉线通知了。

HOW

应用层心跳的确是检测连接有效性,双方是否存活的最佳实践,那么剩下的问题就是怎么实现。

最简单粗暴做法当然是定时心跳,如每隔 30 秒心跳一次,15 秒内没有收到心跳回包则认为当前连接已失效,断开连接并进行重连。这种做法最直接,实现也简单。唯一的问题是比较耗电和耗流量。以一个协议包 5 个字节计算,一天收发 2880 个心跳包,一个月就是 5 * 2 * 2880 * 30 = 0.8 M 的流量,如果手机上多装几个 IM 软件,每个月光心跳就好几兆流量没了,更不用说频繁的心跳带来的电量损耗。

既然频繁心跳会带来耗电和耗流量的弊端,改进的方向自然是减少心跳频率,但也不能过于影响连接检测的实时性。基于这个需求,一般可以将心跳间隔根据程序状态进行调整,当程序在后台时(这里主要考虑安卓),尽量拉长心跳间隔,5 分钟,甚至 10 分钟都可以。而当 App 在前台时则按照原来规则操作。连接可靠性的判断也可以放宽,避免一次心跳超时就认为连接无效的情况,使用错误积累,只在心跳超时 n 次后才判定当前连接不可用。当然还有一些小 trick 比如从收到的最后一个指令包进行心跳包周期计时而不是固定时间,这样也能够一定程度减少心跳次数。

参考:

棋牌游戏服务器的架构设计需要注意哪些点 2017-04
TCP/UDP区别&&心跳包机制,2017-05
心跳机制,2014-07
请教用NIO框架MINA开发IM时的心跳和会话超时问题,2016-12
移动 IM 开发之心跳,2016-04
HTML5 QQ斗地主诞生记,无线游戏产品部

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值