從動物森友會聊主機遊戲聯機機制

最近在玩動物森友會的時候時常會遇到一些迷之聯機問題,在網上一番搜索,發現你們的答案都趨於用玄學來解釋,因而便有了興致想在原理上搞懂這些問題產生的根源以及動森這款遊戲的一些聯機設定背後的技術緣由。數據庫

事先聲明,本人並不從事遊戲行業亦非主機遊戲長期玩家,若有紕漏或其餘角度的補充,歡迎在評論區告知。服務器

遊戲是如何同步的

咱們首先來看看通常遊戲是如何來作同步的。網絡

想象兩個獨立房間裏分別有甲、乙兩個玩家,他們要遠程下一局象棋。他們每下一步前都須要先獲知到當前棋盤的狀況,此時可以有兩種實現方式。架構

第一種叫作鎖步同步,原理是玩家每操做一步就通知給另一個玩家,彼此同步當前的操做序列,經過這些有時序的操做,就可以計算出當前棋局的狀態。但它不容許中間丟失任何一步的信息,不然就會出現很是大的計算誤差。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 的,因此談「垃圾」仍是算不上。

爲何遊戲廠商不自建服務器來提高體驗?

遊戲玩家來自全球各地,若是要用自建服務器來提高體驗,那也得在全球都鋪設服務器,這個成本至關大且實現難度也至關大的。的確如今會有那種全球同服的解決方案,可是通常都是像網絡遊戲這種就靠着聯網來掙錢的公司會有能力和意願採用。主機遊戲的商業模式註定了他們不會花很是高的成本去提高網絡體驗。固然主機生產商本身搞一個全球服務器就是另說了。

參考資料

相關文章
相關標籤/搜索