游戲服務(wù)器架構(gòu)探討
登錄服在設(shè)計(jì)上應(yīng)該能滿足這種動(dòng)態(tài)增刪的需求,我們可以在任何時(shí)候?yàn)榇髤^(qū)增加或減少登錄服的部署。另外,當(dāng)我們?cè)谠黾踊蛞瞥卿浄臅r(shí)候不應(yīng)該需要對(duì)游戲世界服有所改動(dòng),也不會(huì)要求重啟世界服,當(dāng)然也不應(yīng)該要求客戶端有什么更新或者修改,一切都是在背后自動(dòng)完成。
中心服務(wù)器主要維護(hù)一張地圖ID到地圖服務(wù)器地址的映射表。當(dāng)我們要進(jìn)入某張地圖時(shí),會(huì)從中心服上取得該地圖的IP和port告訴客戶端,客戶端主動(dòng)去連接,這樣進(jìn)入他想要去的游戲地圖。在整個(gè)游戲過程中,客戶端始終只會(huì)與一臺(tái)地圖服務(wù)器保持連接,當(dāng)要切換地圖的時(shí)候,在獲取到新地圖的地址后,會(huì)先與當(dāng)前地圖斷開連接,再進(jìn)入新的地圖,這樣保證玩家數(shù)據(jù)在服務(wù)器上只有一份。
我們來(lái)看看結(jié)構(gòu)圖是怎樣的:
中心服務(wù)器
/
/
登錄服 地圖1 地圖2 地圖n
| / /
| / /
客戶端
很簡(jiǎn)單,不是嗎。但是簡(jiǎn)單并不表示功能上會(huì)有什么損失,簡(jiǎn)單也更不能表示游戲不能賺錢。早期不少游戲也確實(shí)采用的就是這種簡(jiǎn)單結(jié)構(gòu)。
??蛻舳酥恍枰B接到中心服上,所有到地圖服務(wù)器的數(shù)據(jù)都由中心服來(lái)轉(zhuǎn)發(fā)。
很完美的解決方案,不是嗎?
這種結(jié)構(gòu)在實(shí)際的部署中也遇到了一些挑戰(zhàn)。對(duì)于一般的MMORPG服務(wù)器來(lái)說(shuō),單臺(tái)服務(wù)器的承載量平均在2000左右,如果你的服務(wù)器很不幸地只能帶1000人,沒關(guān)系,不少游戲都是如此;如果你的服務(wù)器上跑了3000多玩家依然比較流暢,那你可以自豪地告訴你的策劃,多設(shè)計(jì)些大量消耗服務(wù)器資源的玩法吧,比如大型國(guó)戰(zhàn)、公會(huì)戰(zhàn)爭(zhēng)等。
還有聊天處理邏輯,這部分與游戲邏輯沒有任何關(guān)聯(lián),我們也完全可以將其獨(dú)立出來(lái),放到一臺(tái)單獨(dú)的聊天服務(wù)器上去實(shí)現(xiàn)。
在之前描述的部分就如同舞臺(tái)上的演員,是我們能直接看到的,幕后的工作人員我們也來(lái)認(rèn)識(shí)一下。
先從mangos的登錄服代碼開始。mangos的登錄服是一個(gè)單線程的結(jié)構(gòu),雖然在數(shù)據(jù)庫(kù)連接中可以開啟一個(gè)獨(dú)立的線程,但這個(gè)線程也只是對(duì)無(wú)返回結(jié)果的執(zhí)行類SQL做緩沖,而對(duì)需要有返回結(jié)果的查詢類SQL還是在主邏輯線程中阻塞調(diào)用的。
登錄服中唯一的這一個(gè)線程,也就是主循環(huán)線程對(duì)監(jiān)聽的socket做select操作,為每個(gè)連接進(jìn)來(lái)的客戶端讀取其上的數(shù)據(jù)并立即進(jìn)行處理,直到服務(wù)器收到SIGABRT或SIGBREAK信號(hào)時(shí)結(jié)束數(shù)據(jù)庫(kù)的處理也類似,會(huì)使用異步的方式,也是避免耗時(shí)的查詢過程將游戲服務(wù)器主循環(huán)阻塞住。想象一下,因某個(gè)玩家上線而發(fā)起的一次數(shù)據(jù)庫(kù)查詢操作導(dǎo)致服務(wù)器內(nèi)所有在線玩家都卡住不動(dòng)將是多么恐怖的一件事!
單個(gè)數(shù)據(jù)包在網(wǎng)上的格式是“len-type-data”。
前面一直都在說(shuō)接收數(shù)據(jù)時(shí)的處理方法,我們應(yīng)該用專門的IO線程,接收到完整的消息包后加入到主線程的消息隊(duì)列,但是主線程如何發(fā)送數(shù)據(jù)還沒有探討過。
考慮數(shù)據(jù)緩存的話,那這里這可以有兩種實(shí)現(xiàn)方式了,一是為每個(gè)玩家準(zhǔn)備一個(gè)緩沖區(qū),另外就是只有一個(gè)全局的緩沖區(qū),要發(fā)送的數(shù)據(jù)加入到全局緩沖區(qū)的時(shí)候同時(shí)要指明這個(gè)數(shù)據(jù)是發(fā)到哪個(gè)socket的。如果使用全局緩沖區(qū)的話,那 #p#page_title#e#
們可以再進(jìn)一步,使用一個(gè)獨(dú)立的線程來(lái)處理數(shù)據(jù)發(fā)送,類似于邏輯線程對(duì)數(shù)據(jù)的處理方式,這個(gè)獨(dú)立發(fā)送線程也維護(hù)一個(gè)消息隊(duì)列,邏輯線程要發(fā)數(shù)據(jù)時(shí)也只是把數(shù)據(jù)加入到這個(gè)隊(duì)列中,發(fā)送線程循環(huán)取包來(lái)執(zhí)行send調(diào)用,這時(shí)的阻塞也就不會(huì)對(duì)邏輯線程有任何影響了。
采用第二種方式還可以附帶一個(gè)優(yōu)化方案。一般對(duì)于廣播消息而言,發(fā)送給周圍玩家的數(shù)據(jù)都是完全相同的,我們?nèi)绻捎媒o每個(gè)玩家一個(gè)緩沖隊(duì)列的方式,這個(gè)數(shù)據(jù)包將需要拷貝多份,而采用一個(gè)全局發(fā)送隊(duì)列時(shí),我們只需要把這個(gè)消息入隊(duì)一次,同時(shí)指明該消息包是要發(fā)送給哪些socket的即可。有關(guān)該優(yōu)化的說(shuō)明在云風(fēng)描述其連接服務(wù)器實(shí)現(xiàn)的blog文章中也有講到,有興趣的可以去閱讀一下。
,有些類似于Observe模式中的觀察者,當(dāng)收到更新通知時(shí),我們只能更新自己的狀態(tài),對(duì)剛剛發(fā)生的事件我不已不能做任何影響