關於 web service, unity, blogger 等軟體工程筆記

多人網路連線遊戲程式開發之讀書筆記:同步遊戲資料,資料序列化與物件複製,處理網路延遲與伺服器安全性

Edit icon 1 則留言
Multi-Player Game Programming by Joshua Glazer, Sanjay Madhav

Multi-Player Game Programming,這是一本介紹如何建置多人網路遊戲程式的書,提到許多關於網路遊戲傳輸的重要概念,例如如何同步遊戲世界資料,如何讓遊戲玩起順順不卡頓 (lag)等等,以及多人遊戲會遇到的困難與挑戰,有志朝網路遊戲程式設計可以閱讀看看的書籍。

不過關於自己最想了解的後端伺服器架構設計,如何動態橫向擴充伺服器之類的內容 (scalable),倒是沒有提到多少細節就是了……。

Ch1 網路遊戲概觀

介紹網路遊戲類型的歷史,近年來比較重要的應該是這兩種:

  • Massively Multilayer Online Games, MMO
    • MMO 類型的遊戲主要都是玩角色扮演,也被稱 MMORPGs
    • MMO 遇到的系統架構挑戰較為複雜,書中有提到部分 (大量處理玩家要求…)
  • Mobile Networked Games
    • 手機遊戲,回合制遊戲大多採用非同步模式 (asynchronous),不像是 MMO 需要持續同步連線
    • 隨著科技以及網路穩定度的進步,採取同步模式的遊戲應該會越來越多 (synchronous)

在開發網路遊戲之前,會先考慮選擇採用哪種通訊協定 (Communications protocol),根據幾種類型資料:

  • 不保證傳輸 (non-guaranteed data)
    • 當網路不好時,第一時間會放棄同步的資料
  • 保證傳輸 (guaranteed data)
    • 例如如射擊遊戲–開槍的行為
  • 最近狀態的資料 (“most recent sate” data)
    • 假設最近發生的事件最重要
    • 例如射擊遊戲–開槍擊中資料,五秒前到現在必須優先保證傳輸
  • 保證並且快速傳輸 (guaranteed quickset data)
    • 非常重要,得優先傳輸並且要快
    • 例如射擊遊戲–玩家移動的資料

另外也會根據應用需求,決定連線架構 (network model),可參考 Ch6。

Ch2 網際網路 Internet

介紹網路架構的基礎,複習已經計算機概論 (Introduction to computer science) 所學。不過這得開發到一定經驗後,再回來看這章才會比較有感覺吧,網路底層理論總是那麼的硬又無趣…。

  • TCP/IP suite
  • Physical layers
    • Network layer
      • IPv4, IPv6,
      • Routeing
    • Transport layer
      • TCP (Transmission control protocol)
      • UDP (User datagram protocol)
    • Application layer
      • DHCP (Dynamic host configuration protocol)
        • 區網內連線通訊協定,自動分配 IP 位置給用戶,管理區網內網路的手段
      • DNS (Domain name system)
        • 域名與 IP 位置對應的查詢服務協定
  • NAT (Network address translation)
    • IPv4 有限 IP 的解決方案,修改來源或是目的的 IP 位置的技術

Ch3 Berkeley socket

Berkeley socket API,原先發布在 BSD 4.2,提供一套操作 TCP/IP 的基礎 API,後續被廣泛的移植到各家 OS 以及不同程式語言上。

這章介紹如何在 C++ 使用該 API,不過我的程式語言是 Golang 以及 C#,其標準函數庫中都已經包含期更高階的實作,因此很簡略翻翻這章的內容。

Ch4 Object serialization

網路多人遊戲中,需要同步遊戲物件資料到多個玩家中,如何將這些遊戲物件資料轉換成可傳送的格式 (format data) 的過程,即是物件序列化 (serialization)。

本章介紹如何在 C++ 操作記憶體區段來做到序列化,以下是示意的範例:

class Avatar : GameObject {
public:
   Avatar() : mHealth(100), mMagicpoint(100), mAttack(3), mDefend(1) {}
   
   void Write(MemoryStream& inStream) const;
   void Read(MemoryStream& inStream);
  
private:
   int32_t mHealth;
   int32_t mMagicpoint;
   int32_t mAttack;
   int32_t mDefend;
};
void Avatar::Write(MemoryStream& inStream) const {
   inStream.Write(mHealth);
   inStream.Write(mMagicpoint);
   inStream.Write(mAttack);
   inStream.Write(mDefend);
}
void Avatar::Read(MemoryStream& inStream) {
   inStream.Read(&mHealth);
   inStream.Read(&mMagicpoint);
   inStream.Read(&mAttack);
   inStream.Read(&mDefend);
}

其記憶體配置會是:

Address Field SourceValue
Bytes 0-3 mHealth 0x00000064 (100)
Bytes 4-7 mMagicpoint 0x00000064(100)
Bytes 8-11 mAttack 0x00000003(3)
Bytes 12-15 mDefend 0x00000001(1)

甚至有時候有對 bit 操作,看需求。

在不同平台 (platform) 的位元組順序的相容議題 (endian compatibility),little-endian/ big-endian 需要做轉換,通常在跨平台同步資料才需要考慮這個議題,解決方案會直接寫在 MemoryStream 的實作中,如果傳輸資料的 endain 與該平台的 endian 不同時,MemoryStream 讀寫時會轉換處理。

壓縮議題,序列化資料可以再經過壓縮機制再傳輸,書中介紹幾種方式,都是非破壞性壓縮方式 (lossless compression):

不過讀完本章,自己實務上卻不會這樣從底層開始寫起,序列化會採用通用的交換格式:

雖然需要花較多的時間序列化以及較大的資料量,但好處是有現成的函數庫 (library) 可以使用,拿到原始資料也容易人工偵錯。如果專案需求需要更快更小的序列化方法,那會直接考慮 Protocol buffers 的解決方案。

至於壓縮,則是採用 HTTP 常見支援的壓縮演算法,例如 gzip 或是 Deflate

Ch5 Object replication

介紹遠端 (remote) 與本機 (local) 遊戲物件狀態同步的程式框架 (replication framework),將各個玩家在遊戲世界的狀態互相同步。例如一位玩家開門開槍將殭屍殺死,其他玩家也要看到相同的狀態演出。

需要三個主要步驟

  • 標示送出的資料封包包含物件狀態 (object state)
  • 同步物件需要有擁有唯一標示 (uniquely identity)
  • 通知該物件類別同步其物件狀態 (replicated object state)

物件同步的程式架構範例與實作可參考書中細節,紀錄其中幾個要點:

  • Object create registry:註冊物件建立的函數對應表 (class id mapping),使得網路模組知道怎麼建立新物件
  • Multiple objects per packet:有效率的傳輸是讓每次傳送封包大小 (packet) 接近 MTU (Maximum Transmission Unit, 1500 bytes in IEEE802.3),因此可嘗試打包一次傳送多物件的狀態
  • Partial object state replication:部分物件屬性同步,只傳有改變需要同步的物件屬性
  • Remote procedure calls (RPCs):將指令與參數資料包裝,當伺服器端 (host, or server) 發送該資料給用戶端 (client),用戶端立即呼叫指令對應的函數,並且傳送其函數參數

Ch6 Network topologies

基礎架構,考慮多台電腦如何連線。

Client-server

  • 主從式,多個遊戲用戶端 (client) 連到同一台伺服器 (server)
  • 若存在 n 個遊戲用戶,其頻寬複雜度 (bandwidth) 為 O(n)
  • 伺服器作為權威伺服器 (authoritative server),驗證遊戲資料,用戶端得使用伺服器的遊戲資料狀態
  • 計算連線同步延遲 (lag),計算用戶端到伺服器資料傳輸的 round trip time (RTT)
    • RTT < 100 ms 會有較好的連線品質

Peer-to-peer

  • 點為點,多個用戶端互相連結
  • 若存在 n 個遊戲用戶,其頻寬複雜度 (bandwidth) 為 O(n^2)
  • 實作面通常採用 input sharing 機制,同步所有輸入操作 (actions),然後每一個 peer 藉由這些輸入模擬世界狀況
  • 需要確保遊戲狀態在不同 peers 上的一致性 (data consistency),即是相同輸入應該要能模擬出相同結果
    • 同步 random seeds,並採用 pseudo-random number generator (PRNG)
  • 連線與斷線處理
    • 新 peer 加入連線議題,需要得知其他 peers 的連線位置
      • 採用與配對伺服器 (matchmaking server) 取得 master peer 位置,再從該 peer 取得在該網絡中的其他 peer 連線位置
    • 與伺服器斷線的問題不存在於 peer-to-peer 架構中,一般來說,若 peer 斷線,遊戲會有短暫暫停將該 peer 資料清空,然後其他 peers 再繼續模擬世界
Client-server

Client-server

Pear-to-pear

Pear-to-pear

另外該節提供這兩種架構的簡易範例遊戲,可參考書中內容。

Ch7 Latency, Jitter, Reliability

介紹遊戲中延遲 (latency) 原因,根據網路分成兩大部分介紹,並介紹如何計算網路延遲等資料。

非網路原因 (non-network latency)

  • Input sampling latency:輸入延遲,玩家按下實體按鍵與遊戲偵測到按下的以及延遲時間
    • 處理方式:增加軟體與硬體取得輸入狀態的頻率,通常自製輸入硬體才需要考慮…
  • Render pipeline latency:繪製延遲,因 GPU 無法在指定時間內完成所有的 draw commands
    • 處理方式:減少繪製物件數量,減少後製效果,合併 draw commands 等等
  • Multi-threaded render pipeline latency:遊戲多執行序延遲,當其中某個執行序無法在指定時間完成時,其他執行序需要等待 (想像一個執行序跑遊戲模擬,另一個執行序跑繪圖…)
  • VSync:顯卡與螢幕處理垂直同步造成的延遲,但不開 VSync 又可能會導致畫面撕裂 (see Wiki)
  • Display lag & Pixel response time:螢幕顯示造成的延遲,硬體因素遊戲軟體無解啊

網路原因延遲

  • Processing delay:軟體處理的延遲,傳送封包到另一個端點 (endpoint) 需要時間,例如處理 NAT or 封包加解密
    • 處理方式:通常影響非常小,可忽略(routers 效能都很好)
  • Transmission delay:實體傳輸延遲,寫入到 cable 頻寬 (bandwidth) 不足
    • 處理方式:減少傳輸量
  • Queuing delay router:網路介面忙碌的延遲,大量資料傳輸網路介面無法負荷時,會先放在傳輸佇列排隊等待
    • 處理方式:減少傳輸量
  • Propagation delay:物理限制傳播延遲,物理上長距離的通訊會花比較長的時間(台灣連日本會比台灣連美國還要快一些)
    • 處理方式:調整伺服器物理所在位置,越靠近目標用戶所在區域越好

Round trip time (RTT)

  • 計算從用戶端 (client, 遊戲端) 發送要求到伺服器 (server),直到收到伺服器的回應為止
  • 該時間受到用戶端以及伺服器雙方的網路延遲影響
  • 遊戲通常使用 RTT/2 來預估單向傳輸的時間 one-way travel time

Jitter

  • 實際上的 RTT 會在平均預期的 RTT 數值跳動,稱為 jitter (抖動)
  • 通常因為網路延遲 (network latency) 所造成
  • 解決 jitter 方法為解決延遲的議題

Packet loss and Reliability

  • 網路差容易導致網路封包遺失 (packet loss)
  • TCP 與 UDP 選擇,看遊戲需求
    • TCP:保證收到順序與保證資料送達
      • 由於 TCP 保證資料順序,若前次封包沒有成功送達時,會讓後續封包排隊等待傳輸,可能會使用後續較重要的封包卡住 (例如 guaranteed quickset data)
    • UDP:傳輸信任機制,封包順序不保證,且封包可能會遺失
      • 若要確保有成功送達,需要自行實作其通知演算法 (delivery notification)
      • 書中有一大章節介紹如何實作

Ch8 Improved latency handling

網路延遲無可避免,這章節中介紹一些遊戲實作技巧,使得即使在連線品質不好的情況下,能夠降低延遲帶來的影響,讓玩家玩起來感覺還是很順暢。關鍵都在於預測啊。

Dump terminals

由伺服器模擬所有的遊戲狀態,用戶端不知道如何遊戲狀態模擬的流程,僅負責傳送玩家的輸入到伺服器,再接收伺服器回應的結果並且直接顯示。

  • 當網路延遲時,玩家體驗會非常的不好,可能會有角色瞬間移動抑或是輸入反應遲鈍的情況
  • 以角色 (player avatar) 所在位置為例,伺服器說角色新位置在哪裡,直接移動到該位置,會有瞬間移動的體驗

Client side interpolation

收到伺服器結果不是直接顯示,而是以目前的資料,經由一段時間內差 (interpolation) 到新資料。

  • 以角色所在位置為例,伺服器說角色新位置在哪裡,是以目前角色位置為基準,經過一段時間內差到新位置去,會感覺移動較為平滑

Client side prediction

用戶端根據玩家輸入預測模擬資料,收到伺服器資料後再使用內插修正資料

  • 用戶端必須有與伺服器端一樣,相同的模擬遊戲狀態演算法,且是確定預測的 (deterministic)
  • 真實玩家行為是不可被預測……
  • Dead reckoning (航位推測法),根據已知移動方向,推測其位置
    • 可能會因為其他玩家輸入操作,而導致可能會出現的累計誤差 (grown inaccurate)
      • 用戶端以之前資料預測 Avatar 位置,目前已到位置 A,但實際上伺服器已被修正在位置 B,這是因為網路同步延遲而造成預測誤差
        航位推測法預設誤差的示意圖

        航位推測法預設誤差的示意圖

    • 用戶端的修正方式
      • Instance state update,立刻將 Avatar 從 A 拉到 B 的位置,玩家可能會注意到該 Avatar 瞬間跳到 B 的位置。另外注意 B 位置為 RTT/2 的位置,應該要補足預測這時間的位移
      • Interpolation,花些一些些時間將 A 位置平滑內差到 B 位置,除了線性內插 (linear-interpolation) 外,也可以考慮使用曲線內差 (cubic spline interpolation)
      • Second-order state adjustment,內插通常是定速方式 (constant vielocity) 移動,可以多考慮加速度變化來位移移動,數學會變得更加複雜,但能夠使得整個修正過程更不容易被玩家注意到

Server side rewind

能夠接受遊戲狀態時間回溯,根據用戶端輸入重新模擬結果

  • Counter-Strike 射擊遊戲的狙擊槍行為,確保玩家瞄準開槍的目標,不會因為網路延遲遊戲狀態修改而沒有打中

Ch9 Scalability

原本以為會介紹伺服器的議題,如何佈署橫向擴展的伺服器架構,但這章節是介紹遊戲規模擴展的處理,用戶端在大場景以及超多玩家情況下,所會遇到的議題與解決方案。

Object Scope and Relevancy

  • 問題:伺服器 (server) 遊戲狀態更新時,那些資訊需要立即通知給用戶端 (client)
    • 大型遊戲中不可能全部的世界資訊都與用戶端同步
      • 考慮頻寬 (bandwidth) 以及處理時間 (processing time)
    • 部分遊戲機制需要限制同步的資訊,否則玩家可能會資訊作弊 (information cheat,安全性保護)
    • 大部分解決方案為更新玩家所能看見 (visible by player) 或是有相關的資料 (relevancy),採用 visibility culling 的概念來實作

其可能解決技術列於以下。

Static Zones,分靜態區域

  • MMORPGs 常採用,將世界分成是多個小地圖,例如城鎮、平原、森林等等
  • 只同步在相同地圖的遊戲世界資訊
  • 缺點:當玩家都集中在特定地圖時,可能會有不良好的體驗
    • 沒有什麼怪物可以打
    • 大量玩家的資料同步以及繪製 (rendering),導致遊戲延遲
  • 可能的解決方法
    • 不讓玩家進入繁忙的地圖區域
    • 建立分流系統 (see server partitioning)

View Frustum,根據可視區域

  • 只同步可視區域的資料
    只同步玩家 X 前方 view-frustum 內的區域資料,其中紅色點表示為敵人,淡紅點表示敵人,但資料並沒有同步到用戶端

    只同步玩家 X 前方 view-frustum 內的區域資料,其中紅色點表示為敵人,淡紅點表示敵人,但資料並沒有同步到用戶端

  • 通常也會同步一定半徑內距離的區域 (distance-based),處理上述情況中,玩家突然進行 180 度轉向所可能造成的延遲議題
    除了同步玩家 X 之 view frustum 內的區域資料外,也同步其所在位置一定範圍內區域資料,其中紅色點表示為敵人

    除了同步玩家 X 之 view frustum 內的區域資料外,也同步其所在位置一定範圍內區域資料,其中紅色點表示為敵人

  • 關於 view frustum 的數學細節,可參考 Real-Time Collision Detection, by Ericson, 2004

Other visibility techniques

  • 採用 Potentially visible set 切場景,根據遊戲場景設定,預先計算哪有些物件可見或是不可見

Relevancy when not visible

根據不同遊戲設計需求,有些資訊就算玩家角色目前在遊戲世界中看不見,還是得同步更新的資訊

Server partitioning (sharding)

  • 分成多個伺服器模組服務,但還是共用相同的資料庫 (database)
    • Action games: 不同房間,連到不同的遊戲伺服器
    • MMORPGs: 不同地圖,連到不同的遊戲伺服器
  • 在 MMORPGs 中,如果人太多集中於同一張地圖,該遊戲伺服器還有機會會超載
    • 建立排隊系統 (wait in a queue)
    • 看遊戲需求設計…

Instancing

  • 簡單來說就是遊戲分流伺服器,不同分流伺服器各有自己一份遊戲世界狀態
    • 不怕玩家找不到怪物殺
    • 當過多人集中於同張地圖,同個分流還是有可能造成遊戲伺服器崩潰…
      • 例如自己尋找副本組隊,開店攤販系統等,會因人潮而有明顯優勢的遊戲機制,很容易造成玩家集中在一起

Ch10 Security

介紹網路遊戲安全性的保護機制,網路封包以及伺服器驗證,甚至物理機房等級的防護。

Packet sniffing

從封包下手的攻擊:

  • 中間人攻擊 (Man in the middle attack)
    • 傳送資料過程中被他人擷取資料 (例如在區網或是是 Wi-Fi 環境)
    • 通常的保護措施:重要資料傳輸過程中需要加密 (encrypt),例如帳號密碼登入
      • 可採用 Public key cryptography (RSA)
    • 不重要的資料,不加密也沒差
  • Packet sniffing on a host machine
    • 玩家可能會破解伺服器與用戶端 (server-client) 間的通訊,並利用伺服器給予的資料,來進行資訊作弊 (information cheat)
      • 例如破解遊戲中的戰爭迷霧…
    • 通常的保護措施:伺服器應限制資料的給予,只傳用戶端目前能知道的資訊

Input validation

伺服器得永遠驗證輸入:

  • 駭客可以破解並跳過用戶端的輸入保護,直接傳送要求給伺服器
  • 伺服器應該永遠驗證玩家輸入 軟體作弊偵測,Software cheat detection
  • 防止修改遊戲記憶體作弊 (例如 map hacking)
  • 防堵機器人 (BOT)

Securing the server

保護伺服器:

  • DDoS 防護 (distributed denial-of-service attack)
    • 大量要求使得伺服器忙碌不能正常運行
    • 可以使用 Cloud providers (AWS, GCP, or Azure etc…) 的防護措施
  • Bad data (malformed or improper packets)
    • 用戶端亂送資料,可能因為伺服器程式 bug 導致內部狀態錯誤
    • 執行 Fuzz testing,測試那寫得不是很好的程式碼
  • Timing attacks 時間差攻擊
    • 一個範例
      bool Compre(int a[8], int b[8])
      {
         for (int i = 0; i < 8; i++)
            if (a[i] != b[i])
               return false;
         return true;
      }
    • 計算這個函數的執行回傳時間,來猜測 b[0], b[1], ..., b[8] 是怎樣的組合
    • 該範例的解決方式,採用 XOR 判斷
      bool Compre(int a[8], int b[8])
      {
         int val = 0;
         for (int i = 0; i < 8; i++)
            val |= a[i] ^ b[i]
               return val == 0
         return val == 0;
      }
  • Intrusions 使用者入侵伺服器
    • 伺服器作業系統應該時常更新最新版本
    • 注意 spear publishing 攻擊,駭客先入侵公司的個人電腦,然後藉由該電腦入侵伺服器
    • 與員工培養好感情,避免內部員工搞鬼
    • 減少被入侵損失
      • 定期資料備份 (backup)
      • 使用者密碼加密
        • 使用不可逆的加密演算法 (like Blowfish-derived algorithm)
        • 最好再加鹽 (Salt),避免被預先計算的表破解 (rainbow table attack)

Ch11 Real-world engines

介紹 Unreal & Unity 的網路架構,以及遊戲引擎內的物件管理 (spawning objects and replication)。隨意翻一翻,若未來有需要應該會直接去看官方的文件資料。

Ch12 Gamer Services

本章節考慮遊戲外加的額外服務整合,並提供使用 Steamworks SDK 的 C++ 程式碼範例:

  • 遊戲配對 (matchmaking),遊戲大廳配對玩家分房系統
  • 統計資訊 (player statistics),統計玩家在遊戲中獲得的金錢、殺敵數等等資訊
  • 成就系統 (achievements),當玩家達成某個遊戲條件時,給予獎勵的成就系統
  • 排行榜 (leaderboards)
  • 雲端遊戲紀錄儲存 (cloud-based saves),玩家可以在不同電腦上同步遊戲存檔
  • 小額付費交易 (micro-transactions),小額付費購買遊戲中商品
  • 可下載內容 (DLC, downloadable content)

基於遊戲上架發行平台,考慮使用該台的服務:

Ch13 Cloud hosting dedicated servers

這章節探討網路遊戲是否需要架設專有伺服器 (dedicated servers) 來營運,抑或是像 Minecraft 那樣由玩家自行架設。後續提到遊戲伺服器撰寫或是部屬伺服器的管理工具,覺得這章對於現在的我來說,比較沒有參考價值,簡略翻一翻。

在雲端平台上建立遊戲伺服器,需要考慮:

  • 複雜度,考慮遊戲伺服器可能需要開一堆伺服器來一起運作,管理這些機器以及發布流程,會變得相當複雜。甚至得自己想 API 或是工具來管理
  • 費用,在雲端平台 (AWS, GCP, or Azure etc…) 架設伺服器不全然是免費的,需要考慮前期成本以及持續維護的成本
    • 現在各家平台都有新客戶試用方案 (2017-12 紀錄)
      • AWS free tier 一年免費
      • GCP 提供一年內 300 美元額度
      • Azure 提供三十天內 200 credits 或是一年 free services
  • 未預期的設備調整,雲端平台可能會改變機器設備,需要花時間進行測試