3

ouster开发笔记第二周

 3 years ago
source link: https://www.zenlife.tk/ouster-dev2.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ouster开发笔记第二周

2014-04-14

因为要计划离开上海回家,停留武汉几天。期间收拾东西,联系亲友,关注一下公司对自己游戏的善后处理。分心了,这周就是真的没做什么事情了,项目完全没有推进。

封包那边,服务端Go语言毫无问题,痛苦的根源就在客户端C语言这一边,即使用了msgpack-c的库,也只能解析出一个msgpack_object出来。但是C语言肯定要转成struct 才好用。因为没有类型信息,Unmarshal很难做,用void*来表示是不行的,代码生成也没法处理递归调用。最后做了折衷,代码生成时不支持结构体中嵌套结构体。

言归正传。这篇文章就主要整理一下同步问题吧。

记得云风大神的开发笔记系列中提过的,这一块不解决好,到后期项目忙起来,就没机会做好了。

以角色移动为例,从客户端发送一个消息包出去,到达服务端,服务端再转发到其它客户端,这中间是有两个网络传输的时差的。同步就是如何让不同客户端之间,客户端与服务端这间,表现相对的一致。在网上查了些资料,相关东西不多,讲得也不细。有几类做法。一类客户端等待服务端的确认包后才能执行移动。另一类客户端可以自己移动,根据服务端发过来的真实坐标,进行补偿。补偿比如对移动速度进行调整,或者加快减慢帧的播放。

darkeden的做法是最简单的。充分信任客户端,角色移动之后,客户端每隔一定的时间向服务端发送一个PCMove的包,含有角色的坐标信息。服务端收到包之后,几乎没做什么校验,立即就向客户端发回确认,并向其它玩家广播这个移动包。收到服务端发回的确认包之后,客户端那边角色才会继续移动。如果网速卡,收不到服务端发回的移动确认,角色就停顿在某一帧了。而如果发生粘包,同一次中收到服务器那边发回来的确认过多,则出来角色以很快的速度迅速播放完移动的动画。

以前玩这个游戏时看到过一个场景,跟朋友组队在网吧玩,我自己机器上看到角色还在往前走,而朋友机器上看到的则是我被怪物定身了殴打。他那边显示是服务器的真实信息,而我看到的是假的。几秒之后我这边角色被强制拉回原处,显示被怪打死了。这个现象说明,客户端移动是先于服务器确认的:就是客户端走几步,服务端确认客户端走的这几步,客户端收到确认后,再继续走几步这种流程。

darkeden是2003年左右的游戏了,由于充分信任客户端,外挂满天飞。角色移动的坐标不是在服务端做的,而是在客户端做的。而且服务端收到客户端发送的坐标后也没做严格的校验,只是把一个客户端发来的包广播给他周围玩家。导致可以看到别人用外挂,一秒之前还在你看不到的远处,下一秒就飞到你前面来了。原理就是服务器信任并 广播了外挂伪造的移动包坐标信息。除了移动,这个游戏包括技能方面也没处理好,有些无冷却CD之类的技能很容易被外挂利用做加速,所以上面枪那个职业开挂那叫一个变态啊。水魔灵也是的冰矛也是可以开挂,我玩游戏时看到过的有人用加速。总之这个故事告诉我,一定不能信任客户端,必须放到服务端做!

不过也不绝对,跟网友交流,他们做一个山寨COC,寻路逻辑是在客户端做的,服务端只校验。一方面是节省性能的考虑,另一方面是手机的网络环境跟端游差远了,会卡。扯远了...

网上看到的,写WOW的位置同步的做法。WOW方向控制移动的,跟这种2d的坐标控制移动略有不同。客户端发送一个移动包过去,包中会带有移动的方向信息,服务端收到包后开始执行移动。在收到停止移动或者改变移动方向的包之前,服务器那这会一直保持方向执行移动。服务端每隔一定的时间会向客户端发送一个包,对客户端的当前移动进行确认。如果客户端没有收到进一步的移动确认,就停在原处。动画还是播放的,那么显示上就是人物在原地跑啊跑的。服务端每送过来一个包,客户端就允许往前走一点点距离,如果没有下一个包到达了,客户端就像在原处。

如果改变方向了,那么客户端这边会发送一个方向改变的移动包,那么服务端就会调整执行新的移动方向。服务端主导的移动,就会出现跳崖之类的现象:网络卡的情况下,客户端发出向前走了,服务端收到包并执行向前走的动作,由于网络延时客户端没及时收到及时反馈,于是玩家没有下达停止移动的指令,然后就掉下去了。事实上我没玩过WOW,有些东西说的可能不一定对。

由服务端主导移动做法是对的,不应该信任客户端。客户端那边只能决定往哪个方向走,是走还是停,然后向服务端发送命令。

看到网上有人提到了粘包的问题。服务器那边对于一次移动的包并没立即发给客户端,而是与下一次的移动包一起发送了。客户端那边就遇到一次没收到移动确认,而另 一次却同时来了两个移动。这是别人的实战经验,我暂时还没有太多思考过,到遇上再看。作为常识,肯定要关掉Nagle算法的。

上面都是写别人的做法和遇到的一些问题,下面说自己的想法。

客户端始终不知道当前发生了什么,只知道曾经发生过什么,还不知道准确是什么时间发生的。因为客户端收到服务端发来的消息包时,已经经历过一个网络传输时间,而且经历的这个时间由于网络波动或者粘包等原因,还是不确定的。

每个客户端都想告诉服务端它要干什么,但是服务端才能决定实际发生了什么。比如玩家想一个技能甩上去把对方秒了。服务端却告诉他,技能甩上去Miss了,反而被对方 一个技能秒了。即使最简单的,想从A点走到B点,也不能保证你在A到B点移动过程中被技能定身了。

真实与流畅的折衷是必须要的,最反映真实的做法就是服务端告诉客户端发生过什么后,客户端才能发生。但是这代表每次都经过了两次的网络传输,影响流畅性上的体验。最反映流畅的做法就是客户端接受用户输入后,立马执行,不等待服务端的确认。

  • 移动都在服务端做,完全不信任客户端。

客户端只能给向服务端发送“我想去坐标(XX XX)”。服务端收到请求后,在自己这边执行移动。并每隔一段时间向客户端发送一个角色当前坐标信息。

  • 伪同步,客户端只拿服务端坐标作为参考值。

我想做一个预测同步,客户端收到服务端发来的包才能执行移动,但也不是先移动一小段,在期间收到服务端发来包才移动下一小段,否则等待。

服务端发来的包中会包含当前坐标和目标坐标,客户端收到包就开始移动了,根据速度,坐标方向执行。只要客户端与服务端坐标相差不超过一个范围,客户端都是可以继续移动的,以保证流畅。这是一个伪同步,期间如果网络正常,客户会不停地收到服务端发来的坐标值,根据服务端发来的真实位置进行补偿以达到同步。

前面已经说过是伪同步了,补偿就是来做尽量的同步。做精确对时会是一个坑死人不偿命的方式。客户端收到服务器发来的坐标的那一刻,代表着一个网络传输时间之前,服务器上的角色的坐标是在XX。客户端可以通过这个参考值,估算自己与服务端真实位置偏移了多少。只要偏移值不大到一定程度,都不会理会,客户端按照自己的节奏播放移动动画。如果偏移值太大了,则进行补偿。

客户端可以记录以下信息,上次包到达时时间,真实坐标,目标坐标,客户端坐标。这次包到达时时间,真实坐标,目标坐标,客户端坐标。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK