遊戲伺服器開發的基本體系與伺服器端開發的一些建議

d-w發表於2024-04-20

剛開始時以為做遊戲伺服器和做web差不多,但是經過一段時間之後,才發現程式碼太多,太亂了,一看程式碼都想重構,都是踩著坑往前走。這裡我把一些遊戲開發方面的東西整理一下,希望能對那些想做遊戲伺服器開發的朋友有所幫助

近年來,我身邊的朋友有很多都從web轉向了遊戲開發。他們以前都沒有做過遊戲伺服器開發,更談不上什麼經驗,而從網上找的例子或遊戲方面的知識,又是那麼的少,那麼的零散。當他們進入遊戲公司時,顯得一臉茫然。如果是大公司還好點,起碼有人帶帶,能學點經驗,但是有些人是直接進入了小公司,甚至這些小公司只有他一個後臺。他們一肩扛起了公司的遊戲後端的研發,也扛起了公司的成敗。他們也非常盡力,他們也想把遊戲的後端做好。可是就是因為沒什麼經驗,剛開始時以為做遊戲伺服器和做web差不多,但是經過一段時間之後,才發現程式碼太多,太亂了,一看程式碼都想重構,都是踩著坑往前走。這裡我把一些遊戲開發方面的東西整理一下,希望能對那些想做遊戲伺服器開發的朋友有所幫助。

首先,要明確一點,做遊戲伺服器開發和做傳統的web開發有著本質的區別。遊戲伺服器開發,如果沒有經驗,一開始根本沒有一個明確清析的目標,不像web那樣,有些明確的MVC架構,往往就是為了儘快滿足策劃的需求,儘快的實現功能,儘快能讓遊戲跑起來。但是隨著功能越來越多,在老程式碼上面修改的越來越頻繁,遊戲測試時暴露出來的一堆bug,更讓人覺得束手無策,這個時候我們想到了重構,想到了架構的設計。

遊戲的構架設計非常重要,好的構架程式碼清析,責任明確,擴充套件性強,易除錯。這些會為我們的開發省去不少時間。那要怎麼樣設計遊戲的構架呢?可能每個遊戲都不一樣,但是本質上還是差不多的。

對於遊戲伺服器的構架設計,我們首先要了解遊戲的伺服器構架都有什麼組成的?一款遊戲到上線,需要具備哪些功能?有些人可能會說,只要讓遊戲跑起來,訪問伺服器不出問題不就行了嗎?答案是不行的,遊戲構架本身代表的是一個體系,它包括:

1,系統初始化
2,遊戲邏輯
3,資料庫系統
4,快取系統。
5,遊戲日誌
6,遊戲管理工具
7,公共服務元件

這一系統的東西都是不可少的,它們共同服務於遊戲的整個運營過程。我們一點點來介紹各個系統的功能。

一,系統初始化

系統初始化是在沒有客戶端連線的時候,伺服器啟動時所需要做的工作。基本上就是配置檔案的讀取,初始化系統引數。但是我們必須要考慮的是,系統初始化需要的引數配置在哪兒,是配置在本地伺服器,還是配置在資料庫,伺服器啟的時候去資料庫取。配置的修改需不需要重啟伺服器等。

二,遊戲邏輯

遊戲邏輯是遊戲的核心功能實現,也是整個遊戲的服務中心,它被開發的好壞,直接決定了遊戲伺服器在執行中的效能。那在遊戲邏輯的開發中我們要注意些什麼呢?

(1)網路通訊

遊戲是一種網路互動比較強的業務,好的底層通訊,可以最大化遊戲的效能,增加單臺伺服器處理的同時線上人數,給遊戲帶來更好的體驗,至少不容易出現因為網路層導致的資料互動卡頓的現象。在這裡我推薦使用Netty,它是目前最流行的NIO框架,它的用法可以在我之前的文章中檢視,這裡不再多說了。

有人疑問,程式碼也需要分層次?這個是當然了,不同的程式碼,代表了不同的功能實現。現在的開發語言都是物件導向的,如果我們不加思考,不加整理的把功能程式碼亂堆一起,起始看起來是快速實現了功能,但是到後期,如果要修改需求,或在原來的程式碼上增加新的需求,那真是被自己打敗了。所以程式碼一定要分層,主要有以下幾層:

a,協議層,也叫前後臺互動層,它主要負責與前臺互動協議的解析和返回資料。在這一層基本上沒有什麼業務邏輯實現。與前臺互動的資料都在這一層開始,也在這一層終止。比如你使用了Netty框架,那麼Netty的ChannelHandlerContext即Ctx只能出現在這一層,他不能出現到遊戲業務邏輯程式碼的實現中,接收到客戶端的請求,在這一層把需要的引數解析出來,再把引數傳到業務邏輯方法中,業務邏輯方法處理完後,把要返回給客戶端的資料再返回到這一層,在這一層組織資料,返回給客戶端,這樣就可以把業務邏輯和網路層分離,業務邏輯只關心業務實現,而且也方便對業務邏輯進行單元測試。

b,業務邏輯層,這裡處理真正的遊戲邏輯,該計算價格計算價格,該通關的通關,該計時的計時。該儲存資料的儲存資料。但是這一層不直接操作快取或資料庫,只是處理遊戲邏輯計算。因為業務邏輯層是整個遊戲事件的處理核心,所以他的處理是否正確直接決定遊戲的正確性。所以這一層的程式碼要儘量使用面向對角的方法去實現。不要出現重複程式碼或相似的功能進行復制貼上,這樣修改起來非常不方便,可能是修改了某一處,而忘記了修改另外同樣的程式碼。還要考慮每個方法都是可測試的,一個方法的行數最好不要超過一百行。另外,可以多看看設計模式的書,它可以幫助我們設計出靈活,整潔的程式碼。

三,資料庫系統

資料庫是儲存資料庫的核心,但是遊戲資料在儲存到資料庫的時候會經過網路和磁碟的IO,它的訪問速度相對於記憶體來說是很慢的。一般來說,每次訪問資料庫都要和資料庫建立連線,訪問完成之後,為了節省資料庫的連線資源,要再把連線斷開。這樣無形中又為伺服器增加了開銷,在大量的資料訪問時,可能會更慢,而遊戲又是要求低延時的,這時該怎麼辦呢?我們想到了資料庫連線池,即把訪問資料庫的連線放到一個地方管理,用完我不斷開,用的時候去那拿,用完再放回去。這樣不用每次都建立新的連線了。但是如果要我們自己去實現一套連線池管理元件的話,需要時間不說,對技術的把控也是一個考驗,還要再經過測試等等,幸好網際網路開源的今天,有一些現成的可以使用,這裡推薦Mybatis,即實現了程式碼與SQL的分離,又有足夠的SQL編寫的靈活性,是一個不錯的選擇。

四,快取系統

遊戲中,客戶端與伺服器的互動是要求低延遲的,延遲越低,使用者體驗越好。像之前說過的一樣,低延遲就是要求伺服器處理業務儘量的快,客戶端一個請求過來,要在最短的時間內響應結果,最低不得超過500ms,因為加上來回的網路傳輸耗時,基本上就是600ms-到700ms了,再長玩家就會覺得遊戲卡了。如果直接從資料庫中取資料,處理完之後再存回資料庫的話,這個效能是跟不上的。在伺服器,資料在記憶體中處理是最快的,所以我們要把一部分常用的資料提前載入到記憶體中,比如說遊戲資料配置表,經常登陸的玩家資料等。這樣在處理業務時,就不用走資料庫了,直接從記憶體中取就可以了,速度更快。遊戲中常見的快取有兩種,1,直接把資料儲存在jvm或伺服器記憶體中,2,使用第三方的快取工具,這裡推薦Redis,詳細的用法可以自己去查詢。

五,遊戲日誌

日誌是個好東西呀,一個遊戲中更不能少了日誌,而且日誌一定要記錄的詳細。它是玩家在整個遊戲中的行為記錄,有了這個記錄,我們就可以分析玩家的行為,查詢遊戲的不足,在處理玩家在遊戲中的問題時,日誌也是一個良好的憑證和快速處理方式。
在遊戲中,日誌分為:1,系統日誌,主要記錄遊戲伺服器的系統情況。比如:資料庫能否正常連線,伺服器是否正常啟動,資料是否正常載入;2,玩家行為日誌,比如玩家傳送了什麼請求,得到了什麼物品,消費了多少貨幣等等;3,統計日誌,這種日誌是對遊戲中所有玩家某種行為的一種統計,根據這個統計來分析大部分玩家的行為,得出一些共性或不同之處,以方法運營做不同的活動吸引使用者消費。
在構架設計中,日誌記錄一定要做為一種強制行為,因為不強制的話,可能由於某種原因某個功能忘記加日誌了,那麼當這個功能出問題了,或者運營跟我們要這個功能的一些資料庫,就傻眼了。又得加需求,改程式碼了。日誌一定要設計一種良好的格式,日誌記錄的資料要容易讀取,分解。日誌行為可以用列舉描述,在功能最後的處理方法裡面加上這個列舉做為引數,這樣不管誰在呼叫這個方法時,都要去加引數描述。
俗話說,工欲善其事,必先利其器。遊戲管理工具是對遊戲執行中的一系列問題處理的一種工具。它不僅是給開發人員用,大多數是給運營使用。遊戲上線後,我們需要針對線上的問題進行不同的處理。不可能把所有問題都讓程式設計師去處理吧,於是程式設計師們想到了一個辦法,給你們做一個工具,你們愛誰處理誰處理去吧。

六, 遊戲管理工具

遊戲管理工具是一個不斷增漲的系統,因為它很多時候是伴隨著遊戲中遇到的問題而實現的。但是根據經驗,有一些功能是必須有的,比如:伺服器管理,主要負責伺服器的開啟,關閉,伺服器配置資訊,玩家資訊查詢,玩家管理,比如踢人,封號;統計查詢,玩家行為日誌查詢,統計查詢,次留率查詢,郵件服務,修改玩家資料等,根據遊戲的不同要求,凡是可以能過工具實現的,都做到遊戲管理工具裡面。它是針對所有伺服器的管理。一個好的,全的遊戲管理工具,可以提高遊戲運營中遇到問題處理的效率,為玩家提供更好的服務。

七,公共元件

公共元件是為遊戲執行中提供公共的服務,比如,充值伺服器,我們沒必須一個服用一個充值,而且你也不能對外提供多個充值伺服器地址,和第三方公司對接,他們絕對不幹,這是要瘋呀;還有運營搞活動時的禮包碼,還有註冊使用者的管理,玩家一個註冊賬號可以進不同的區等。這些都是針對所有區服提供的服務,所以要單獨做,與遊戲邏輯分開,這樣方便管理,部署和負載均衡。還有SDK的登陸驗證,現在手遊比較多,與渠道對接裡要進行驗證,這往往是很多http請求,速度慢,所以這個也要拿出來單獨做,不要在遊戲邏輯中去驗證,因為網路IO的訪問時間是不可控制的,http是阻塞的請求。
所以,綜上來看,一個遊戲伺服器起碼有幾個大的功能模組組成:a,遊戲邏輯工程;b,日誌處理工程;c,充值工程;d,遊戲管理工具工程;e,使用者登陸工程;f,公共活動工程等,根據遊戲的不同需要,可能還有其它的。所在在構架的設計中,一定要考慮到系統的分散式部署,儘量把公共的功能拆出來做,這樣可以增強系統的可擴充套件性。

伺服器端開發的一些建議

本文作為遊戲伺服器端開發的基本大綱,是遊戲實踐開發中的總結。第一部分專業基礎,用於指導招聘和實習考核, 第二部分遊戲入門,講述遊戲伺服器端開發的基本要點,第三部分服務端架構,介紹架構設計中的一些基本原則。希望能幫到大家

一 專業基礎

1.1 網路

1.1.1 理解TCP/IP協議
網路傳輸模型
滑動視窗技術
建立連線的三次握手與斷開連線的四次握手
連線建立與斷開過程中的各種狀態
TCP/IP協議的傳輸效率
思考

1)請解釋DOS攻擊與DRDOS攻擊的基本原理
2)一個100Byte資料包,精簡到50Byte, 其傳輸效率提高了50%
3)TIMEWAIT狀態怎麼解釋?
1.1.2 掌握常用的網路通訊模型
Select
Epoll,邊緣觸發與平臺出發點區別與應用
Select與Epoll的區別及應用
1.2 儲存
計算機系統儲存體系
程式執行時的記憶體結構
計算機檔案系統,頁表結構
記憶體池與物件池的實現原理,應用場景與區別
關聯式資料庫MySQL的使用
共享記憶體
1.3 程式
對C/C++語言有較深的理解
深刻理解介面,封裝與多型,並且有實踐經驗
深刻理解常用的資料結構:陣列,連結串列,二叉樹,雜湊表
熟悉常用的演算法及相關複雜度:氣泡排序,快速排序
二 遊戲開發入門

2.1防禦式程式設計
不要相信客戶端資料,一定要檢驗。作為伺服器端你無法確定你的客戶端是誰,你也不能假定它是善意的,請做好自我保護。(這是判斷一個伺服器端程式設計師是否入門的基本標準)
務必對於函式的傳人參數和返回值進行合法性判斷,內部子系統,功能模組之間不要太過信任,要求低耦合,高內聚
外掛式的模組設計,模組功能的健壯性應該是內建的,儘量減少模組間耦合
2.2 設計模式
道法自然。不要迷信,迷戀設計模式,更不要生搬硬套
簡化,簡化,再簡化,用最簡單的辦法解決問題
借大寶一句話:設計本天成,妙手偶得之
2.3 網路模型
自造輪子: Select, Epoll, Epoll一定比Select高效嗎?
開源框架: Libevent, libev, ACE
2.4 資料持久化
自定義檔案儲存,如《夢幻西遊》
關聯式資料庫: MySQL
NO-SQL資料庫: MongoDB
選擇儲存系統要考慮到因素:穩定性,效能,可擴充套件性
2.5 記憶體管理
使用記憶體池和物件池,禁止執行期間動態分配記憶體
對於輸入輸出的指標引數,嚴格檢查,寧濫勿缺
寫記憶體保護。使用帶記憶體保護的函式(strncpy, memcpy, snprintf, vsnprintf等),嚴防陣列下標越界
防止讀記憶體溢位,確保字串以'\0'結束
2.6 日誌系統
簡單高效,大量日誌操作不應該影響程式效能
穩定,做到伺服器崩潰是日誌不丟失
完備,玩家關鍵操作一定要記日誌,理想的情況是透過日誌能重建任何時刻的玩家資料
開關,開發日誌的要加級別開關控制
2.7 通訊協議
採用PDL(Protocol Design Language), 如Protobuf,可以同時生成前後端程式碼,減少前後端協議聯調成本, 擴充套件性好
JSON,文字協議,簡單,自解釋,無聯調成本,擴充套件性好,也很方便進行包過濾以及寫日誌
自定義二進位制協議,精簡,有高效的傳輸效能,完全可控,幾乎無擴充套件性
2.8 全域性唯一Key(GUID)
為合服做準備
方便追蹤道具,裝備流向
每個角色,裝備,道具都應對應有全域性唯一Key
2.9 多執行緒與同步
訊息佇列進行同步化處理
2.10 狀態機
強化角色的狀態
前置狀態的檢查校驗
2.11 資料包操作
合併, 同一幀內的資料包進行合併,減少IO操作次數
單副本, 用一個包儘量只儲存一份,減少記憶體複製次數
AOI同步中減少中間過程無用資料包
2.12 狀態監控
隨時監控伺服器內部狀態
記憶體池,物件池使用情況
幀處理時間
網路IO
包處理效能
各種業務邏輯的處理次數
2.13 包頻率控制
基於每個玩家每條協議的包頻率控制,癱瘓變速齒輪
2.14 開關控制
每個模組都有開關,可以緊急關閉任何出問題的功能模組
2.15 反外掛反作弊
包頻率控制可以消滅變速齒輪
包id自增校驗,可以消滅WPE
包校驗碼可以消滅包攔截篡改
圖形識別嗎,可以踢掉99%非人的操作
魔高一尺,道高一丈
2.16 熱更新
核心配置邏輯的熱更新,如防沉迷系統,包頻率控制,開關控制等
程式碼基本熱更新,如Erlang,Lua等
2.17 防刷
關鍵系統資源(如元寶,精力值,道具,裝備等)的產出記日誌
資源的產出和消耗盡量依賴兩個或以上的獨立條件的檢測
嚴格檢查各項操作的前置條件
校驗引數合法性
2.18 防崩潰
系統底層與具體業務邏輯無關,可以用大量的機器人壓力測試暴露各種bug,確保穩定
業務邏輯建議使用指令碼
系統性的保證遊戲不會崩潰
2.19 效能最佳化
IO操作非同步化
IO操作合併緩寫 (事務性的提交db操作,包合併,檔案日誌緩寫)
Cache機制
減少競態條件 (避免頻繁進出切換,儘量減少鎖定使用,多執行緒不一定由於單執行緒) 多執行緒不一定比單執行緒快
減少記憶體複製
自己測試,用資料說話,別猜
2.20 運營支援
介面支援:實時查詢,控制指令,資料監控,客服處理等
實現考慮提供Http介面
2.21 容災與故障預案

三 伺服器端架構

3.1 什麼是好的架構?
滿足業務要求
能迅速的實現策劃需求,響應需求變更
系統級的穩定性保障
簡化開發。將複雜性控制在架構底層,降低對開發人員的技術要求,邏輯開發不依賴於開發人員本身強大的技術實力,提高開發效率
完善的運營支撐體系
3.2 架構實踐的思考
簡單,滿足需求的架構就是好架構
設計效能,抓住重要的20%, 沒必要從程式程式碼裡面去摳效能
熱更新是必須的
人難免會犯錯,儘可能的用一套機制去保障邏輯的健壯性
遊戲伺服器的設計是一項頗有挑戰性的工作,遊戲伺服器的發展也由以前的單服結構轉變為多服機構,甚至出現了bigworld引擎的分散式解決方案,最近了解到Unreal的伺服器解決方案atlas也是基於叢集的方式。

負載均衡是一個很複雜的課題,這裡暫不談bigworld和atlas的這類伺服器的設計,更多的是基於功能和場景劃分伺服器結構。

首先說一下思路,伺服器劃分基於以下原則:

分離遊戲中佔用系統資源(cpu,記憶體,IO等)較多的功能,獨立成伺服器。
在同一伺服器架構下的不同遊戲,應儘可能的複用某些伺服器(程序級別的複用)。
以多執行緒併發的程式設計方式適應多核處理器。
寧可在伺服器之間多複製資料,也要保持清晰的資料流向。
主要按照場景劃分程序,若需按功能劃分,必須保持整個邏輯足夠的簡單,並滿足以上1,2點。
伺服器結構圖:

各個伺服器的簡要說明:

Gateway 是應用閘道器,主要用於保持和client的連線,該伺服器需要2種IO,對client採用高併發連線,低吞吐量的網路模型,如IOCP等,對伺服器採用高吞吐量連線,如阻塞或非同步IO。

閘道器主要有以下用途:

分擔了網路IO資源
同時,也分擔了網路訊息包的加解密,壓縮解壓等cpu密集的操作。
隔離了client和內部伺服器組,對client來說,它只需要知道閘道器的相關資訊即可(ip和port)。
client由於一直和閘道器保持常連線,所以切換場景伺服器等操作對client來說是透明的。
維護玩家登入狀態。
World Server 是一個控制中心,它負責把各種計算資源分佈到各個伺服器,它具有以下職責:

管理和維護多個Scene Server。
管理和維護多個功能伺服器,主要是同步資料到功能伺服器。
複雜轉發其他伺服器和Gateway之間的資料。
實現其他需要跨場景的功能,如組隊,聊天,幫派等。
Phys Server 主要用於玩家移動,碰撞等檢測。

所有玩家的移動類操作都在該伺服器上做檢查,所以該伺服器本身具備所有地圖的地形等相關資訊。具體檢查過程是這樣的:首先,Worldserver收到一個移動資訊,WorldServer收到後向Phys Server請求檢查,Phys Server檢查成功後再返回給world Server,然後world server傳遞給相應的Scene Server。

Scene Server 場景伺服器,按場景劃分,每個伺服器負責的場景應該是可以配置的。理想情況下是可以動態調節的。

ItemMgr Server 物品管理伺服器,負責所有物品的生產過程。在該伺服器上儲存一個物品掉落資料庫,伺服器初始化的時候載入到記憶體。任何需要產生物品的伺服器均與該伺服器直接通訊。

AIServer 又一個功能伺服器,負責管理所有NPC的AI。AI伺服器通常有2個輸入,一個是Scene Server傳送過來的玩家相關操作資訊,另一個時鐘Timer驅動,在這個設計中,對其他伺服器來說,AIServer就是一個擁有很多個NPC的客戶端。AIserver需要同步所有與AI相關的資料,包括很多玩家資料。由於AIServer的Timer驅動特性,可在很大程度上使用TBB程式庫來發揮多核的效能。

把網路遊戲伺服器分拆成多個程序,分開部署。這種設計的好處是模組自然分離,可以單獨設計。分擔負荷,可以提高整個系統的承載能力。

缺點在於,網路環境並不那麼可靠。跨程序通訊有一定的不可預知性。伺服器間通訊往往難以架設除錯環境,並很容易把事情攪成一團糨糊。而且正確高效的管理多連線,對程式設計師來說也是一項挑戰。

前些年,我也曾寫過好幾篇與之相關的設計。這幾天在思考一個問題:如果我們要做一個底層通用模組,讓後續開發更為方便。到底要解決怎樣的需求。這個需求應該是單一且基礎的,每個應用都需要的。

正如 TCP 協議解決了網際網路上穩定可靠的點對點資料流通訊一樣。遊戲世界實際需要的是一個穩定可靠的在遊戲系統內的點對點通訊需要。

我們可以在一條 TCP 連線之上做到這一點。一旦實現,可以給遊戲服務的開發帶來極大的方便。

可以把遊戲系統內的各項服務,包括並不限於登陸,拍賣,戰鬥場景,資料服務,等等獨立服務看成網路上的若干終端。每個玩家也可以是一個獨立終端。它們一起構成一個網路。在這個網路之上,終端之間可以進行可靠的連線和通訊。

實現可以是這樣的:每個虛擬終端都在遊戲虛擬網路(Game Network)上有一個唯一地址 (Game Network Address , GNA) 。這個地址可以預先設定,也可以動態分配。每個終端都可以透過遊戲網路的若干接入點 ( GNAP ) 透過唯一一條 TCP 連線接入網路。接入過程需要透過鑑權。

鑑權過程依賴內部的安全機制,可以包括密碼證書,或是特別的接入點區分。(例如,玩家接入網路就需要特定的接入點,這個接入點接入的終端都一定是玩家)

鑑權透過後,網路為終端分配一個固定的遊戲域名。例如,玩家進入會分配到 player.12345 這樣的域名,資料庫接入可能分配到 database 。

遊戲網路預設提供一個域名查詢服務(這個服務可以透過鑑權的過程註冊到網路中),讓每個終端都能透過域名查詢到對應的地址。

然後,遊戲網路裡所有合法接入的終端都可以透過其地址相互發起連線並通訊了。整個協議建立在 TCP 協議之上,工作於唯一的這個 TCP 連線上。和直接使用 TCP 連線不同。遊戲網路中每個終端之間相互發起連線都是可靠的。不僅玩家可以向某個服務發起連線,反過來也是可以的。玩家之間的直接連線也是可行的(是否允許這樣,取決於具體設計)。

由於每個虛擬連線都是建立在單一的 TCP 連線之上。所以減少了互連網上發起 TCP 連線的各種不可靠性。鑑權過程也是一次性唯一的。並且我們提供域名反查服務,我們的遊戲服務可以清楚且安全的知道連線過來的是誰。

系統可以設計為,遊戲網路上每個終端離網,域名服務將廣播這條訊息,通知所有人。這種廣播服務在網際網路上難以做到,但無論是廣播還是組播,在這個虛擬遊戲網路中都是可行的。

在這種設計上。在邏輯層面,我們可以讓玩家直接把聊天資訊從玩家客互端傳送到聊天伺服器,而不需要建立多餘的 TCP 連線,也不需要對轉發處理聊天訊息做多餘的處理。聊天伺服器可以獨立的存在於遊戲網路。也可以讓廣播服務主動向玩家推送訊息,由伺服器向玩家發起連線,而不是所有連線請求都是由玩家客互端發起。

虛擬遊戲網路的構成是一個獨立的層次,完全可以撇開具體遊戲邏輯來實現,並能夠單獨去按承載量考慮具體設計方案。非常利於剝離出具體遊戲專案來開發並最佳化。

最終,我們或許需要的一套 C 庫,用於遊戲網路內的通訊。api 可以和 socket api 類似。額外多兩條接入與離開遊戲網路即可。

相關文章