浅谈手游程序基本坑
当下国内的游戏公司应该都一定程度上参和到手游市场了的吧?很多的游戏开发人员都是之前页游过来的,在程序设计和功能实现上都延续了页游的那一套思路方法,原则上来是对功能实现上并不存在任何的问题。但是细细分析下里面是存在着一些点可以被优化或者说直接是坑。
前奏
我个人经历过三个手游项目,两个卡牌一个MMO,接下来提出来的点可能是会傻逼。但是我明确的知道很多项目都是比较随意的去对待这些问题的,所以才有了我一系列的小文章来论述这些问题。希望大家在开新项目的时候可以尽可能的避开这些底层设计控制的坑。
坑
顾名思义手机游戏,绝大多数的情况下都是运行在手机环境下的。手机对比电脑不足之处实在是太多。这边就围绕着接下来的问题提出几个关注点:
- 手机的电量是宝贵的
- 手机的发热量是要被控制的
- 手机的流量是宝贵的
- 手机网络的延迟是不可控的
- 手机网络是不稳定的
断线重连(客户端)
因为手机网络的不稳定性,导致手机游戏的频繁的断开、重连网络是非常常见的现象。在页游时代这个行为是一个F5(刷新)就搞定的事情,但是在手机时代这个方案明显行不通。所以我们有了下面这个话题:
在开始这个话题之前我用wireshark工具抓包了如下几个游戏并简单观察其机制(不一定是对的)
- 《皇室战争》 tcp+udp协议混合使用,主界面的操作流程是由tcp socket来控制传输的,战斗过程中是udp协议进行数据的传输的。在网络断开的情况下就是重新走登录流程。
- 《王者荣耀》 跟《皇室战争》的传输模式基本上是一样的也是tcp和udp联合使用。
- 《暗黑破坏神2》tcp协议,因为当下用的协议就是tcp所以着重研究了该游戏的网络重连。
以打副本为例子:
- 如果是进入游戏后自己主动断开网络,可以继续参与游戏的战斗,直到副本结算时会弹出网络异常请重新连接,点击重连会对数据进行重发,过了5秒后如果还没有成功又弹出该界面。如果这个时候网络连接上去是可以按照正常的流程参与结算的。
- 在无感知的情况下断开前端网络(手机不断网,把路由器断网):点击打副本进不去。如果在副本里面的话可以继续进行战斗知道结算。同样的会出现第1种那样的情况。
- 前面2中情况说的都是服务器已经断开连接的情况,从抓包分析上看,服务器应该是3次心跳都没有收到的情况下回主动断开与前端的socket连接。 如果在这三次心跳时间内重现网络连接上去,其实前后端都认为没有发生过网络异常。可以按照正常的流程进行结算。
- 如果网络连接失败的情况下,下次再网络状况良好的地方还可以正常的结算。
.....
总结:
内容表现非常的多,我单测试这个就用了一个上午的时间,这边也不浪费大家的时间直接来看结论和合理的设计:
- 客户端必须做对出去包的缓存处理。 以备用网络连接断开点击重连时请求数据还存在。
- 在网络连接过程中,如果socket一直没有断开(前端的没有断),前端仍然可以往socket发送数据,在第一次点击重连时可以再原socket上发送数据,如果第二次点击重连时就应该手动把旧的socket断开,新建立一条根服务端的socket并把缓存数据发送出去。
- 不管怎么什么情况下,如果手机主动得到了socket断开的回调,就应该进入自动重连的模式,如果有重要包应该直接弹窗,否则的话就后台自己重连,重连后立即把缓存的数据发送出去。
- 服务端一般会在多次心跳未收到的情况下主动断开socket,以免造成过多的“僵尸”玩家存在。
- 服务端做数据缓存操作,因为有可能前端发过来多个请求结算,这个时候第二个包肯定是结算失败的。所以需要从缓存中把上一次结算的时间返回给前端。
网络中断(服务端)
分析下网络中断的状态和行为可以分为如下四种:
- 玩家主动退出游戏,网络断开
- 玩家kill掉游戏进程,网络断开
- 玩家网络状况糟糕频繁的掉线重连
- 接电话、钻地铁、回复消息等游戏进程进入后台的网络断开
从四种断网方式中可以断定出玩家的接下来行为:
- 前两种行为如果要继续游戏必须重新登录游戏
- 后两种行为切回游戏界面就可以继续游戏
对于服务器来说,除了第一种状态下服务器知道玩家是下线情况外,其他的三种状态服务器是无法区分的,因为对于服务器来说都没有主动收到前端的fin包(网络中断)的包,这也就是为什么基本上所有的游戏都有心跳包的原因,其实就是用来界定网络是否还存在的情况。
在传统的页游中,主要发生了断线行为都是直接把数据存档,内存数据消除。听起来好像没有问题,但是回去分析玩家行为时会发现,其实后两种行为在切回游戏界面时是可以继续进行游戏的,这个时候如果把数据dump有两种情况:
- 内存数据直接丢失,可能造成玩家接下来的数据就异常,比如说在做刷怪任务时,可能中途有数据直接传到服务器来保存,但是因为只是结算的时候用,所以也不会设计到入库,这样的情况就比较糟糕了,玩家重新回来的时候数据就丢了,但是对于前端来说还是正常的...
- 频繁的dump数据到磁盘本身是比较耗费性能,而且也是无意义的。比如说玩家在座地铁,可能一条线上断网20次,那就是20次的从磁盘加载数据到内存,再从内存dump数据到磁盘。
其他的细节就不追究还是直接来看结论:
-
状态
对于玩家进程行为加多一种状态标记:dump,所以玩家最起码有如下三种状态:(在实际的项目中往往是远大于这三种状态的)- offline 离线状态
- online 在线状态
- dump 数据缓存状态(玩家下线了,但是数据还留在缓存中的状态如果玩家重新登录的话只需要把连接重新指向下就好)
-
临时数据
手游情况比较特殊,基本上可以说所有的数据都必须要入库处理操作好点。最容易联想到的现象就是别人早上打开游戏,然后home键到后台,晚上再继续游戏,不入库的话根本掌握不住这个时间点。临时缓存数据也需要入库操作,一般会加上时效性。
-
服务器的重连
如果仅仅是通过socket来进行标记,服务器是无法判断是否是重连。也就是说走的路线都是重登陆了。所以这个时候前端跟服务器重连时必须给服务器带过里标记: 重连、登录标记。服务器根据这两个标记刷新数据、推送数据。(比如重连不需要额外推送红点和数据,重登陆的话可能需要把上一把的临时数据的存档清楚)
-
前后端数据同步
在后面2中情况下(重新切回主界面),如果涉及到跨天或者活动相关的推送时是会比较尴尬的。比如说:跨天刷新数据,原本是要把缓存数据都清楚然后重新获取,但是因为进入了后台得不到cpu使用权就丢失了跨天的回调,导致久数据无法刷新;活动推送如果在登录状态下回直接推送、重登陆也会出发推送,但是对于重连一般不会触发推送这个点也是比较尴尬的。所以也有一些做法是做成进入后台xx小时,同时重连的时候跟打开app的时间不在同一天都会触发游戏重新登录的(kill app 重连)。
流量
流量是人民币,如果不珍惜也会对留存造成一定的影响哦。
因为手游开发人员普遍来源于页游,页游这块网络的不需要太多的考虑的。所以前端的缓存做的是相对来说比较少,都是直接跟服务器请求数据的,但是再手游情况下听起来就觉得不怎么妥....
废话不多说直接说结论:
-
合并数据包
对数据进行合并操作,在网络出口处,如果在一个瞬间又多个包同时推送给同一个玩家需要对包进行合并操作。 一个tcp的包头最小20字节呀(很大),所以能合并就合并下吧。
-
数据前端缓存
在协议商定时额外的带多一个标记位,用于指定是获取更新数据呢还是获取全部数据。(建议本地有缓存的情况下都做更新数据处理),对于切tab操作(手残党最喜欢做),尽可能的避免反复请求。 切记:别做成每次打开界面都跟服务器拿数据。
-
分批按量发送
数据分批分量来推送,避免推送大数据,最典型的代表就是排行榜、拍卖行。
-
数据扁平化设计
例如把一个int数据拆出来表示多个字段的意义。
-
数据压缩
对于大于xx字节的数据包进行压缩。
电量和发热量
当前市场上很多游戏插着充电线电量都还是在减少的。也有游戏玩一会手机就成为了暖手炉的。
这两个点影响流失的比率还是比较高的。我就卸载很多这样的游戏。想想你的手机原本充满电用一天的,但是现在玩了一会游戏就要自动关机了。想想你用你的双手握着一个暖炉的时候是怎么样感觉....
那么怎么来降低这两个点呢:(因为我主要是后端,这个点可能点的比较虚)
- 减少cpu的使用率,可以从内存换cpu的角度上去衡量下。
- 减少GPU的使用,前端gpu的渲染非常耗电。
- 减少网络应答,这个对比前面两个可以小到忽略。因为网络也是io对cpu也是比较的耗的。
总结
这里的结论可能并不一定都是最好的,也可能考虑到的点还没有那么全面。欢迎前来拍砖...
(本文的结论基本上都是经过线上测试验证过的,并不是自己臆想天开得来的)