作为一个资深的游戏玩家,当然玩过英雄联盟和王者荣耀,但是为什么进入游戏的第一件事是选择服务器呢?为什么英雄联盟不同区不可以一起游戏但是王者荣耀却可以呢?这些问题我相信大家都有过,我这一篇文章就将从服务器架构、通信方式和同步方案三个方面来阐述MOBA游戏的通信原理。


服务器架构

随着图形界面的出现,游戏更多的采用图形界面与用户交互。此时随着在线人数的增加和游戏数据的增加,服务器变得不抗重负。于是就有了分服模型。分服模型结构如下:

分服模型是游戏服务器中最典型,也是历久最悠久的模型。在早期服务器的承载量达到上限的时候,游戏开发者就通过架设更多的服务器来解决。这样提供了很多个游戏的“平行世界”,让游戏中的人人之间的比较,产生了更多的空间。其特征是游戏服务器是一个个单独的世界。每个服务器的帐号是独立的,每台服务器用户的状态都是不一样的,一个服就是一个世界,大家各不牵扯。

后来游戏玩家呼吁要跨服打架,于是就出现了跨服战,再加上随着游戏的运行,单个服务器的游戏活跃玩家越来越少,所以后期就有了服务器的合并以及迁移,慢慢的以服务器的开放、合并形成了一套成熟的运营手段。目前多数游戏还采用分服的结构来架设服务器,多数页游还是采用这种模式。

分服虽然可以解决服务器扩展的瓶颈,但单台服务器在以前单线程的方式来运行,没办法充分利用服务器资源,于是又演变出了以下2种线程模型。

  • 异步-多线程:基于每个场景(或者房间),分配一个线程。每个场景的玩家同属于一个线程。游戏的场景是固定的,不会很多,如此线程的数量可以保证不会不断增大。每个场景线程,同样采用tick轮询的方式,来定时更新该场景内的(对象状态,刷新地图,刷新NPC)数据状态。玩家如果跨场景的话,就采用投递和通知的方式,告知两个场景线程,以此更新两个场景的玩家数据。

  • 多进程:由于单进程架构下,总会存在承载量的极限,越是复杂的游戏,其单进程承载量就越低,因此一定要突破进程的限制,才能支撑更复杂的游戏。多进程系统的其他一些好处:能够利用上多核CPU能力、更容易进行容灾处理。

MOBA类玩法和RPG有很大的不同,在于其在线广播单元的不确定性和广播数量很小。而且需要匹配一台房间服务器让少数人进入一个服务器。

这一类游戏最重要的是其“游戏大厅”的承载量,每个“游戏房间”受逻辑所限,需要维持和广播的玩家数据是有限的,但是“游戏大厅”需要维持相当高的在线用户数,所以一般来说,这种游戏还是需要做“分服”的。典型的游戏就是《王者荣耀》这一类游戏了。而“游戏大厅”里面最有挑战性的任务,就是“自动匹配”玩家进入一个“游戏房间”,这需要对所有在线玩家做搜索和过滤。

玩家先登录“大厅服务器”,然后选择组队游戏的功能,服务器会通知参与的所有游戏客户端,新开一条连接到房间服务器上,这样所有参与的用户就能在房间服务器里进行游戏交互了。

开头我说到为什么王者荣耀可以跨服匹配但是英雄联盟不可以,我猜想的就是在这个匹配服务器的访问范围不同,英雄联盟应该是大区服务器集群与另一大区服务器集群之前完全没有阻断,无法进行数据传输,但是王者荣耀可能matchServer是能够访问其他大区服务器集群的,这样即使不在同一大区,也能一起匹配游戏。而且玩过王者荣耀的也应该了解,就是iOS游戏用户和安卓游戏用户是不能相互访问资料的,这也印证了部分大区登录服务器(loginServer)集群不能相互访问的起因。

通信方式

说到通信方式,一般会有http和socket 两种方式,但http底层也是采用socket,只是每次通信完成以后都会断开,这种方式对于需要频繁交互的双方来说,显得效率太低了,所以一般实时要求高的游戏都是采用socket方式来通信。

可是sokect通信,又分为两种:TCP vs UDP,具体是采用那种socket类型,需要具体来看游戏游戏类型。以下是两种类型的优劣:

从上面的对比中,我们可以会发现,关于socket,我们想做的事情,tcp都帮我们做了,我们只需要建立链接,然后像读写文件一样读写就可以了。而udp需要我们自己设计一切。看到这一切,可能第一感觉就是采用tcp而非udp,那么真实情况是如此么?基于游戏的业务以及场景不同,我可以明确的告诉你,王者荣耀是采用udp的,包括腾讯多数长链接手游都是采用udp,这是为何?

  1. tcp保证数据可靠性是有代价的

    tcp能够保证数据包的可靠性和有序,这一切都帮你封装好了。TCP发送一个数据包,等待一段时间,直到检测到数据包丢失了,如果没有接收到它的ACK,接下来就重新发送丢失的数据包到目标计算机。重复的数据包将被丢弃在接收端,乱序的数据包将被重新排序。以此来保证数据包的可靠性和有序性。

    但为了保证可靠和有序,就要保证TCP无论什么情况,只要数据包出错,就必须等待数据包的重发。这是什么意思呐,就是说,即使最新的数据已经到达,但还是不能访问这些数据包,新到的数据会被放在一个队列中,需要等待丢失的包重新发过来之后,所有数据没有丢失才可以访问。

    如此,如果遇到网络环境太差或者不稳定,比如说国内的移动网络,或者是遭遇到了网络阻塞,出现一个数据包丢失,所有事情都需要停下来等待这个数据包重发。客户端会出现等待接收数据,玩家操作会出现卡顿以及响应不及时的现象。

  2. udp的可靠性—DIY手动组装

    从上面我们可以知道udp主要在可靠性上主要是不能保证数据包的顺序,比如第100个收到的数据包并不一定是第100个发出的数据包,同时也无法保证不丢包,期间有一个包丢失,udp本是也不会去校检。如果这两个问题解决了,udp的大部分可靠性问题也就解决了。

    具体的方案我们这一篇就不在细说,大体上是如此来解决:

    • 为每个数据包增加序列号,每发一次包,增加本地序号。

    • 每个数据包增加一段位域,用来容纳多个确认符。确认字符多少个,跟进应用的发包速率来觉得,速率越高,确认字符的数量也相应越多。

    • 每次收到包,把收到的包上序列号变为确认字符,发送包的时候带上这些确认字符。

    • 如果从确认字符里面发现某个数据包有丢失,把它留给应用程序来编写一个包含丢失数据的新的数据包,必要的话,这个包还会用一个新的序列号发送。

    • 针对多次收到同一包的时候可以放弃它

同步方案

游戏中常见的同步方案,有状态同步和帧同步,一般大型的MMOARPG都是采用的是状态同步,比如魔兽世界,状态同步采用C/S架构,所有的状态由服务器来控制,安全性比较高,但是流量比较大。帧同步采用的是囚徒模式,所有c端强制采用一个逻辑帧率,从而保证输出一致,其特点是流量小,安全性比较差。

王者荣耀采用的就是帧同步,那么具体帧同步是什么,如何实现的?

可以把一个游戏看成一个巨大的状态机,所有的参与者都采用同一个逻辑帧率来不断的向前推进。

图中是A、B、C三个玩家的时间轴,这个时间轴不是电脑上的本地时间,而是A、B、C联机时定义的一个时间轴。虚线分隔出来时间片称为turn,可以理解成一帧。箭头表示该玩家将自己的操作指令广播给其他玩家。

我们把一盘游戏看成一个大型的状态机,因为大家玩的是同一款的游戏,因此F是相同的,初始状态S0也是相同的。在第一个turn结束时,所有玩家都接收到了完全一样的输入I,注意这里的I不是一个值,而是包含了当前游戏中所有玩家的操作指令集合。t1时刻所有玩家的电脑自行计算结果。由于F、S0和I是固定的,所以每个玩家电脑上计算出的下一个状态S1一定是相同的。

  • 我们把游戏的前进分为一帧帧,这里的帧和游戏的渲染帧率并不是一个,只是借鉴了帧的概念,自定义的帧,我们称为turn。游戏的过程就是每一个turn不断向前推进,每一个玩家的turn推进速度一致。

  • 每一帧只有当服务器集齐了所有玩家的操作指令,也就是输入确定了之后,才可以进行计算,进入下一个turn,否则就要等待最慢的玩家。之后再广播给所有的玩家。如此才能保证帧一致。

  • Lockstep的游戏是严格按照turn向前推进的,如果有人延迟比较高,其他玩家必须等待该玩家跟上之后再继续计算,不存在某个玩家领先或落后其他玩家若干个turn的情况。使用Lockstep同步机制的游戏中,每个玩家的延迟都等于延迟最高的那个人。

  • 由于大家的turn一致,以及输入固定,所以每一步所有客户端的计算结果都一致的。

可以明显的知道,这种囚徒模式的帧同步,在第二帧的时候,因为玩家1有延迟,而导致第二帧的同步时间发生延迟,从而导致所有玩家都在等待,出现卡顿现象。

为了解决这个问题可以用乐观锁。

囚徒模式的帧同步,有一个致命的缺陷就是,若联网的玩家有一个网速慢了,势必会影响其他玩家的体验,因为服务器要等待所有输入达到之后再同步到所有的c端。另外如果中途有人掉线了,游戏就会无法继续或者掉线玩家无法重连,因为在严格的帧同步的情况下,中途加入游戏是从技术上来讲是非常困难的。因为你重新进来之后,你的初始状态和大家不一致,而且你的状态信息都是丢失状态的,比如,你的等级,随机种子,角色的属性信息等。 比如玩过早期的冰封王座都知道,一旦掉线基本这局就废了,需要重开,至于为何没有卡顿的现象,因为那时都是解决方案都是采用局域网的方式,所以基本是没有延迟问题的。

后期为了解决这个问题,如今包括王者荣耀,服务器会保存玩家当场游戏的游戏指令以及状态信息,在玩家断线重连的时候,能够恢复到断线前的状态。不过这个还是无法解决帧同步的问题,因为严格的帧同步,是要等到所有玩家都输入之后,再去通知广播client更新,如果A服务器一直没有输入同步过来,大家是要等着的,那么如何解决这个问题?

采用“定时不等待”的乐观方式在每次Interval时钟发生时固定将操作广播给所有用户,不依赖具体每个玩家是否有操作更新。如此帧率的时钟在由服务器控制,当客户端有操作的时候及时的发送服务器,然后服务端每秒钟20-50次向所有客户端发送更新消息。服务器不会再等到搜集完所有用户输入再进行下一帧,而是按照固定频率来同步玩家的输入信息到每一个c端,如果有玩家网络延迟,服务器的帧步进是不会等待的,比如上图中,在第二帧的时候,玩家A的网速慢,那么他这个时候,会被网速快的玩家给秒了(其他游戏也差不多)。但是网速慢的玩家不会卡到快的玩家,只会感觉自己操作延迟而已。

总结

通过上述分析,简要总结MOBA游戏的关键通信技术有以下三点:1.房间分服机制;2.数据包通过定制化高而且同样能可靠的UDP协议传送;3.为了解决玩家数据同步的机制同时又提高游戏体验,采用了乐观锁的帧同步技术。

Last modification:April 7th, 2020 at 09:32 pm