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