http://www.infoq.com/cn/articles/game-server-development-3
在上一篇文章中,我們介紹了如何使用Pomelo來搭建聊天服務器。在這篇文章中,我們為大家介紹如何使用Pomelo框架來搭建MMO RPG服務器,并分析其設計思路和實現方法。以此來幫助大家更好的理解和使用Pomelo框架,理解Pomelo框架游戲開發的基礎流程,使用方法和設計理念。
本文中的游戲服務端架構,只是為了說明Pomelo的開發理念和設計思路,并不是基于Pomelo開發的唯一方案,開發者完全可以根據自己的實際應用環境設計不同的服務端架構。
開始之前
Pomelo框架與MMO RPG
我們曾在本系列第一篇文章曾介紹過pomelo的架構。我們先簡單回顧一下Pomelo為我們的游戲開發提供了什么:
相關贊助商
QCon北京2016大會,4月21-23日,北京·國際會議中心,精彩內容邀您參與!
- 可擴展的服務器架構。Pomelo中對服務器端進行了抽象,將服務器分為承載鏈連接的前端服務器和負責業務邏輯的后端服務器,并提供了便利,高效的分布式擴展支持,讓使用者可以在少改甚至不改的前提下實現對服務端的擴展。
- 完整的通信框架:Pomelo對客戶端與服務器之間,服務器與服務器之間的通信進行了完整的封裝。開發者只需要按照默認規則來填寫服務代碼就可以完成接口的發布和調用,而不用考慮內部實現。
- 大量游戲開發基礎庫:除了基本的服務器框架之外,Pomelo還提供了很多游戲開發需要用到的基礎庫,如任務調度,AI控制,尋路等,而這些庫還會隨著Pomelo的發展進一步完善。
- 基于Node.JS輕量級的開發環境,以及大量的模塊。相對于傳統的開發語言,Node.JS有著輕量,快捷的特性(0.3版中啟動一個包括10幾個服務器集成的服務端只需要不到4S)。而活躍的開源社區也提供了大量的第三方模塊。
作為Pomelo游戲開發的入門導引,本文的重點將放在游戲基礎架構的搭建上,因此本文將主要介紹下面三個方面的內容:游戲服務端的構建,與客戶端的通訊,服務器的擴展。
本文的參考示例
我們使用demo Treasures作為本文的參考示例,游戲的截圖如下:

從上圖可以看出,在treasures中,玩家會進入一張遍布寶物的地圖中,通過拾取寶物來獲得積分。所有玩家的積分在右上角會有一個排名。下面是這個demo的關鍵點:
- 每個玩家的行動對其他玩家來看都是實時的。
- 在獲取寶物時積分會更新積分榜,這個更新對所有玩家實時可見。
- 寶物會定時刷新。
相對于一般的的MMO RPG,這個demo顯得十分簡陋:沒有持久化,沒有戰斗,沒有AI。。。但是,其中實現了MMO RPG中最核心的亮點功能:一個可以容納多個在線玩家的游戲場景,以及玩家之間的實時互動。那些功能的確實可以讓系統的結構更加清晰明確,成為一個非常好的項目導引。
搭建游戲服務端
由于游戲邏輯十分簡單,我們后端采用一臺單獨的場景服務器來運行整個游戲邏輯,同時加入一臺連接服務器來承載用戶連接,系統的設計如下:

下面,我們就按照這個設計來搭建游戲服務端。
編寫場景服務
游戲場景是玩家所處的虛擬環境,而場景服務器就是游戲場景在服務端的抽象,根據游戲類型和內容的不同,場景服務器的復雜程度也會千差萬別。在我們的例子中,場景的構成十分簡單:一張開放的游戲地圖,地圖中的玩家,以及定時刷新的寶物。
首先,作為一個場景服務器,需要能夠儲存用戶和寶物的信息,這里我們直接使用一個放在內存中的map來儲存場景中的所有實體,同時,我們將所有場景中的實體都抽象為一個Entity對象,放在這個map中。
為了能夠操縱這些數據,還需要暴露出對外接口,我們使用的接口有下面三種:
- 初始化的接口:我們在init方法中會設置場景信息,配置參數等,并啟動場景中的時鐘循環。
- 實體訪問接口:如AddEntity和RemoveEntity接口等,我們使用這些接口來訪問和修改場景中的實體。
- 刷新場景中的寶物:當滿足條件時,外部事件會調用該接口來刷新地圖中的寶物。
我們通過一個無限循環的tick來驅動場景服務,在每一個tick中會更新場景中所有實體的狀態信息,我們最終設計如下:

搭建場景服務器
在完成場景服務的代碼之后,我們還需要提供一個場景服務運行的平臺,在Pomelo中,我們通過搭建一個場景服務器來實現。
Pomelo中的服務器分為兩類,負責承載用戶連接的前端服務器和運行邏輯的后端服務器。作為負責核心邏輯的場景服務器,自然是屬于后端服務器,因此,我們在/game-sever/config/server.json中加入以下配置:
"area": [ {"id": "area-server-1", "host": "127.0.0.1", "port": 3250, "areaId": 1} ]
其中的“area”是我們為場景服務器類型所起的名稱,其對應的內容就是場景服務器的列表,可以看出,現在我們只加入了單臺場景服務器。與聊天服務器相比,場景服務器的配置并沒有明顯區別,只是多了一個areaId的屬性。我們使用這一屬性來標明這個場景服務器對應的場景id,我們的建議設計是一個游戲服務器對應一個獨立的游戲場景,這樣可以大大減少場景管理的開銷,并提高單場景的負載能力。Pomelo中每一個服務器就是一個獨立的進程,相對于一個場景服務的開銷,單獨的服務器造成的開銷是可以接受的。當然,這只是建議設計,框架本身完全支持一個游戲服務器對應多個游戲場景的設計。開發者可以根據具體的應用情況調整場景服務器的配置,通過加入場景管理邏輯,實現一個場景服務器和場景之間的自由配置。
啟動場景服務
在加入場景服務器之后,我們還需要對服務端的運行環境進行配置,在場景服務器啟動時運行對應的場景服務。為了實現這一目標,我們需要在app.js中加入如下配置:
app.configure('production|development', 'area', function(){ var areaId = app.get('curServer').areaId; area.init(dataApi.area.findById(areaId)); });
app.configure方法用來對指定的服務器進行配置,包括三個參數,前兩個參數分別是運行環境和服務器類型的filter,第三個參數是在滿足前面兩個filter的情況下需要運行的代碼。在上面的配置中,第一個參數"production|development"表示是針對線上和開發兩種環境,之后的‘area’參數表示的是針對area服務器類型。 關于app.js初始化的更多內容,見 其中的標志位“frontend:true”表示這是一個前端服務器,“clientPort:3010”則表示該服務器對外暴露前端。對于前端服務器,在啟動時就會默認加載連接組件,因此我們不需要在app.js中進行額外的配置。在pomelo 0.3中,如果需要使用原生websocket等非默認的連接方式,則需要在app.js中加入相應配置。在客戶端,我們在啟動時連接對應的接口,就可以建立起與服務端的連接。
處理客戶端請求
Pomelo中,我們提供了兩種客戶端向服務端發送請求的方法,request/response模式和notify模式。
request/response 模式與web中的請求模式相似,是標準的請求/響應模式,對于客戶端的一個請求,服務端會給出一個響應。以玩家的移動為例,當需要移動時,會發送一個請求給服務端,服務端會驗證客戶端的請求,并返回結果客戶端,下圖是具體請求流程:

最上面的是客戶端代碼,在這里,我們向服務端發送一個移動請求。 之后,服務端會根據請求的route(area.playerHandler.move)找到對應的處理方法(/game-server/area/handler/playerHander.move),然后調用該方法來處理客戶端的請求。當處理完成之后,會并在next方法中傳入處理結果,結果會發回客戶端,并作為回調函數的參數傳回。
notify模式的運行流程與request/response類似,只是當服務端處理請求后不會發送任何響應。客戶端通過pomelo.notify方法來發出notify請求,notify請求的參數與pomelo.request相似,只是不需回調函數,notify方法的實例如下:
``` pomelo.notify('area.playerHandler.pick', params); ```
服務端消息推送
與web服務不同,game服務端會有大量的推送消息,要實現這一功能,我們使用pomelo中的push模式來實現。下圖以“move”為例,展示了服務端的推送流程:

Treasures中的廣播功能是通過一個全局的channel來實現的,在游戲中的所有玩家在加入游戲后都會加入一個全局的channel中。當需要廣播消失時,服務端就會調用這個全局的channel來對所有用戶進行消息推送。
擴展游戲服務端
在前面兩節中,我們使用pomelo搭建了一個單節點的游戲服務器。在這一節中,將介紹如何使用Pomelo來對服務端進行擴展,搭建分布式的游戲服務。
由于游戲服務器的復雜性,像web服務器簡單的水平擴展是不現實的,而根據業務邏輯的不同,不同的服務器也有著不同的擴展方案。因此我們分別以前面介紹的連接服務器和場景服務器為例,來對他們進行擴展。
連接服務器的擴展
連接服務器作用是負責維護所有客戶端的連接,負責客戶端消息的接受和推送,在MMO RPG中,連接服務器往往是性能的熱點之一,因此對連接服務器的擴展對于提高系統負載有很重要的現實意義。 在例子treasures中,我們通過加入一個負載均衡服務器(gate服務器),來實現連接服務器的擴展:當客戶端登錄時,會首先連接gate服務器,來分配一個連接服務器,之后,客戶端會斷開與gate服務器的連接,在與其對應的連接服務器建立連接,如下圖所示:

要實現這一功能,在服務端,我們首先要在server.config中加入新的前端服務器,代碼如下:
``` "gate": [ {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true} ] ```
之后,在gate服務器上編寫對應的負載均衡接口,在例子中,我們采用了使用uid的crc值對服務器數目取模的方式,代碼如下:
``` module.exports.dispatch = function(uid, connectors) { var index = Math.abs(crc.crc32(uid)) % connectors.length; return connectors[index]; }; ```
最后,在客戶端加入對應的連接代碼,就完成了對連接服務器的擴展。 在我們的例子中只用到了兩臺連接服務器,而在實際應用中,可以根據實際環境,編寫自己的負載均衡算法,加入更多的連接服務器。
場景服務器的擴展
與連接服務器不同,場景服務器中包含著大量的狀態信息,如果對單一的場景進行擴展,需要復雜的同步機制和大量遠程調用。因此,在Pomelo中,我們的場景擴展是通過加入新的場景來進行的。 在設計游戲時,整個世界就被分為多個不同的場景。而在服務器端,一個場景則與一個獨立的場景服務器相對應,場景服務的擴展可以通過加入新的場景來完成。下面,就以treasure為例,介紹一下場景服務的的擴展方法: 因為場景擴展是通過加入新的場景來完成的,所以首先要在server.json中加入新的場景服務器:
在加入場景服務器之后,我們還要保證發往場景服務器的請求可以被分發到正確的場景去。而Pomelo中,對于同一類型的服務器,默認的分法方法是采用隨機分配的方式,這是不能滿足我們要求的。因此,我們需要加入自己的路由算法,流程如下:

首先,我們編寫了新的路由算法:根據用戶所屬的場景id進行投遞。之后,我們在app.js中使用app.route方法來將我們的路由算法配置為針對area服務器的默認方法。app.route方法接受兩個參數,第一個是需要自定義route的服務器類型,第二個參數就是具體的路由算法。我們分別將‘area’服務類型和我們編寫的算法傳入,就可以實現自定義的路由功能了。
跨平臺客戶端
除了原有的JS客戶端之外,pomelo還提供了多種其他的客戶端,而不同的客戶端可能會對應著不同的長連接協議。但是經過pomelo封裝之后,在Pomelo服務端,不同客戶端在連接上連接上是完全對等的。因此,你可以使用pomelo框架實現跨平臺聯機對戰的(前提是你開發了針對多個平臺的游戲客戶端)。
最終架構
經過擴展后,我們的最終服務器架構如圖所示:

總結
在本文中,我們介紹了如何使用Pomelo框架來構建了一個包括連接服務器和場景服務器的MMO RPG服務端。并介紹了如何使用pomelo特性,來對游戲服務端進行擴展,并構建出了一個分布式的MMO RPG服務。 但是,這只是游戲服務端開發中最為基礎的部分,MMO RPG中很多基礎功能在這里都沒有實現,如數據持久化,AI控制,尋路系統等。在下面的文章中,我們會進一步介紹Pomelo在游戲開發中的應用。
參考示例
撿寶demo
Pomelo啟動流程
Pomelo框架
posted on 2016-03-14 09:14
思月行云 閱讀(392)
評論(0) 編輯 收藏 引用 所屬分類:
Node.js