【正文】
因此作為個(gè)人沒時(shí)間也不可能對(duì)每一區(qū)域都了解。讓我們來詳細(xì)描述一下這個(gè)過程,客戶端在LoginServer上驗(yàn)證通過時(shí),LoginServer為其生成了本次會(huì)話的session key,但只是保存在當(dāng)前的LoginServer上,不會(huì)存數(shù)據(jù)庫,也不會(huì)發(fā)送給WorldServerMgr??墒?,那么多的LoginServer,要一個(gè)個(gè)問下來,這效率也太低了,后面排的長隊(duì)一定會(huì)開始叫喚了。與此同時(shí),與登錄服的連接還沒有斷開,直到客戶端確實(shí)連接上了選定的世界服并且走完了排隊(duì)過程為止。三個(gè)狀態(tài)的轉(zhuǎn)換流程大致為:LogonState驗(yàn)證成功版本檢查版本低于最新值轉(zhuǎn)到UpdateState |版本等于最新值轉(zhuǎn)到WorldState這個(gè)版本檢查的和決定下一個(gè)狀態(tài)的過程是在LogonState中進(jìn)行的,下一個(gè)狀態(tài)的選擇是由當(dāng)前狀態(tài)來決定。WorldServerMgr內(nèi)部的實(shí)現(xiàn)很簡單,監(jiān)聽一個(gè)固定的端口,接受來自WorldServer的主動(dòng)連接,并檢測(cè)其狀態(tài)。GateWay/WorldServer為各個(gè)獨(dú)立的世界服或者通過網(wǎng)關(guān)連接到后面的世界服。關(guān)于事件機(jī)制的考慮其實(shí)還很多,但都是一些不成熟的想法。在QT中,事件因?yàn)槎际桥c窗口相關(guān)的,所以事件回調(diào)時(shí)都是從當(dāng)前窗口開始,一級(jí)一級(jí)向上派發(fā),直到有一個(gè)窗口返回true,截?cái)嗔耸录奶幚頌橹埂W屑?xì)來看,事件與信號(hào)其實(shí)并無多大差別,從我們對(duì)其需求上來說,都只要能注冊(cè)事件或信號(hào)響應(yīng)函數(shù),在事件或信號(hào)產(chǎn)生時(shí)能夠被通知到即可。放到我們今天討論的State模式中,就拿登錄服所處理的兩個(gè)狀態(tài)來說,也許用mangos所采用的遍歷處理函數(shù)的方法可能更簡單,但當(dāng)系統(tǒng)中的狀態(tài)數(shù)量增多,狀態(tài)標(biāo)識(shí)也變多的時(shí)候,State模式就顯得尤其重要了。我們的邏輯處理類會(huì)從MachineBase派生,當(dāng)取出數(shù)據(jù)包后交給當(dāng)前狀態(tài)處理,前面描述的兩個(gè)狀態(tài)類從StateBase派生,每個(gè)狀態(tài)類只處理該狀態(tài)標(biāo)識(shí)下需要處理的消息。然后,按照State模式的描述,我們還需要一個(gè)Context類,也就是狀態(tài)機(jī)管理類,用以管理當(dāng)前的狀態(tài)類。}。如果使用全局緩沖區(qū)的話,那我們可以再進(jìn)一步,使用一個(gè)獨(dú)立的線程來處理數(shù)據(jù)發(fā)送,類似于邏輯線程對(duì)數(shù)據(jù)的處理方式,這個(gè)獨(dú)立發(fā)送線程也維護(hù)一個(gè)消息隊(duì)列,邏輯線程要發(fā)數(shù)據(jù)時(shí)也只是把數(shù)據(jù)加入到這個(gè)隊(duì)列中,發(fā)送線程循環(huán)取包來執(zhí)行send調(diào)用,這時(shí)的阻塞也就不會(huì)對(duì)邏輯線程有任何影響了??梢俏覀兪褂昧饲懊娼榻B的優(yōu)化方案后,可能這里便不再需要環(huán)形緩沖區(qū)了,至少我們并不再需要他們是環(huán)形的了。那要是追的人跑的太慢,跑的人轉(zhuǎn)了一圈過來反追上追的人了呢?那您也先歇會(huì)兒吧。Ghost Cheng的方案因?yàn)樘峁┝硕鄠€(gè)隊(duì)列,可以使得多個(gè)IO線程可以總工程師的,互不干擾的使用自己的隊(duì)列,只是還有一個(gè)遺留問題我們還不了解其解決方法。那這樣有時(shí)也會(huì)出現(xiàn)IO線程未寫滿一個(gè)隊(duì)列,而邏輯線程又沒有數(shù)據(jù)可處理的情況,特別是當(dāng)數(shù)據(jù)量很少時(shí)可能會(huì)很容易出現(xiàn)。實(shí)現(xiàn)雖然簡單,但功能是絕對(duì)滿足需求的,只是性能上可能稍稍有些不盡如人意。很簡單,讓那些獨(dú)立的IO線程在接收完數(shù)據(jù)后自己送過來就是了。當(dāng)然,也有把這些分享到單獨(dú)的進(jìn)程中去做的。只是,在對(duì)性能要求比較高的服務(wù)器上,select一般不會(huì)是最好的選擇。那我們就先來看看,游戲服務(wù)器代碼實(shí)現(xiàn)中,main函數(shù)都做了些什么。登錄服除了帳號(hào)驗(yàn)證外還得提供另一項(xiàng)功能,就是在玩家的帳號(hào)驗(yàn)證成功后返回給他一個(gè)服務(wù)器列表讓他去選擇。慢著,外掛偷密碼的目的是什么?是為了能用我們的帳號(hào)進(jìn)游戲!如果我們總是用一種固定的算法來對(duì)密碼做散列,那外掛只需要記住這個(gè)散列后的字串就行了,用這個(gè)做密碼就可以成功登錄。想象一下帳號(hào)驗(yàn)證的實(shí)現(xiàn)方法,最容易的那就是把用戶輸入的明文用帳號(hào)和密碼直接發(fā)給登錄服,服務(wù)器根據(jù)帳號(hào)從數(shù)據(jù)庫中取出密碼,與用戶輸入的密碼相比較。開始學(xué)習(xí)網(wǎng)絡(luò)編程的時(shí)候我犯過這樣的錯(cuò)誤,以為port的定義為unsigned short,所以想當(dāng)然的認(rèn)為服務(wù)器的最大連接數(shù)為65535,這會(huì)是一個(gè)硬性的限制。早期選擇UDP的主要原因還是帶寬限制,現(xiàn)在寬帶普通的情況下TCP比UDP多出來的一點(diǎn)點(diǎn)開銷與開發(fā)的便利性相比已經(jīng)不算什么了。服務(wù)器結(jié)構(gòu)探討一點(diǎn)雜談再強(qiáng)調(diào)一下,服務(wù)器結(jié)構(gòu)本無所謂好壞,只有是否適合自己。在之前描述的部分就如同舞臺(tái)上的演員,是我們能直接看到的,幕后的工作人員我們也來認(rèn)識(shí)一下。這其中最消耗系統(tǒng)資源的當(dāng)屬生物的AI處理了,尤其是那些復(fù)雜的尋路算法,所以我們可以考慮把這部分AI邏輯獨(dú)立出來,由一臺(tái)單獨(dú)的AI服務(wù)器來承擔(dān)。一般來說,當(dāng)某一部分能力達(dá)不到我們的要求時(shí),最簡單的解決方法就是在此多投入一點(diǎn)資源。你知道大陸現(xiàn)在有多少游戲在運(yùn)營嗎?或許你又該說,我們不該在一開始就把自己的目標(biāo)定的太低!好吧,我們還是先不談這個(gè)。如果一個(gè)演示socket API用的echo服務(wù)器就能滿足MMOG服務(wù)器的需求,那寫服務(wù)器該是件多么愜意的事啊??蛻舳酥恍枰B接到中心服上,所有到地圖服務(wù)器的數(shù)據(jù)都由中心服來轉(zhuǎn)發(fā)。先稱它為游戲世界的中心服務(wù)器吧,畢竟是管理者嘛,大家都以它為中心??梢赃@樣認(rèn)為,一塊地圖就是一個(gè)獨(dú)立的數(shù)據(jù)處理單元。這個(gè)激活的過程也就是從中心庫上把我們的帳號(hào)數(shù)據(jù)拷貝到所要到的大區(qū)帳號(hào)數(shù)據(jù)庫中。從數(shù)據(jù)庫服務(wù)器的部署上來說,可以將帳號(hào)和角色數(shù)據(jù)都放在一個(gè)中心數(shù)據(jù)庫中,也可分為兩個(gè)不同的庫分別來處理,基到從物理上分到兩臺(tái)不同的服務(wù)器上去也行。我們需要持久化的數(shù)據(jù)有玩家的帳號(hào)及密碼,玩家創(chuàng)建的角色相關(guān)信息,另外還有一些游戲世界全局共有數(shù)據(jù)也需要持久化。正如我們之前所描述過的那樣,登錄服在大多數(shù)情況下都是比較空閑的,也許我們的一個(gè)擁有20個(gè)游戲世界的大區(qū)僅僅使用10臺(tái)或更少的登錄服即可滿足需求。該結(jié)構(gòu)存在一個(gè)服務(wù)器資源配置的問題。但不管是采用何種方式進(jìn)入,照目前看來我們的服務(wù)器起碼得提供一個(gè)帳號(hào)驗(yàn)證的功能。第三篇:百萬用戶級(jí)游戲服務(wù)器架構(gòu)設(shè)計(jì)百萬用戶級(jí)游戲服務(wù)器架構(gòu)設(shè)計(jì)服務(wù)器結(jié)構(gòu)探討最簡單的結(jié)構(gòu)所謂服務(wù)器結(jié)構(gòu),也就是如何將服務(wù)器各部分合理地安排,以實(shí)現(xiàn)最初的功能需求。而且,以特殊字符做分隔的消息包定義還加大了一點(diǎn)點(diǎn)網(wǎng)絡(luò)數(shù)據(jù)量。決定了OS也就基本上確定了網(wǎng)絡(luò)IO模型,windows上的IOCP和linux下的epool,或者直接使用現(xiàn)有的網(wǎng)絡(luò)框架,如ACE和asio等,其他還有些商業(yè)的網(wǎng)絡(luò)庫在國內(nèi)的使用好像沒有見到,不符合中國國情嘛。至于搜集玩家機(jī)器資料所涉及到的法律問題不是我們?cè)摽紤]的。好比我們?nèi)タ匆粓鐾頃?huì),舞臺(tái)上演員們按著預(yù)定的節(jié)目單有序地上演著,但這就是整場晚會(huì)的全部嗎?顯然不止,在幕后還有太多太多的人在忙碌著,甚至在晚會(huì)前和晚會(huì)后都有。既然說到了優(yōu)化,我們也稍稍考慮一下現(xiàn)在結(jié)構(gòu)下可能采用的優(yōu)化方案。最后的結(jié)構(gòu)看起來大概是這樣的:Zone Server Zone ServerMaster Server Master ServerGateway Server Center ServerClient服務(wù)器結(jié)構(gòu)探討最終的結(jié)構(gòu)如果我們就此打住,可能馬上就會(huì)有人要嗤之以鼻了,就這點(diǎn)古董級(jí)的技術(shù)也敢出來現(xiàn)。事實(shí)上,在大多數(shù)游戲的大部分時(shí)間里,這個(gè)數(shù)字也是很讓人眼紅的。我知道,有人一定會(huì)說,才帶2000人,那是你水平不行,我隨便寫個(gè)TCP服務(wù)器都可帶個(gè)五六千連接。早期不少游戲也確實(shí)采用的就是這種簡單結(jié)構(gòu)。游戲的地圖服務(wù)器之間也是這么回事。回想一下我們?cè)鴳?zhàn)斗過無數(shù)個(gè)夜晚的暗黑破壞神,整個(gè)暗黑的世界被分為了若干個(gè)獨(dú)立的小地圖,當(dāng)我們?cè)诘貓D間穿越時(shí),一般都要經(jīng)過一個(gè)叫做傳送門的裝置。比如我們?cè)诠倬W(wǎng)上注冊(cè)了一個(gè)帳號(hào),這時(shí)帳號(hào)數(shù)據(jù)是只保存在中心數(shù)據(jù)庫上的。而且,通過DNS來實(shí)現(xiàn)的負(fù)載均衡已經(jīng)包含了所做的修改對(duì)登錄服及客戶端的透明。最后,有關(guān)數(shù)據(jù)持久化的問題也在這里考慮一下。這種結(jié)構(gòu)也就使得登錄服不是固定配備給個(gè)游戲世界,而是全區(qū)共有的。但是,如果玩家想要切換游戲世界,他只能先退出當(dāng)前游戲世界,然后進(jìn)入新的游戲世界重新進(jìn)行帳號(hào)驗(yàn)證。一般來說,我們?cè)诮尤胗螒蚍?wù)器的時(shí)候都會(huì)要提供一個(gè)帳號(hào)和密碼,驗(yàn)證通過后才能進(jìn)入。當(dāng)然,這也與早期2D游戲的技術(shù)要求相對(duì)比較簡單,游戲邏輯也沒有現(xiàn)在這般復(fù)雜有關(guān)。最后的理由有些俗了。在平時(shí)的開發(fā)中我也搜索過相關(guān)的中文網(wǎng)頁,很少有講游戲服務(wù)器相關(guān)技術(shù)的,大家的討論主要還是集中在3D相關(guān)技術(shù),所以也希望我將開始的這幾篇文章能夠起到拋磚引玉的作用,潛水的兄弟們也都上來透透氣。我所了解的早些的開發(fā)團(tuán)隊(duì),其成員間沒有什么嚴(yán)格的分工,大家憑興趣自由選擇一些模塊來負(fù)責(zé),完成了再去負(fù)責(zé)另一模塊,有其他同事的工作需要接手或協(xié)助的也會(huì)立即轉(zhuǎn)入。在這里,我們不打算對(duì)現(xiàn)有游戲結(jié)構(gòu)做評(píng)價(jià),而是試著從頭開始搭建一個(gè)我們需要的MMOG結(jié)構(gòu)。簡單點(diǎn)來實(shí)現(xiàn),我們完全可以拋棄這個(gè)大區(qū)的概念,認(rèn)為一個(gè)大區(qū)也就是放在同一個(gè)機(jī)房的多臺(tái)服務(wù)器組,各服務(wù)器組間沒有什么關(guān)系。另外在實(shí)際的游戲運(yùn)營中,有些游戲世界很火爆,而有些游戲世界卻非常冷清,甚至沒有多少人玩的情況也是很常見的。當(dāng)然,在這里也不會(huì)存在要求添加太多登錄服的情況。一般最常用,也最簡單部署的應(yīng)該是基于DNS的負(fù)載均衡系統(tǒng)了,其通過在DNS中為一個(gè)域名配置多個(gè)IP地址來實(shí)現(xiàn)。而每個(gè)游戲世界都有自己的游戲數(shù)據(jù)庫服務(wù)器,只允許本游戲世界內(nèi)的服務(wù)器連接。如縱隊(duì)、好友、公會(huì)、戰(zhàn)場和副本等,這些都是通過基本邏輯功能組合或擴(kuò)展而成。當(dāng)人數(shù)增加到三個(gè)時(shí),我們對(duì)等的合作關(guān)系可能會(huì)有些復(fù)雜,因?yàn)槲覀兠總€(gè)人都同時(shí)要與另兩個(gè)人合作協(xié)商。在整個(gè)游戲過程中,客戶端始終只會(huì)與一臺(tái)地圖服務(wù)器保持連接,當(dāng)要切換地圖的時(shí)候,在獲取到新地圖的地址后,會(huì)先與當(dāng)前地圖斷開連接,再進(jìn)入新的地圖,這樣保證玩家數(shù)據(jù)在服務(wù)器上只有一份。2000人,似乎我們的策劃朋友們不大愿意接受這個(gè)數(shù)字。仔細(xì)看一看這個(gè)需求,我們想要的僅僅只是一臺(tái)管理連接的服務(wù)器,并不打算讓他承擔(dān)太多的游戲邏輯。我們也不在這個(gè)名字上糾纏了,就按大家通用的叫法,還是稱他為網(wǎng)關(guān)服務(wù)器吧。而對(duì)于游戲服來說,有一臺(tái)還是多臺(tái)網(wǎng)關(guān)服是沒有什么區(qū)別的。最后是數(shù)據(jù)庫了,為了減輕數(shù)據(jù)庫的壓力,提高數(shù)據(jù)請(qǐng)求的響應(yīng)速度,我們可以在數(shù)據(jù)庫之前建立一個(gè)數(shù)據(jù)庫緩存服務(wù)器,將一些常用數(shù)據(jù)緩存在此,服務(wù)器與數(shù)據(jù)庫的通信都要通過這臺(tái)服務(wù)器進(jìn)行代理。在以時(shí)間收費(fèi)的游戲中,我們還需要一臺(tái)計(jì)費(fèi)的服務(wù)器,這臺(tái)服務(wù)器一般接在網(wǎng)關(guān)服務(wù)器上,注冊(cè)玩家登錄和退出事件以記錄玩家的游戲時(shí)間。接下來先說說我在開發(fā)中遇到過的一些困惑和一基礎(chǔ)問題探討吧,這些問題可能有人與我一樣,也曾遇到過,或者正在被困擾中,而所要探討的這些基礎(chǔ)問題向來也是爭論比較多的,我們也不評(píng)價(jià)其中的好與壞,只做簡單的描述。消息包格式定義包括三段,包長、消息碼和包體,爭論的焦點(diǎn)在于應(yīng)該是消息碼在前還是包長在前,我們也把這個(gè)當(dāng)作是信仰問題吧,有興趣的去云風(fēng)的blog上看看,論論。這個(gè)socket句柄才是描述每個(gè)連接的唯一標(biāo)識(shí)。如果我們把這兩項(xiàng)功能集成到一個(gè)服務(wù)進(jìn)程中,則最終的結(jié)構(gòu)很簡單:clientserver嗯,太簡單了點(diǎn),這樣也敢叫服務(wù)器結(jié)構(gòu)?好吧,現(xiàn)在我們來往里面稍稍加點(diǎn)東西,讓它看起來更像是服務(wù)器結(jié)構(gòu)一些。最后的結(jié)構(gòu)圖應(yīng)該像這樣:loginServergameServer|/|/client該結(jié)構(gòu)下的玩家操作流程為,先選擇大區(qū),再選擇大區(qū)下的某臺(tái)服務(wù)器,即某個(gè)游戲世界,點(diǎn)擊進(jìn)入后開始帳號(hào)驗(yàn)證過程,驗(yàn)證成功則進(jìn)入了該游戲世界。服務(wù)器結(jié)構(gòu)探討登錄服的負(fù)載均衡回想一下我們?cè)谕鎤ow時(shí)的操作流程:,首先就會(huì)要求我們輸入用戶名和密碼進(jìn)行驗(yàn)證,驗(yàn)證成功后才會(huì)出來游戲世界列表,之后是排隊(duì)進(jìn)入游戲世界,開始游戲...可以看到跟前面的描述有個(gè)很明顯的不同,那就是要先驗(yàn)證帳號(hào)再選擇游戲世界。另外,當(dāng)我們?cè)谠黾踊蛞瞥卿浄臅r(shí)候不應(yīng)該需要對(duì)游戲世界服有所改動(dòng),也不會(huì)要求重啟世界服,當(dāng)然也不應(yīng)該要求客戶端有什么更新或者修改,一切都是在背后自動(dòng)完成。當(dāng)然,如果找不到這樣的解決方案,自己從頭打造一個(gè)也并不難。wow中一共有八個(gè)大區(qū),我們想要進(jìn)入某個(gè)大區(qū)游戲之前,必須到官網(wǎng)上激活這個(gè)區(qū),這是為什么呢?一般來說,在各個(gè)大區(qū)帳號(hào)數(shù)據(jù)庫之上還有一個(gè)總的帳號(hào)數(shù)據(jù)庫,我們可以稱它為中心數(shù)據(jù)庫。決定了地圖的管理方式也就決定了我們的服務(wù)器結(jié)構(gòu),我們?nèi)匀幌葟淖詈唵蔚膶?shí)現(xiàn)方式開始說起。當(dāng)人數(shù)繼續(xù)增加,情況就變得不那么簡單了,我們得需要一個(gè)管理者來對(duì)我們的工作進(jìn)行分工、協(xié)調(diào)。但是簡單并不表示功能上會(huì)有什么損失,簡單也更不能表示游戲不能賺錢。這里有必要再解釋下這個(gè)數(shù)字。至少在現(xiàn)在來說,一個(gè)游戲世界內(nèi),也就是一組服務(wù)器內(nèi)同時(shí)有五六千個(gè)在線的玩家還是件讓人很興奮的事。我們可以試著對(duì)地圖進(jìn)行一些劃分,由一個(gè)Master Server來管理一些更小的Zone Server,玩家通過網(wǎng)關(guān)連接到Master Server上,而實(shí)際與地圖有關(guān)的邏輯是分派給更小的Zone Server去處理。當(dāng)然,這只是一種簡單的實(shí)現(xiàn),也是普通使用的一種方案,如果后期想對(duì)消息廣播做一些優(yōu)化的話,那可能才需要多考慮一下。好了,做完這些優(yōu)化我們的服務(wù)器結(jié)構(gòu)大體也就定的差不多了,暫且也不再繼續(xù)深入,更細(xì)化的內(nèi)容等到各個(gè)部分實(shí)現(xiàn)的時(shí)候再探討。從記錄玩家登錄的時(shí)間,地址,機(jī)器信息到游戲過程中的每一項(xiàng)操作都可以作為日志記錄下來,以備查錯(cuò)及數(shù)據(jù)挖掘用。如果真有權(quán)利去選擇的話,選自己最熟悉的吧。但實(shí)際上,我覺得這是完全沒有必要的,真要出現(xiàn)這樣的錯(cuò)誤,直接斷開這個(gè)客戶端的連接可能更安全。好了,廢話說完了,下一篇,我們開始進(jìn)入登錄服的設(shè)計(jì)吧。用戶在登錄時(shí)發(fā)送給服務(wù)器的是明文的帳號(hào)和經(jīng)散列后的不可逆密碼串,服務(wù)器取出密碼后也用同樣的算法進(jìn)行散列后再進(jìn)行比較。wow使用的是第6版,也就是SRP6算法。確實(shí)是太簡單了,不過簡單的結(jié)構(gòu)正好更適合我們來看一看游戲服務(wù)器內(nèi)部的模塊結(jié)構(gòu),以及一些服務(wù)器共有組件的實(shí)現(xiàn)方法。所以,mangos登錄服主循環(huán)的邏輯,也包括后面游戲服的邏輯,主循環(huán)的關(guān)鍵代碼其實(shí)是在SocketHandler中,也就是那個(gè)Select函數(shù)中。想象一下,因某個(gè)玩家上線而發(fā)起的一次數(shù)據(jù)庫查詢操作導(dǎo)致服務(wù)器內(nèi)所有在線玩家都卡住不動(dòng)將是多么恐怖的一件事!另外還有