使用GoWorld遊戲服務器引擎輕鬆實現分佈式聊天服務器

GoWorld遊戲服務器引擎簡介

GoWorld是一款開源的分佈式可擴展的遊戲服務器引擎,使用Go語言(Golang)編寫。它採用相似BigWorld的結構,使用了簡化的場景-對象框架。以一個典型的MMORPG爲例,每一個服務器上會有多個場景,每一個場景裏能夠包含多個對象,這些對象包括玩家、NPC、怪物等。GoWorld服務器能夠將場景分配到在不一樣的進程甚至不一樣的機器上,從而使得遊戲服務器的負載是可擴展的。git

開源分佈式遊戲服務器引擎:https://github.com/xiaonanln/goworld,歡迎賞星,共同窗習 也可在GitHub Wiki查看本文github

聊天室是遊戲裏很是常見的一個功能,例如一個MMORPG遊戲裏會有世界聊天室、職業聊天室、幫派聊天室等。GoWorld爲此提供了很是簡單且高效率的支持,使得開發者能夠輕鬆實現分佈式的聊天室功能。這裏咱們就是嘗試使用GoWorld所提供的功能實現一個分佈式可擴展的聊天服務器。shell

聊天室功能說明

咱們要實現的聊天室包含如下的一些功能:數據庫

  • 註冊
  • 登陸
  • 說話
  • 切換聊天室

爲了面向更廣大的遊戲開發者,咱們使用Cocos Creater 1.5.2開發聊天室客戶端,客戶端編程語言使用Javascript。 因爲這僅僅只是一個Demo,所以服務端和客戶端的功能都相對簡單,可是依然用到了GoWorld的一些很是卓越的特性。編程

使用GoWorld開發分佈式聊天服務器

安裝Go 1.8.3

編譯GoWorld須要安裝Golang 1.8.3,請確保本身的Go的版本足夠新。服務器

得到GoWorld開源遊戲服務器

運行如下的命令得到GoWorld遊戲服務器引擎。因爲GoWorld依賴其餘較多的外部庫,所以這個過程可能須要花費一點時間。併發

go get -u github.com/xiaonanln/goworld

編寫聊天服務端代碼

咱們在%GOPATH%/src/github.com/xiaonanln/goworld/examples/chatroom_demo目錄中開發聊天室服務端。能夠經過地址 https://github.com/xiaonanln/goworld/tree/master/examples/chatroom_demo 查看全部的服務端代碼。框架

聊天服務器主函數

使用GoWorld開發服務端須要開發者提供main函數入口。main函數通常會註冊幾個自定義的類型,而後調用goworld.Run進入遊戲服務端主循環。編程語言

// serverDelegate 定義一些遊戲服務器的回調函數(必須提供) type serverDelegate struct { game.GameDelegate } func main() { goworld.RegisterSpace(&MySpace{}) // 註冊自定義的Space類型(必須提供) // 註冊Account類型 goworld.RegisterEntity("Account", &Account{}, false, false) // 註冊Avatar類型,並定義屬性 goworld.RegisterEntity("Avatar", &Avatar{}, true, true).DefineAttrs(map[string][]string{ "name": {"Client", "Persistent"}, "chatroom": {"Client"}, }) // 運行遊戲服務器 goworld.Run(&serverDelegate{}) }

如上所示,main函數的邏輯很是簡單。首先註冊一個自定義的場景對象類型MySpace,這個是GoWorld強制要求的,不然運行會報錯。而後main註冊了兩個實現聊天服務器邏輯的對象類型(Account和Avatar)。Account負責註冊和登陸流程,Avatar負責玩家聊天邏輯。這兩個類型的具體實如今下文繼續詳述。最後main調用goworld.Run運行遊戲服務器的主循環。分佈式

自定義場景類型MySpace

GoWorld引擎要求咱們必須在main裏註冊一個自定義的場景類型。因爲聊天服務器沒有任何場景邏輯,所以這個類型也沒有任何具體的代碼實現。場景能夠幫助實現MMORPG遊戲、或者開房間類型的遊戲,可是對於一個簡單的聊天服務器來講並無什麼做用。所以咱們只定義並註冊一個空的場景類型便可。

// MySpace 是一個自定義的場景類型 // // 因爲聊天服務器沒有任何場景邏輯,所以這個類型也沒有任何具體的代碼實現 type MySpace struct { entity.Space // 自定義的場景類型必須繼承一個引擎所提供的entity.Space類型 }

帳號對象類型Account

帳號類型定義以下。全部的自定義對象類型都必須繼承entity.Entity。當有玩家客戶端鏈接服務器的時候,服務器就會自動建立一個Account對象,而且將新的客戶端做爲Account對象的客戶端。在GoWorld引擎中,每一個對象均可以有最多一個客戶端對象。這樣,服務端對象就能夠經過RPC調用客戶端對象的一些函數,並經過屬性機制更新客戶端對象的一些屬性。

// Account 是帳號對象類型,用於處理註冊、登陸邏輯。 type Account struct { entity.Entity // 自定義對象類型必須繼承entity.Entity logining bool }

帳號註冊

當客戶端點擊註冊的時候,就會給服務端發送一個註冊的RPC請求。Account對象須要定義一個函數(Register_Client)來接受這個RPC請求,以下所示。函數名末尾的_Client表明這是一個能夠由客戶端調用的RPC函數。Register_Client函數使GoWorld引擎提供的方便的KVDB模塊進行帳號-密碼數據的存儲和讀取。當帳號密碼不存在的時候,就在KVDB中插入新的帳號和密碼。註冊過程在建立新帳號的同時,建立一個Avatar對象,而後馬上銷燬。這是爲了在數據庫中生成新的Avatar對象的數據,並得到其惟一的ID(avatarID)並將Avatar的ID也存入到KVDB中,和這個帳號進行綁定。

func (a *Account) Register_Client(username string, password string) { goworld.GetKVDB("password$"+username, func(val string, err error) { if val != "" { a.CallClient("ShowError", "這個帳號已經存在") return } goworld.PutKVDB("password$"+username, password, func(err error) { avatarID := goworld.CreateEntityLocally("Avatar") // 建立一個Avatar對象而後馬上銷燬,產生一次存盤 avatar := goworld.GetEntity(avatarID) avatar.Attrs.Set("name", username) avatar.Destroy() goworld.PutKVDB("avatarID$"+username, string(avatarID), func(err error) { a.CallClient("ShowInfo", "註冊成功,請點擊登陸") }) }) }) }

帳號登陸

Account對象使用Login_Client處理來自客戶端的登陸請求,以下所示。 首先,從KVDB中得到正確的帳號和密碼並和玩家所提供的密碼進行比較。若是密碼正確,咱們再次使用KVDB得到帳號所對應的Avatar ID,並使用這個Avatar ID開始從數據庫裏載入Avatar對象。

func (a *Account) Login_Client(username string, password string) { goworld.GetKVDB("password$"+username, func(correctPassword string, err error) { if correctPassword == "" { a.CallClient("ShowError", "帳號不存在") return } if password != correctPassword { a.CallClient("ShowError", "密碼錯誤") return } goworld.GetKVDB("avatarID$"+username, func(_avatarID string, err error) { avatarID := common.EntityID(_avatarID) goworld.LoadEntityAnywhere("Avatar", avatarID) a.Call(avatarID, "GetSpaceID", a.ID) }) }) }

這裏咱們使用goworld.LoadEntityAnywhere函數載入Avatar對象。在一個分佈式服務器中,Avatar對象可能在任意一個服務端邏輯進程中建立。所以在這種狀況下,Account向剛載入的Avatar對象發起一次GetSpaceID請求,試圖得到Avatar對象所在的場景。Avatar對象須要定義GetSpaceID函數來處理請求,並把本身所在的場景ID發送給Account對象,代碼以下所示。和上面的_Client結尾函數不一樣的是,這裏的RPC調用者和接受者都是服務端的對象,所以不須要提供_Client標記。

func (a *Avatar) GetSpaceID(callerID EntityID) { a.Call(callerID, "OnGetAvatarSpaceID", a.ID, a.Space.ID) }

Account對象在收到OnGetAvatarSpaceID回調以後,能夠經過EnterSpace請求讓本身遷移到Avatar對象所在的進程,代碼以下所示。場景切換是GoWorld所提供的強大的對象操做功能,它使得服務端的對象能夠在各個場景裏方便的切換,大幅度簡化了開發者實現分佈式服務的開發難度。

func (a *Account) OnGetAvatarSpaceID(avatarID common.EntityID, spaceID common.EntityID) { // 若是發現Avatar對象和Account對象在同一個服務器,則不須要進行場景切換 avatar := goworld.GetEntity(avatarID) if avatar != nil { a.onAvatarEntityFound(avatar) return } a.Attrs.Set("loginAvatarID", avatarID) a.EnterSpace(spaceID, entity.Position{}) }

Account對象在切換場景結束以後,再次在當前邏輯進程裏尋找指定的Avatar對象。而後調用onAvatarEntityFound函數完成最後的登陸邏輯,也就是經過GiveClientTo函數把Account當前的客戶端鏈接移交給Avatar對象,而後Account對象由於失去客戶端而被銷燬。

func (a *Account) OnMigrateIn() { loginAvatarID := common.EntityID(a.Attrs.GetStr("loginAvatarID")) avatar := goworld.GetEntity(loginAvatarID) if avatar != nil { a.onAvatarEntityFound(avatar) } else { // failed a.CallClient("ShowError", "登陸失敗,請重試") a.logining = false } } func (a *Account) onAvatarEntityFound(avatar *entity.Entity) { a.GiveClientTo(avatar) } // OnClientDisconnected 會在對象失去客戶端的時候被調用 func (a *Account) OnClientDisconnected() { a.Destroy() }

Account對象在載入Avatar對象並完成登陸的過程彷佛有些複雜,涉及到Avatar對象載入,兩次RPC調用以及一次對象遷移。不過GoWorld所提供的機制使得咱們能夠方便地將Avatar對象建立到各個不一樣的服務器進程中。

Avatar對象邏輯

Avatar對象表明一名已經登陸的聊天室玩家。和上述的Account對象同樣,咱們首先須要定義一個Avatar類型。

定義與初始化

// Avatar 對象表明一名玩家 type Avatar struct { entity.Entity } // OnCreated 函數會在對象建立結束的時候調用 func (a *Avatar) OnCreated() { a.Entity.OnCreated() a.setDefaultAttrs() } func (a *Avatar) setDefaultAttrs() { a.Attrs.Set("chatroom", "1") a.SetFilterProp("chatroom", "1") }

當Avatar對象載入成功以後,咱們會爲它設置默認的聊天室。

經過使用a.Attrs.Set將Avatar對象的chatroom屬性設置爲1。屬性機制是GoWorld所提供的一種存儲對象信息,並提供對象數據自動存盤、自動同步到客戶端的機制。所以服務端對象在設置chatroom屬性的同時,客戶端也會收到這個屬性的更新,並同步到UI界面上。

而後Avatar對象使用SetFilterProp函數設置本身的一個filter屬性:chatroom = 1。Filter屬性機制是GoWorld爲了高效率地實現遊戲裏各類聊天室所提供的客戶端過濾和通知機制。服務端可使用Filter屬性機制向全部知足filter屬性要求的對象的客戶端發起廣播,這樣的效率要遠遠優於一個個對一個對象進行掃描併發送客戶端RPC。

說話和切換聊天室

Avatar對象提供SendChat_Client函數來處理來自客戶端的說話請求,以下所示。

func (a *Avatar) SendChat_Client(text string) { text = strings.TrimSpace(text) if text[0] == '/' { // this is a command cmd := spaceSep.Split(text[1:], -1) if cmd[0] == "join" { a.enterRoom(cmd[1]) } else { a.CallClient("ShowError", "沒法識別的命令:"+cmd[0]) } } else { a.CallFitleredClients("chatroom", a.GetStr("chatroom"), "OnRecvChat", a.GetStr("name"), text) } }

SendChat_Client把以/開頭的內容看成一個命令,並進行特殊處理。其餘內容則做爲普通的說話內容,並經過調用引擎Filter屬性機制所提供的CallFitleredClients函數將說話人的名字和內容都發送到全部在當前聊天室的玩家客戶端。

若是玩家發了一個/join ...的命令,則會被當作一個切換聊天室的請求。切換聊天室的邏輯很是簡單,只須要將聊天室名字設置爲新的Filter屬性值,並設置爲玩家眷性從而更新到客戶端便可。

func (a *Avatar) enterRoom(name string) {
	a.SetFilterProp("chatroom", name)
	a.Attrs.Set("chatroom", name)
}

聊天室客戶端

聊天室客戶端的代碼都在:https://github.com/xiaonanln/goworld-chatroom-demo-client ,由Javascript編寫。客戶端代碼除了對服務端通訊協議進行解析和封裝以外,其餘界面邏輯很是簡單,所以這裏再也不詳述。另外在http://goworldgs.com/chatclient/上也有一個可運行的客戶端和服務端實現,有興趣的能夠點開查看。

編譯運行服務端

一個完整的GoWorld服務器包含三個部分:中心分發器、網關服務器和邏輯服務器。咱們剛纔所編寫的代碼全是邏輯服務器的代碼,中心分發器和網關服務器是固定的程序,直接編譯運行便可。

編譯中心分發器dispatcher

cd %GOPATH%/src/github.com/xiaonanln/goworld/components/dispatcher
go build

編譯網關服務器

cd %GOPATH%/src/github.com/xiaonanln/goworld/components/gate
go build

編譯chatroomdemo遊戲服務器

cd %GOPATH%/src/github.com/xiaonanln/goworld/examples/chatroom_demo
go build

設置GoWorld配置文件

咱們使用goworld根目錄下的goworld.ini.sample做爲遊戲服務器的配置文件。

cd %GOPATH%/src/github.com/xiaonanln/goworld
cp goworld.ini.sample goworld.ini

配置文件設置了KVDB所使用的數據庫類型(默認爲MongoDB)、Avatar對象數據庫所使用的數據庫類型(默認爲MongoDB),以及dispatcher、gate、game所使用的各類配置。若是使用MongoDB做爲KVDB和對象數據庫,請另外安裝和運行MongoDB 3.x。

運行服務器

cd %GOPATH%/src/github.com/xiaonanln/goworld
nohup components/dispatcher/dispatcher & nohup components/gate/gate -gid 1 & nohup examples/chatroom_demo/chatroom_demo -gid 1 & 

運行客戶端

在Cocos Creater中設置全部Scene中的GoWorld對象的地址爲localhost,端口爲網關服務器(gate1)的http端口,默認15012,而後運行客戶端便可鏈接到本地服務器。

總結

如上所述,在使用GoWorld所提供的分佈式場景-對象框架和其餘功能的狀況下,咱們能夠輕鬆開發出一個分佈式可擴展的聊天室服務端。不過GoWorld所提供的功能更適合開發分場景、分房間的遊戲類型。另外GoWorld所提供的熱更新功能對於大型的遊戲服務端項目來講也是必不可少的。

開源分佈式遊戲服務器引擎:https://github.com/xiaonanln/goworld,歡迎賞星,共同窗習

對Go語言服務端開發感興趣的朋友歡迎加入QQ討論羣:662182346

相關文章
相關標籤/搜索