目錄
Pomelo
安裝Pomelo
創建demoserver項目
pomelo命令
項目結構說明
pomelo框架
架構
服務器實現
客戶端請求與響應、廣播的抽象介紹
Pomelo
pomelo是一個快速、可擴展、Node.js分布式游戲服務器框架,對游戲服務器開發感興趣的同學可以關注關注。
之前做頁游,端游和手游一直都是用的C++,或者C++ + lua,C++ + golang的方式來開發,說實話,C++用起來得心用手,有多少坑自己心里有點逼數,用的大都是騰訊系的框架庫,要么是盛大系的框架庫,底層比較穩定,即使如此,但一旦有個項目要立項,之前代碼不可換皮的情況下,你發現用C++開發還真的是笨重,因為你可能又得重新編一大堆的庫,或許編完就得大半天功夫,有些工具類庫可能還得自己去寫。
后來在2018年接手國外的一個游戲項目,老外借助gameSparks平臺開發,用的nodejs,這個時候我才關注nodejs,到現在已經2年了我不算是nodejs的開發老人,但也算是用nodejs開發了不少游戲項目,那么這里我給大家分享一個比較熱門,活躍的服務器框架pomelo。
用它的主要好處:
1.?入門簡單,有比較豐富的文檔和示例(雖然現在看版本也比較老了,但是入門沒什么問題)
2.分布式多進程且擴展簡單(單進程多線程,每個服務器都是一個Node進程,通過配置文件就可以管理集群)
3.可以不去關注底層和網絡相關邏輯,聚焦業務邏輯的處理,對于有Web服務器開發經驗卻沒有游戲服務器開發經驗來說還是比較友好的
4.提供了很多工具和客戶端支持(像IOS、Android & Java、Javascript、C、Cocos2d-x、U3D等)
?
安裝Pomelo
安裝要求
不管是windows還是linux 我更喜歡用源碼安裝,畢竟源碼時最新的,而且我也可以選擇版本來更新。
比如用命令簡單傻瓜操作:
npm install pomelo -g
如果用源碼安裝:
git clone https://github.com/NetEase/pomelo.git
cd pomelo
npm install -g
安裝完之后看到我的版本信息:
說明:Pomelo安裝可能出現各種失敗
1.?回頭去檢查一下,Python是否2.5 < version < 3.0和VC++編輯器是否有問題
2.如果以前全局安裝過Pomelo,最好刪除掉 “C:\Users\當前用戶\AppData\Roaming\npm\node_modules”目錄下Pomelo文件夾和“C:\Users\當前用戶\AppData\Roaming\npm-cache”目錄下Pomelo開頭的文件夾
3.如果并不報錯,npm卡住不動,多數是網絡原因,重復多安幾次;或者打開FQ工具試試;也可以用淘寶鏡像?cnpm?安裝.
創建demoserver項目
$ pomelo init ./demoserver
或者使用命令
$ mkdir demoserver
$ cd demoserver
$ pomelo init
在初始化項目過程中需要你選擇網絡連接協議:
Please select underly connector, 1 for websocket(native socket), 2 for socket.io, 3 for wss, 4 for socket.io(wss), 5 for udp, 6 for mqtt
除了5 for udp,其它都是長連接,我們接下來選擇1,至于這些協議之間有什么區別,請各位自行查找資料,這里不贅述了。
如果你不小心選擇網絡協議錯了,不要緊,你仍然有機會修改app.js的配置 :
如果你需要替換成wss,那么你只需要參考app.js.wss里的配置,修改里邊對應的位置:
項目初始化成功后轉到項目根目錄,執行安裝項目執行 npm-install.bat 依賴項 (其它平臺執行npm-install.sh),接下來我有必要說一下pomelo的命令,因為你在目前或者后期會經常和一些命令打交道。
pomelo命令
命令行工具pomelo是Pomelo框架提供的一個小工具,該工具能夠幫助開發者更便捷、更有效率地進行應用開發。該工具包括的命令支持絕大多數的應用開發操作,包括創建初始項目、啟動應用、停止應用、關閉應用等。
init
init: 創建一個新項目,該項目中包含創建pomelo應用的基本文件及pomelo應用的簡單示例。支持相對路徑和絕對路徑。默認情況下為當前路徑,項目名稱為當前文件夾名稱。
pomelo init? projectname
在創建新項目時,需要選擇新項目使用的與客戶端通信時使用的connector,1代表Websocket(native socket),2代表?socket.io。
start
start: 啟動應用及服務器。
pomelo start [-e,–env ] [-d,–directory?] [-D,–daemon] 命令格式
其中,-e 用來選擇啟動時使用的env,如production,development,stress-test等; -d 用來指定項目目錄; -D 用來開啟daemon模式啟動,如果開啟了daemon,那么進程將轉入后臺運行, 所有的日志將不再打印到console上,只能通過對應的日志文件查看日志。
list
list: 列出當前應用開啟的所有服務器的信息,包括服務器Id、服務器類型、pid、堆使用情況、啟動時長。
pomelo list [-u,–username ] [-p,–password ] [-h,–host ] [-P,–port ] 命令格式
當應用啟動后,該命令列出所有服務器信息。由于當執行此操作時,pomelo是作為監控管理框架的一個客戶端的,在連接注冊到master上的時候,需要進行身份驗證。默認生成的項目中,有一個默認的用戶名admin,口令也為admin,因此在不指定用戶名和口令的時候,默認使用的用戶名和口令均為admin,下面的stop命令和kill命令均需要使用用戶名和口令驗證,默認值與此處相同。應用的管理用戶可以通過修改config/adminUser.json文件進行配置;
執行本命令時,還需要指定master服務器的ip和port, 這樣可以是的pomelo list可以在任意地方執行。pomelo stop/kill/add等也同樣需要指定master服務器的ip和port,默認使用127.0.0.1:3005作為master服務器的地址。
4 . pomelo命令:stop
stop: 關閉應用及服務器或者停止指定的服務器。
pomelo stop [-u,–username ] [-p,–password ] [-h,–host ] [-P,–port ] […] 命令格式
stop用來停止當前應用,優雅地關閉應用。和kill命令不同,這種關閉首先會切斷客戶端與服務器的連接,然后逐一關閉所有服務器。如果指定了服務器serverId的話,則會關閉特定的服務器,而不是關閉所有的服務器。與list命令一樣,需要權限驗證,默認的用戶名和密碼均為admin,也需要指定master服務器的位置, 跟pomelo list一樣,默認使用127.0.0.1:3005。
kill
該命令需在項目的根目錄或game-server下使用;
kill: 強制關閉應用及服務器。
pomelo kill [-u,–username ] [-p,–password ] [-h,–host ] [-P,–port ] [-f,–force]
該命令強制關閉應用。在本地進行應用開發過程中,如果遇到kill之后還有服務器進程沒有關閉的情況,可以增加–force選項,強制關閉所有服務器進程。該操作相當地暴力,可能產生數據丟失等不好的影響,可以在開發調試時使用,不推薦在線上使用該命令。該命令同樣也需要進行身份驗證以及指定master服務器的位置,具體方式同list和stop。
6 . pomelo命令:add
add: 運行時動態添加服務器。pomelo add也需要身份驗證以及指定master服務器的地址。
pomelo add [-u,–username ] [-p,–password ] [-h,–host ] [-P,–port ] […]
args參數是用來指定新增服務器的參數的,包括服務器類型,服務器id等, 支持一次增加一臺或多臺同類型的服務器;示例如下:
pomelo add host=127.0.0.1 port=8000++ clientPort=9000++ frontend=true clusterCount=3 serverType=connector
pomelo add host=127.0.0.1 port=8000 clientPort=9000 frontend=true serverType=connector id=added-connector-server
masterha
masterha: 當啟用masterha高可用的時候,用來啟動master服務器的slave節點。需要在game-server/config目錄下配置masterha.json。其他的命令行參數類似于pomelo start;
pomelo masterha [-d,–direcotry?]
其他命令
–version:列出當前使用pomelo的版本信息。
–help:列出所有pomelo支持的命令及使用說明。
項目結構說明
game-server :??游戲服務器,所有游戲服務器功能和邏輯都在此目錄下
game-server/app.js:入口文件
game-server/app:?存放游戲邏輯和功能相關代碼都這個子目錄下,servers目錄下可以新建多個目錄來創建不同類型的服務器,在pomelo中,使用路徑來區分服務器類型
game-server/config:存放游戲服務器配置文件目錄,像日志、服務器、數據庫等幾乎所有配置文件都可以存放到此目錄下
game-server/logs:日志目錄,存放游戲服務器所有日志文件
web-server:? web服務器(如果你是個H5游戲,這里就是Web客戶端,如果是IOS、Andriod客戶端,這目錄就沒什么用)
shared:公共代碼存放處,這里要以放一些共用代碼
???啟動game-server
cd game-server
pomelo start
?
測試連接
? 1.啟動web-server
cd web-server
node app
修改web-server里的app.js :
打開瀏覽器:
測試gameserver
這里可以用我剛才說的pomelo命令查看下當前的服務器:
?
好了,你現在可以運行demo了,那么接下來就得開始循序漸進熟悉框架了
pomelo框架
架構
該架構把游戲服務器做了抽象, 抽象成為兩類:前端服務器和后端服務器, 如圖:
前端服務器(frontend)的職責:
>負責承載客戶端請求的連接
>維護session信息
>把請求轉發到后端
>把后端需要廣播的消息發到前端
后端服務器(backend)的職責:
>處理業務邏輯, 包括RPC和前端請求的邏輯
>把消息推送回前端
服務器實現
動態語言的面向對象有個基本概念叫鴨子類型 服務器的抽象也同樣可以比喻為鴨子, 服務器的對外接口只有兩類, 一類是接收客戶端的請求, 叫做handler, 一類是接收RPC請求, 叫做remote, handler和remote的行為決定了服務器長什么樣子。 因此我們只要定義好handler和remote兩類的行為, 就可以確定這個服務器的類型。
服務器抽象的實現
利用目錄結構與服務器對應的形式, 可以快速實現服務器的抽象。
以下是示例圖:
圖中的connector, connector, gate三個目錄代表三類服務器類型, 每個目錄下的handler與remote決定了這個服務器的行為(對外接口)。 開發者只要往handler與remote目錄填代碼, 就可以實現某一類的服務器。這讓服務器實現起來非常方便。 讓服務器動起來, 只要填一份配置文件servers.json就可以讓服務器快速動起來。 配置文件和對應的進行架構如下所示:
1 . gate服務器
一個應用的gate服務器,一般不參與rpc調用,也就是說其配置項里可以沒有port字段,僅僅有clientPort字段,它的作用是做前端的負載均衡。客戶端往往首先向gate服務器發出請求,gate會給客戶端分配具體的connector服務器。具體的分配策略一般是根據客戶端的某一個key做hash得到connector的id,這樣就可以實現各個connector服務器的負載均衡。
2 . connector服務器
connector服務器接收客戶端的連接請求,創建與客戶端的連接,維護客戶端的session信息。同時,接收客戶端對后端服務器的請求,按照用戶配置的路由策略,將請求路由給具體的后端服務器。當后端服務器處理完請求或者需要給客戶端推送消息的時候,connector服務器同樣會扮演一個中間角色,完成對客戶端的消息發送。connector服務器會同時擁有clientPort和port,其中clientPort用來監聽客戶端的連接,port端口用來給后端提供服務。
目前pomelo提供了hybridconnector和sioconnector,其中hybridconnector支持tcp,websocket; sioconnector支持socket.io。但是實際編程中,只有這些connector可能還無法滿足我們的需求,我們可能需要自己定制自己的connector,pomelo提供了定制connector的接口,我們將會在后邊的項目實例種用到。
3 . 應用邏輯服務器
gate服務器和connector服務器又都被稱作前端服務器,應用邏輯服務器是后端服務器,它完成實際的應用邏輯,提供服務給客戶端,當然客戶端的請求是通過前端服務器路由過來的。后端服務器之間也會通過rpc調用而有相互之間的交互。由于后端服務器不會跟客戶端直接有連接,因此后端服務器只需監聽它提供服務的端口即可。
4 . master服務器
master服務器加載配置文件,通過讀取配置文件,啟動所配置的服務器集群,并對所有服務器進行管理。
5 . rpc調用
pomelo中使用rpc調用進行進程間通信,在pomelo中rpc調用分為兩大類,使用namespace進行區分,namespace為sys的為系統rpc調用,它對用戶來說是透明的,目前pomelo中系統rpc調用有:
1.后端服務器向前端服務器請求session信息
2.后端服務器通過channel推送消息時對前端服務器發起的rpc調用
3.前端服務器將用戶請求路由給后端服務器時也是sys rpc調用
除了系統rpc調用外,其余的由用戶自定義的rpc調用屬于user namespace的rpc調用,需要用戶自己完成rpc服務端remote的handle代碼,并由rpc客戶端顯式地發起調用
6 . route,touter
route用來標識一個具體服務或者客戶端接受服務端推送消息的位置,對服務端來說,其形式一般是…,例如"chat.chatHandler.send", chat就是服務器類型,chatHandler是chat服務器中定義的一個Handler,send則為這個Handler中的一個handle方法。對客戶端來說,其路由一般形式為onXXX,當服務端推送消息時,客戶端會有相應的回調。 一般來說具體的同類型應用服務器都會有多個,當客戶端請求到達后,前端服務器會將用戶客戶端請求派發到后端服務器,這種派發需要一個路由函數router,可以粗略地認為router就是根據用戶的session以及其請求內容,做一些運算后,將其映射到一個具體的應用服務器id。可以通過application的route調用給某一類型的服務器配置其router。如果不配置的話,pomelo框架會使用一個默認的router。pomelo默認的路由函數是使用session里面的uid字段,計算uid字段的crc32校驗碼,然后用這個校驗碼作為key,跟同類應用服務器數目取余,得到要路由到的服務器編號。注意這里有一個陷阱,就是如果session沒有綁定uid的話,此時uid字段為undefined,可能會造成所有的請求都路由到同一臺服務器。所以在實際開發中還是需要自己來配置router。
7 . Channel
channel可以看作是一個玩家id的容器,主要用于需要廣播推送消息的場景。可以把某個玩家加入到一個Channel中,當對這個Channel推送消息的時候,所有加入到這個Channel的玩家都會收到推送過來的消息。一個玩家的id可能會被加入到多個Channel中,這樣玩家就會收到其加入的Channel推送過來的消息。需要注意的是Channel都是服務器本地的,應用服務器A和B并不會共享Channel,也就是說在服務器A上創建的Channel,只能由服務器A才能給它推送消息。
8 . request, response, notify, push
pomelo中有四種消息類型的消息,分別是request,response,notify和push,客戶端發起request到服務器端,服務器端處理后會給其返回響應response;notify是客戶端發給服務端的通知,也就是不需要服務端給予回復的請求;push是服務端主動給客戶端推送消息的類型。
9 . filter
filter分為before和after兩類,每類filter都可以注冊多個,形成一個filter鏈,所有的客戶端請求都會經過filter鏈進行一些處理。before filter會對請求做一些前置處理,如:檢查當前玩家是否已登錄,打印統計日志等。after filter是進行請求后置處理的地方,如:釋放請求上下文的資源,記錄請求總耗時等。after filter中不應該再出現修改響應內容的代碼,因為在進入after filter前響應就已經被發送給客戶端。
10 . handler
handler是實現具體業務邏輯的地方,在請求處理流程中,它位于before filter和after filter之間,handler的接口聲明如下:
handler.methodName = function(msg, session, next) {
// …
}
參數含義與before filter類似。handler處理完畢后,如有需要返回給客戶端的響應,可以將返回結果封裝成js對象,通過next傳遞給后面流程。
11 . error handler
error handler是一個處理全局異常的地方,可以在error handler中對處理流程中發生的異常進行集中處理,如:統計錯誤信息,組織異常響應結果等。error handler函數是可選的,格式如下:
app.set(‘errorHandler’, handleFunc);
來向pomelo框架進行注冊,函數聲明如下:
errorHandler = function(err, msg, resp, session, next) {
// …
}
err是前面流程中發生的異常;resp是前面流程傳遞過來,需要返回給客戶端的響應信息。
12 . component
pomelo 框架是由一些松散耦合的component組成的,每個component完成一些功能。每個component往往有start,afterStart,stop等調用,用來完成生命周期管理。
13 . admin client, monitor, master
monitor運行在各個應用服務器中,它會向master注冊自己,向master上報其服務器的信息,當服務器群有變化時,接收master推送來的變化消息,更新其服務器上下文。
master運行在應用服務器中,它會收集整個服務器群的信息,有變化時會將變化推送到各個monitor;同時,master還接受admin client的請求,按照client發出的命令,執行對應的操作。
client獨立運行自己的進程,它會發起到master的連接,然后通過對master發出請求或者命令,來管理整個服務器群。
14 . admin module
在pomelo中,module特指服務器監控管理模塊,實現的是監控邏輯,比如收集進程狀態等。用戶在使用時,可以通過application的registerAdmin注冊管理模塊,實現自己定制的監控管理功能。每一個module中都會定義可選的四種回調函數:
- masterHandler(agent, msg, cb) 當有應用服務器給master發監控數據時,這個回調函數會由master進程進行回調,完成應用服務器的消息處理;
- monitorHandler(agent, msg, cb) 當有master請求應用服務器的一些監控信息時,由應用服務器進行回調,完成對master請求的處理;
- clientHandler(agent, msg, cb)當由管理客戶端向master請求服務器群信息時,由master進程進行回調處理客戶端的請求。
- start(cb) 當admin module,注冊加載完成后,這個回調會被執行,在這里可以做一些初始化工作。
客戶端請求與響應、廣播的抽象介紹
所有的web應用框架都實現了請求與響應的抽象。盡管游戲應用是基于長連接的, 但請求與響應的抽象跟web應用很類似。 下圖的代碼是一個request請求示例:
請求的api與web應用的ajax請求很象,基于Convention over configuration的原則, 請求不需要任何配置。 如下圖所示,請求的route字符串:chat.chatHandler.send, 它可以將請求分發到chat服務器上chatHandler文件定義的send方法。
Pomelo的框架里還實現了request的filter機制,廣播/組播機制,詳細介紹見pomelo框架參考。
服務器間RPC調用的抽象介紹
架構中各服務器之間的通訊主要是通過底層RPC框架來完成的,該RPC框架主要解決了進程間消息的路由和RPC底層通訊協議的選擇兩個問題。 服務器間的RPC調用也實現了零配置。實例如下圖所示:
上圖的remote目錄里定義了一個RPC接口: chatRemote.js,它的接口定義如下:
chatRemote.kick = function(uid, player, cb) {}
其它服務器(RPC客戶端)只要通過以下接口就可以實現RPC調用:
app.rpc.chat.chatRemote.kick(session, uid, player, function(data){});
?這個調用會根據特定的路由規則轉發到特定的服務器。(如場景服務的請求會根據玩家在哪個場景直接轉發到對應的server)。
rpc的使用遠比其它rpc框架簡單好多,因為我們無需寫任何配置文件,也無需生成stub。因為我們服務器抽象的實現的方式,使得rpc客戶端可以在應用啟動時掃描服務器目錄自動生成stub對象。
完成了以上三個目標, 一個實時的分布式應用框架的輪廓就搭出來了。接下來我們在下一章節里說明下在當前demoserver的基礎上不斷地擴充,豐富它的功能。
?