从动物森友会聊主机游戏联机机制

最近在玩动物森友会的时候时常会遇到一些迷之联机问题,在网上一番搜索,发现你们的答案都趋于用玄学来解释,因而便有了兴致想在原理上搞懂这些问题产生的根源以及动森这款游戏的一些联机设定背后的技术缘由。数据库

事先声明,本人并不从事游戏行业亦非主机游戏长期玩家,若有纰漏或其余角度的补充,欢迎在评论区告知。服务器

游戏是如何同步的

咱们首先来看看通常游戏是如何来作同步的。网络

想象两个独立房间里分别有甲、乙两个玩家,他们要远程下一局象棋。他们每下一步前都须要先获知到当前棋盘的状况,此时可以有两种实现方式。架构

第一种叫作锁步同步,原理是玩家每操做一步就通知给另一个玩家,彼此同步当前的操做序列,经过这些有时序的操做,就可以计算出当前棋局的状态。但它不容许中间丢失任何一步的信息,不然就会出现很是大的计算误差。app

第二种叫作状态同步,顾名思义是玩家每操做一步,就同步整个棋盘的状态。这种方式能够容忍中间某些状态丢失,最终获得的状态依旧仍是一致的。分布式

在实际实践中,针对那种玩家操做很是高频的游戏会更多使用锁步同步,例如王者荣耀。而对于那些卡牌类游戏更偏向于直接用状态同步。测试

游戏是如何联机的

通讯架构

不管是上述哪一种同步方式,咱们都须要经过网络在多个主机间交换数据。咱们如今将场景转换成甲、乙、丙三我的一块儿下跳棋。为保证三我的最终获得的游戏状态都是一致的,咱们每每须要有一台 Host 主机来做为权威主机,其余主机只能经过权威主机下发的数据(状态/操做序列)来更新本身本地的游戏数据。动画

在这里咱们假设甲来作「Host」,乙、丙每操做一步,都须要先发送给甲确认,无误后再发送该操做被确认的信息给乙、丙,乙、丙此时才可以认为操做成功并将画面更新到最新的状态。甲主机上在任意时刻都存有当前游戏的真正状态,其余主机只是在 follow 甲主机的状态以更新本身的游戏画面。spa

在上述模式下,因为甲主机既要做为游戏主机,又要做为状态同步的主机,当联机用户数一多,甲主机就会不堪重负,出现所谓的「炸机/炸岛」现象。另外,这种模式会须要甲主机一直存活,只能做为短期内的伺服方案。因此有些游戏会引入一个外部自建/官方的服务器来承担这个状态同步的功能,例如个人世界。但究其原理是如出一辙的。.net

NAT 穿透

在了解完上面的基础知识后,咱们可以发现,在不考虑外部服务器的状况下,咱们会对玩家主机间的网络有如下几点要求:

  1. 甲可以向乙、丙发送数据
  2. 乙、丙可以向甲发送数据
  3. 乙、丙之间不须要有网络联通保障

虽然上述要求看起来很容易,可是因为如今网络运营商都会不一样程度地使用 NAT 技术,因此致使要让两台家用主机创建双向通讯并非一件很是容易的事情。

家用网络通常有四种不一样的 NAT 类型:

Full-cone NAT

  • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
  • Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort.

(Address)-restricted-cone NAT

  • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
  • An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to hAddr:any. "Any" means the port number doesn't matter.

Port-restricted-cone NAT

  • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
  • An external host (hAddr:hPort) can send packets to iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to hAddr:hPort.

Symmetric NAT

  • Each request from the same internal IP address and port to a specific destination IP address and port is mapped to a unique external source IP address and port; if the same internal host sends a packet even with the same source address and port but to a different destination, a different mapping is used.
  • Only an external host that receives a packet from an internal host can send a packet back.

上述四种 NAT 类型简单概括就是说:

  1. 前三种 cone 类型的 NAT 都能将内网的 iAddr:iPort 映射到一个固定的外网 eAddr:ePort 上。只有 Symmetric 类型对于同样的 iAddr:iPort 但不一样的目的IP和端口也会被映射到不一样的 eAddr:ePort 上去。
  2. 前三种 cone 类型的 NAT 的区别能够直接从名字中看出来,address-restricted-cone 表示只有本身对外发出过包的 address 有能力向其发送包,port-retricted-cone 的意思是只有本身对外发出过包的 address:port 地址有能力向其发送包。
  3. 第四种 Symmetric 类型对外部返回报文来源的限制是与 port-retricted-cone 一致的。

主机上的网络测试功能可以告诉咱们当前网络的 NAT 类型。Switch 上的 Type A、B、C、D 分别映射到上面四种类型,而 PS4 上则是 Type 1(直连,无 NAT)、2(非 Symmetric NAT)、3(Symmetric NAT)。为方便下文叙述,咱们用 Switch 的 ABCD 指代上述四种网络类型。

理解了四种 NAT 类型各自的限制,咱们就可以经过推导判断出,哪两个 NAT 类型的网络是不可能创建双向通讯的,而再也不须要去人肉尝试。这里咱们分别举例来介绍不一样 NAT 类型下的不一样状况,甲做为 Host 主机,而且咱们有一个外部的联机服务能够获取到甲乙的外网 IP 信息。所谓的联机服务是一个第三方服务器,甲乙都能经过访问它去搜索到对方的外网IP和端口号信息,同时也能够将本身的外网IP和端口号信息给注册到上面。因此这里甲、乙可以在通讯前就知道彼此的通讯地址信息。

若是甲的 NAT 类型是 A :

  • 不管乙的类型是 A/B/C/D,乙都可以直接向甲的 eAddr:port 发送数据,而当甲已经收到乙的数据时,也可以得到到乙的 eAddr2:port2,以及向乙发送数据的资格,从而创建双向通讯。

若是甲的 NAT 类型是 B :

  • 当乙的 NAT 类型是 B/C/D :甲先使用 192.168.1.1:10001 => 1.1.1.1:10002(甲外网出口) => 2.2.2.2:20002(乙外网入口) 向乙尝试发送数据,虽被乙拒绝,但在乙的路由器上留下了访问记录,从而使得乙具有了向甲发送数据的能力。而当乙发送完数据,又会使得甲得到向乙发送数据的能力,从而创建双向通讯。
  • 当乙的 NAT 类型是 A 时同上甲为 A 时逻辑

若是甲的 NAT 类型是 C :

  • 当乙的 NAT 类型是 D :乙尝试链接甲的时候会被拒绝,而且甲也无法知道乙映射的端口号是哪个因此亦没法链接到乙。没法创建任一方向的通讯。
  • 当乙的 NAT 类型是 B :C-B 的链接同上面 B-C 的链接。
  • 当乙的 NAT 类型是 C :C-C 和 C-B/B-C 的区别仅在于要求双方出口的端口要一直保持一致,要求更加严格,但依旧可以创建双向通讯。
  • 当乙的 NAT 类型是 A 时亦可以创建双向通讯。

若是甲的 NAT 类型是 D :

  • 当乙的 NAT 类型是 D:没法创建任一方向的通讯。
  • 当乙的 NAT 类型是 C:同 C-D,没法创建任一方向的通讯。
  • 当乙的 NAT 类型是 A/B:同 A-D 和 B-D,可以创建双向通讯。

综上推导,能够有如下结论:

  1. 只有 C-D,D-C,D-D 的组合是没有机会可以创建双向通讯的,其余组合在 NAT 层面上都可以具有双向通讯的能力。
  2. 类型为 A/B 的玩家理论上连其余任何类型的玩家都不会有 NAT 上的问题。

固然上述都是理论,实际中是否真的可以链接上还取决于其余网络情况甚至是程序编写逻辑的因素。

动森是如何作联机的

许多主机游戏在联机的时候都会有一些在玩家看来很是奇怪甚至奇葩的设定,这些设定都和上面讲的同步机制和联机网络问题相关。

动森的联机模式也有诸多有意思(恼人)的设定,例如:

  • 联机状态下没法更改岛的装饰
  • 当一个玩家上岛时,会须要全部人暂停近很长时间来等其成功加入
  • 当一个玩家离开时,一样须要你们同步目送其离开,而且在离开时会保存当前时刻的数据进度
  • 当有个玩家掉线/强行退出时,全部人的数据会回滚岛上一次玩家登岛/正常离开时的版本
  • 当岛内有玩家打开了对话窗口时,人不能正常离开也不能上岛

如下分析仅仅是我在玩了 200 个小时游戏后,结合本身的软件工程经验对动森实现方式的猜想。在没有看代码前谁也没法保证这种猜想的绝对正确性,何况相比正确性,我更在意这个猜想过程的开心,因此你们能够更多以工程角度来思考而不是纠结于其是否的确是这么实现的。

咱们能够把联机游戏的过程分如下几个环节来分别讨论:

1. 甲打开联机权限(即所谓的开门),用本身主机做为 Host

这一步甲将本身的外网 IP 和端口号(如 1.1.1.1:10001)注册在了 Switch 的联机服务中。

2. 乙经过搜索找到甲,尝试加入

  • 乙经过联机服务先将本身的外网 IP 和端口号(如 2.2.2.2:10002)注册上去。(即游戏里询问是否要联机的那一步)
  • 再经过搜索获得甲的 1.1.1.1:10001 (即动森里搜索好友的那一步),尝试链接。

    注意,此时甲主机在后台也经过联机服务知道了乙在连它,而且甲也会根据 NAT 类型的不一样,用乙的 2.2.2.2:10002 去连乙,尝试打通双向通讯。

3. 创建链接,上岛

当上面一步确认可以创建双向通信后,就能够开始上岛了。上岛又分为如下几个步骤:

3.1 Host 打包当前全部游戏状态

在上岛前,甲主机(Host)会把当前时刻的全部人的游戏数据给打包一份 snapshot。

这里的 snapshot 数据内容包括岛屿数据和玩家数据。

3.2 下载对方岛的 snapshot

动森上岛时会弹出一个显示进度的动画,这个动画的过程就是在下载目标岛的 snapshot 数据,很明显可以发现若是在中国连美国的玩家,这个过程会很是漫长,这个是因为跨境网速慢致使的。

3.3 其余人同步等待,直到新玩家上岛

之因此其余玩家要等待新玩家上岛是由于上一步保存的 snapshot 必须是最新的结果,这也意味着其余玩家不能再有增量操做,不然新玩家上岛时状态就不一致了。

4. 开始游戏

当上岛完毕,就能够开始正常开始游戏了。这时候就会遇到一个如何同步玩家彼此操做数据的问题。

这里咱们把玩家的操做分为两种类型:

  1. 影响游戏数据(低频,有时序要求,须要落盘)
  2. 影响游戏画面但不影响游戏数据(高频,无时序要求,不须要落盘)

若是仔细体验会发现,当咱们在挖坑、和 NPC 对话、丢物品等会对全局游戏数据产生直接影响的操做时,偶尔会出现一下卡顿,这是由于这些会影响全局状态的操做在渲染画面前都须要先向 Host 主机请求是否容许,这里若是出现网络抖动的话就会出现卡顿/失败的状况。可是咱们跑步的时候却不多出现卡顿,但有时会出现「闪回」,由于跑动只影响了玩家当前位置,不影响游戏数据,就算出现闪回也是可以接受的,并且还不须要强制保证时序性。何况若是跑步也要去 Host 端询问就会致使整个游戏体验都很是卡。可是像丢物品这种操做若是数据错乱或者时序错乱的话,整个状态就不一致了,会很是严重。

因此这里的同步方式实际上是锁步同步。只不过度别对低频和高频作了分别的策略。

5. 玩家退出/强退/掉线

玩家若是正常退出游戏,会触发一个「保存数据」界面。要理解这个保存数据的含义,咱们要把游戏里的数据分为两类:

  1. 岛屿的状态
  2. 每一个玩家的每一步操做数据

首先对于主机游戏来讲,其真正有效的数据都是要最终落盘到主机本地存储里。但试想若是每一次更新都触发玩家本地主机存储的更新,到时候要回滚起来也会变得异常麻烦,更不用说磁盘的 IO 还很是慢。因此这里更可能的实现方式实际上是,Host 主机的内存里存放着当前游戏的权威数据,包括岛屿状态和玩家操做。另外,不管玩家用什么方式退出,咱们都必须确保结束游戏后,全部玩家本地的存档加上 Host 主机上的存档都是某一个时间点上的真正游戏状态。游戏数据的正确性优先级是高于用户体验的。下面会有例子来解释这点的重要性。

当玩家正常退出触发「保存数据」时,Host 主机首先会开启一个当前时刻的 snapshot,而后每一个玩家的主机都会向 Host 主机去下载属于其的操做数据,并落盘到本地。

但当有玩家异常退出时,因为其并无下载属于他的数据,因此他的本地游戏数据还停留在上一次保存的时间点 T1 上,为了知足咱们前面说的数据正确性的保证,虽然岛上其余玩家没有掉线,而且他们游戏里的状态都是最新的也是正确的数据,但不得不为了让这个家伙的数据是正确的而把其余全部人的数据都回滚到了时间 T1 上,这就是为何动森会出现掉线回档的缘由。

常见问题

任天堂联机服务垃圾吗?

经过上面的解释可以理解,这些看似奇葩的联机体验背后,的确是有着很是多技术难题的。并且任天堂毕竟是一家游戏公司不是专业分布式数据库公司,虽然目前的技术实现方案有诸多能够改进的地方,可是也是要算上 ROI 的,因此谈「垃圾」仍是算不上。

为何游戏厂商不自建服务器来提高体验?

游戏玩家来自全球各地,若是要用自建服务器来提高体验,那也得在全球都铺设服务器,这个成本至关大且实现难度也至关大的。的确如今会有那种全球同服的解决方案,可是通常都是像网络游戏这种就靠着联网来挣钱的公司会有能力和意愿采用。主机游戏的商业模式注定了他们不会花很是高的成本去提高网络体验。固然主机生产商本身搞一个全球服务器就是另说了。

参考资料

相关文章
相关标签/搜索