概述:本文嘗試從開發者角度梳理開發實時聯網遊戲後臺服務過程中可能面臨的挑戰,並針對性地提供相應解決思路,期望幫助開發者依據自身遊戲特點做出合理的技術選型。 維基百科關於網路遊戲的定義:通過計算機網路,將專用伺服器和使用者的客戶端裝置(手機、PC、遊戲主機等)相連,讓多名玩家同時聯機進行遊戲的娛樂形式,由此可知網路遊戲涉及三個角色:客戶端、網路、伺服器,從網路架構上來講網路遊戲可分為C/S 架構和P2P架構(特指客戶端間直連通訊),在實際開發中還有一種C/S和P2P架構混合:C/M架構。
P2P架構不在本文討論範圍,C/M架構和C/S架構類似,和經典的LAMP網站架構類似一般C/S架構的遊戲後臺也可劃分為如下三層:(1)網路接入層;(2)遊戲邏輯層;(3) 資料儲存層。 網路接入、遊戲邏輯、資料儲存層各自所面臨的問題域及對應技術棧都大為不同,做此劃分不僅有助於模組解耦、技術分工、元件複用,也可方便服務的運維部署。本文也著重從這三個方面來闡述遊戲伺服器的開發。1、網路接入層
網路接入層的主要任務是建立客戶端和後臺服務以及客戶端之間的通道,接收來自客戶端大量併發請求,考核該層的主要效能指標是:高吞吐、低延遲。因而網路接入層開發考驗的是開發者高效能網路程式設計的功底,即解決C10K甚至C10M的能力。
1.1協議選擇
根據OSI的七層網路參考模型,我們可將網遊網路也做如下7層劃分:
其中4層以下都由作業系統來負責,開發者無需為此操心,在實際的開發過程中開發者首要面臨的問題便是傳輸層是採用TCP還是UDP,下表簡要對比了兩者的優劣。 綜合兩者優劣,簡單來說除非對延遲有極致要求(例如FPS、MOBA類遊戲)需採用UDP外,TCP可應對大部分遊戲。在實際遊戲開發中不管是採用TCP還是UDP方式,都很少直接通過 Socket程式設計方式來進行,一來因為開發工作量大,質量效能難以保證;二來平臺相容性不好(比如H5並沒有提供socket程式設計能力),而是基於更上層的通訊協議比如基於TCP的HTTP、Websocket協議,GRPC,以及基於UDP實現的QUIC,WebRTC協議等。 值得注意的是基於安全性考慮,瀏覽器標準未提供UDP收發能力,QUIC協議也只在chrome得到了支援,WebRTC也還不是瀏覽器事實標準且協議初始目的是用於實現點對點的音視訊通訊,協議內容過於龐雜不容易提煉應用於遊戲開發中,因而現階段H5遊戲還只能採用HTTP或Websocket方式通訊。通訊協議確定後,隨後要考慮的便是遊戲物件的序列化,序列化主要有基於文字、基於二進位制兩種,其優劣如下表所示。在開發過程中一般會先採用文字序列化方式,便於前後端開發聯調,在遊戲正式上線前切換至二進位制序列化方式以減少傳輸流量、提升編解碼效率。
至於資料安全性問題,為了保護敏感資料安全開發者可以選擇安全的https或WSS通訊協議,而對於直接基於TCP協議通訊,可採用先用RSA協商加密祕鑰,然後使用對稱加密方式將資料加密後傳送。通過以上分析,對於遊戲協議型別的選擇我們給出有以下準則:
1、弱聯網類遊戲:諸如休閒、卡牌類遊戲可直接HTTP協議,對安全性有要求的話就使用HTTPS;
2、實時性,互動性要求較高:這類遊戲一般需要保持長連線,優先選擇標準的ws協議(同時使用二進位制序列化方式),如考慮安全性可使用wss協議。而對於提供socket介面的native平臺也可使用TCP協議,同時對資料做對稱加密增強安全性;
3、實時性要求極高:不僅需要和伺服器保持長連線,且延遲和網路抖動都要求極高(如FPS,賽車類遊戲),可使用基於UDP的實現流傳輸協議如QUIC,KCP等。
1.2併發模型
為了處理來自客戶端的併發請求,服務端有4種常見的併發模型。
1.2.1程式
程式是最早採用的併發模型,程式作為操作資源分配、排程的單位,擁有獨立的執行空間。程式併發模型中每個請求由獨立的程式來處理,程式一次只能處理一個請求,該模型最大的優點就是簡單。如果處理請求的程式由於系統呼叫而阻塞或程式的時間片用完,搶佔式的程式排程器就會暫停舊程式執行,排程執行新的程式,這個過程涉及大開銷的上下文切換,程式併發模型的缺點是比較低效。最典型的採用程式模型的服務有Apache。
1.2.2執行緒
執行緒併發模型是程式模型的改進,執行緒從屬於程式,是系統更小粒度的執行排程單元。不同請求可由程式內多個併發執行的執行緒來處理,這些執行緒由作業系統核心自動排程。執行緒相對程式的主要優勢在於,排程上下文切換開銷更小,但由於多個執行緒共享地址空間,需要額外的執行緒間互斥、同步機制來保證程式性正確性。典型的採用執行緒模型的服務有Tomcat。
1.2.3 IO多路複用
利用作業系統提供的epoll等IO多路複用機制,能同時監控多個連線上讀、寫事件, IO多路複用也稱事件驅動模型,網路程式執行邏輯可抽象為事件驅動的狀態機。 IO多路複用避免了讀寫阻塞,減少了上下文切換,提升了CPU利用率和系統吞吐率。但IO多路複用它將原本“同步”、線性的處理邏輯變成事件驅動的狀態機,處理邏輯分散於大量的事件回撥函式。這種非同步、非線性的模型,極大地增加了程式設計難度,如nodeJs的常見的回撥地獄問題。典型的採用IO複用模型的服務有Nginx,netty。
1.2.4 協程
協程也稱為輕量級執行緒,是一種協同的、非搶佔式的多工併發模型。 協程執行在使用者空間,當遇到阻塞或特定入口時,通過顯式呼叫切換方法主動讓出CPU,由任務排程器選取另一個協程執行。
協程切換隻是簡單地改變執行函式棧,不涉及核心態與使用者態轉化,也涉及上下文切換,開銷遠小於程式/執行緒切換。協程的概念雖早已提出,隨著近些年年越來越多的語言(go、 Haskell)內建對協程支援才被開發者所熟知,協程極大的優化了開發者程式設計體驗,在同步、順序程式設計風格能快速實現程式邏輯,還擁有IO多路複用非同步程式設計的效能。典型的採用協程模型的服務有openresty(Lua), gevent(Python), golang。
以上總結了目前4種常用的併發模型,它們在工作原理、執行效率、程式設計難度等方面有顯著區別,各自有適用場景,在實際使用時應該根據需求仔細評估。在實際開發過程中如果沒有可複用的現成網路元件或歷史包袱我們建議使用協程併發模式開發網路接入層服務。