Go語言網絡游戲服務器模塊化編程

本文以使用origin框架(一款使用Go語言寫的開源游戲服務器框架)為例進行說明,當然也可以使用其它的框架或者自己寫。

在框架中PBProcessor用來處理Protobuf消息,在使用之前,需要使用Register函數注冊網絡消息:

func (pbProcessor *PBProcessor) Register(msgType uint16, msg proto.Message, handle MessageHandler) {var info MessageInfoinfo.msgType = reflect.TypeOf(msg.(proto.Message))info.msgHandler = handlepbProcessor.mapMsg[msgType] = info
}

網絡消息來時通過MsgRoute進行分發:

func (pbProcessor *PBProcessor) MsgRoute(clientId string, msg interface{}, recyclerReaderBytes func(data []byte)) error {pPackInfo := msg.(*PBPackInfo)defer recyclerReaderBytes(pPackInfo.rawMsg)v, ok := pbProcessor.mapMsg[pPackInfo.typ]if ok == false {return fmt.Errorf("cannot find msgtype %d is register", pPackInfo.typ)}v.msgHandler(clientId, pPackInfo.msg)return nil
}

這是框架提供的基礎功能。在平常使用中最方便的就是游戲中各個功能模塊相互獨立,減少耦合性。

Go語言中支持包,可以將包看作一個模塊,進行模塊化編程。游戲中常見的操作是玩家數據的存取以及網絡消息處理,可以定義接口:

package common// 游戲邏輯模塊的存取
type ISaveLoad interface {// 由于每個模塊使用的PB不一樣,在使用InitFromPB之前需要調用NewPB創建PBNewPB() proto.Message// 從PB中讀取模塊數據InitFromPB(pb proto.Message)// 完成數據讀取后的處理,主要解決模塊數據的相互依賴,比如模塊A可能會依賴模塊B,但模塊B的數據可能還沒讀取出來PostLoad(pb proto.Message)// 保存模塊數據到PB中,bSave2DB用于判斷是存數據庫,還是發送給客戶端,可能存在有些數據不能發給客戶端,可以通過此變量進行處理Save2PB(bSave2DB bool) proto.Message
}// 游戲邏輯模塊
type IGameModule interface {ISaveLoad// 獲取模塊ID,這些ID都可以寫在PB中,客戶端、服務器共用,玩家上線時,服務器可以根據模塊ID發送數據給客戶端GetModuleID() netmsg.ModuleID
}// 游戲邏輯模塊的工廠模式
type IModuleFactory interface {// 新建模塊,每個模塊中都有一個IRole歸屬,方便使用角色中的其它模塊數據NewModule(owner IRole) IModule// 注冊模塊中的網絡消息RegNetMsg()
}// 游戲角色
type IRole interface {// 賬號IDGetUserID() uint64// 角色IDGetRoleID() uint64// 發消息給客戶端SendMsg2Client(cmdId netmsg.NetCmdID, pb proto.Message)// 獲取角色中的游戲模塊GetGameModule(ID netmsg.ModuleID) IGameModule
}

定義好接口后,就可以將Go的包當然游戲模塊編寫邏輯了,這樣寫的邏輯會比較清晰。

下面以背包模塊為例來說明,創建一個bag目錄來作為游戲邏輯模塊,里面再分文件來區分是模塊注冊(bag_mod.go),模塊IO(bag_io.go),模塊邏輯(bag.go)等等,為什么里面還要這么分,是因為當一個模塊比較大時,定位起來方便,比如在實際開發中經常需要定位要IO部分,可能需要修改與客戶端的通信。

bag.go

package bagtype bag struct{owner common.IRolecap uint16
}func newBag(owner common.IRole) {return &bag{owner: owner}
}func (slf *bag) addItem(pb *netmsg.BagAddItem) {
}

bag_mod.go

package bagfunc init() {// 這里向模塊管理器注冊模塊,模塊管理器會調用factory的NewModule創建模塊,調用RegNetMsg注冊網絡消息mod.RegModule(netmsg.ModuleID_Bag, factory{})
}type factory struct {
}func (f factory) NewModule(owner common.IRole) common.IGameModule {return newBag(owner)
}// 注冊本模塊中的所有網絡消息處理函數
func (f factory) RegNetMsg() {mod.RegNetMsg(netmsg.NetCmdID_AddItem, onAddItem)
}func onAddItem(p *bag, pb *netmsg.BagAddItem) {p.addItem(pb)
}

bag_io.go

package bagfunc (slf *Bag) GetModuleID() netmsg.ModuleID {return netmsg.ModuleID_Bag
}func (slf *Bag) NewPB() proto.Message {return &netmsg.Bag{}
}func (slf *Bag) InitFromPB(pb proto.Message) {msg := pb.(*netmsg.Bag)slf.cap = msg.Cap
}func (slf *Bag) Save2PB(isSave2DB bool) proto.Message {return &netmsg.Bag{Cap: slf.cap}
}func (slf *Bag) PostLoad(pb proto.Message) {
}

前面代碼中有使用到mod包,它是模塊的管理包。

mod.go

package modvar (modules   = map[netmsg.ModuleID]common.IModuleFactory{}mapNetMsg = map[netmsg.NetCmdID]netmsg.ModuleID{}process   *processor.PBProcessormodType   netmsg.ModuleID
)// 供各邏輯模塊調用以注冊模塊
func RegModule(moduleID netmsg.ModuleID, module common.IModuleFactory) {if _, ok := modules[id]; ok {log.Fatalf("Repeated RegModule Module ID:%s", moduleID.String())return}modules[id] = module
}// 供Service調用以注冊各模塊的網絡消息
func RegModuleNetMsg(p *processor.PBProcessor) {// 記錄下處理器process = pfor _, m := range modules {m.RegNetMsg()}
}// 供各模塊調用以注冊本模塊的網絡消息處理。M為模塊結構指針,T為處理函數使用的網絡消息結構指針
func RegNetMsg[T proto.Message, M any](cmdId netmsg.NetCmdID, handle func(M, T)) {f := func(p common.IRole, pb T) {// 根據網絡消息ID查模塊IDid, ok := mapNetMsg[cmdId]if !ok {return}// 根據模塊ID獲取取模塊m := p.GetGameModule(id)if m != nil {defer func() {if r := recover(); r != nil {buf := make([]byte, 4096)l := runtime.Stack(buf, false)errString := fmt.Sprint(r)log.Errorf("UserID:%d RoleID:%d Module:%v NetMsg:%v Core dump info[%s]\n%s",p.GetUserID(), p.GetRoleID(), id, cmdId, errString, string(buf[:l]))}}()// 調用處理函數時,把模塊接口轉為實際的模塊指針handle(m.(M), pb)}}if _, ok := mapNetMsg[cmdId ]; ok {panic("Repeated RegModule Module NetMsg:%s", id.String())} else {mapNetMsg[cmdId ] = modType}register(cmdId, f)
}// 注冊網絡消息處理器,UserData為游戲邏輯模塊結構的指針,T為模塊網絡消息處理函數中使用的網絡消息結構指針
func register[T proto.Message, UserData any](cmdId netmsg.NetCmdID, handle func(UserData, T)) {f := func(userData interface{}, msg proto.Message) {// 轉換為游戲邏輯模塊結構的指針p := userData.(UserData)// 轉換為消息處理函數中使用的網絡消息結構指針pb := msg.(T)handle(p, pb)}var pb T// 這里調用origin框架的PB處理器,注冊網絡消息處理函數process.Register(uint16(cmdId), pb, f)
}

在各個模塊中調用mod.RegModule來注冊模塊,如bag_mod.go所示。
然后在origin的服務中調用mod.RegModuleNetMsg來注冊各模塊的網絡消息。比如:

package myServicefunc init() {// 注冊服務service
}type service struct {service.Serviceprocess  *processor.PBProcessor
}func (slf *myService) OnInit() error {slf.process = processor.NewPBProcessor()mod.RegModuleNetMsg(slf.process)
}

這樣就可以清晰地寫游戲邏輯中的模塊了。

如果本文對你有幫助,歡迎點贊收藏!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/88169.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/88169.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/88169.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【機器人】Aether 多任務世界模型 | 4D動態重建 | 視頻預測 | 視覺規劃

Aether 是一個的世界模型,整合幾何重建與生成建模的統一框架,實現類人空間推理能力。 來自ICCV 2025,該框架具有三大核心功能: (1) 4D動態重建,(2) 動作條件視頻預測, (3) 目標條件視覺規劃。 代碼地址&…

MiniMind:3小時訓練26MB微型語言模型,開源項目助力AI初學者快速入門

開發|界面|引擎|交付|副駕——重寫全棧法則:AI原生的倍速造應用流來自全棧程序員 nine 的探索與實踐,持續迭代中。 歡迎關注評論私信交流~ 在大型語言模型(LLaMA、GPT等)日益流行的今天,一個名為…

相機Camera日志實例分析之五:相機Camx【萌拍閃光燈后置拍照】單幀流程日志詳解

【關注我,后續持續新增專題博文,謝謝!!!】 上一篇我們講了: 這一篇我們開始講: 目錄 一、場景操作步驟 二、日志基礎關鍵字分級如下 三、場景日志如下: 一、場景操作步驟 操作步…

[2-02-02].第03節:環境搭建 - Win10搭建ES集群環境

ElasticSearch學習大綱 基于ElasticSearch7.8版本 一、ElasticStack下載: 1.Elasticsearch 的官方地址 2.Elasticsearch 下載地址: 二、集群搭建: 第1步:創建es目錄: 1.創建 elasticsearch-cluster 文件夾,在內部…

操作系統核心技術剖析:從Android驅動模型到鴻蒙微內核的國產化實踐

目錄 一、移動端操作系統技術細節 1. Android 內核版本 核心模塊 驅動架構 國內定制案例 2. iOS XNU內核關鍵模塊 安全機制 3. HarmonyOS 多內核架構 驅動隔離 二、PC端操作系統技術細節 1. Windows NT內核 模塊分層 驅動模型 國內適配 2. macOS(X…

整合Spring、Spring MVC與MyBatis:構建高效Java Web應用

本文將詳細講解如何整合Spring、Spring MVC和MyBatis(SSM框架),通過一個人員信息查詢案例展示完整開發流程。所有代碼基于提供的文件實現。一、項目結構src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── qcb…

視頻插幀技術:從流暢觀影到AI創作的革命

一、起源:為什么需要視頻插幀? 視頻的本質是連續播放的靜態幀序列,幀率(FPS) 決定了流暢度。早期電影受限于拍攝技術和存儲成本,普遍采用24FPS,而現代顯示設備(如120Hz屏幕&#xf…

【一起來學AI大模型】PyTorch 實戰示例:使用 BatchNorm 處理張量(Tensor)

PyTorch 實戰示例 演示如何在神經網絡中使用 BatchNorm 處理張量(Tensor),涵蓋關鍵實現細節和常見陷阱。示例包含數據準備、模型構建、訓練/推理模式切換及結果分析。示例場景:在 CIFAR-10 數據集上實現帶 BatchNorm 的 CNNimport…

第8章:應用層協議HTTP、SDN軟件定義網絡、組播技術、QoS

應用層協議HTTP 應用層協議概述 應用層協議非常多,我們重點熟悉以下常見協議功能即可。 Telnet:遠程登錄協議,基于TCP 23端口,用于遠程管理設備,采用明文傳輸。安全外殼協議 (SecureShell,SSH) ,基于TCP 22端口,用于…

uniapp頁面間通信

uniapp中通過eventChannel實現頁面間通信的方法,這是一種官方推薦的高效傳參方式。我來解釋下這種方式的完整實現和注意事項:?發送頁面(父頁面)?:uni.navigateTo({url: /pages/detail/detail,success: (res) > {/…

Android ViewModel機制與底層原理詳解

Android 的 ViewModel 是 Jetpack 架構組件庫的核心部分,旨在以生命周期感知的方式存儲和管理與 UI 相關的數據。它的核心目標是解決兩大痛點: 數據持久化: 在配置變更(如屏幕旋轉、語言切換、多窗口模式切換)時保留數…

雙倍硬件=雙倍性能?TDengine線性擴展能力深度實測驗證!

軟件擴展能力是軟件架構設計中的一個關鍵要素,具有良好擴展能力的軟件能夠充分利用新增的硬件資源。當軟件性能與硬件增加保持同步比例增長時,我們稱這種現象為軟件具有線性擴展能力。要實現這種線性擴展并不簡單,它要求軟件架構精心設計&…

頻繁迭代下完成iOS App應用上架App Store:一次快速交付項目的完整回顧

在一次面向商戶的會員系統App開發中,客戶要求每周至少更新一次版本,涉及功能迭代、UI微調和部分支付方案的更新。團隊使用Flutter進行跨平臺開發,但大部分成員日常都在Windows或Linux環境,只有一臺云Mac用于打包。如何在高頻率發布…

springsecurity03--異常攔截處理(認證異常、權限異常)

目錄 Spingsecurity異常攔截處理 認證異常攔截 權限異常攔截 注冊異常攔截器 設置跨域訪問 Spingsecurity異常攔截處理 認證異常攔截 /*自定義認證異常處理器類*/ Component public class MyAuthenticationExceptionHandler implements AuthenticationEntryPoint {Overr…

企業如何制作網站?網站制作的步驟與流程?

以下是2025年網站制作的綜合指南,涵蓋核心概念、主流技術及實施流程: 一、定義與范疇 網站制作是通過頁面結構設計、程序設計、數據庫開發等技術,將視覺設計轉化為可交互網頁的過程,包含前端展示與后臺功能實現。其核心目標是為企…

Rust+Blender:打造高性能游戲引擎

基于Rust和Blender的游戲引擎 以下是基于Rust和Blender的游戲引擎開發實例,涵蓋不同應用場景和技術方向的實際案例。案例分為工具鏈整合、渲染技術、物理模擬等類別,每個案例附核心代碼片段或實現邏輯。 工具鏈整合案例 案例1:Blender模型導出到Bevy引擎 使用blender-bev…

Git基本操作1

Git 是一款分布式版本控制系統,主要用于高效管理代碼版本和團隊協作開發。它能精確記錄每次代碼修改,支持版本回溯和分支管理,讓開發者可以并行工作而互不干擾。通過本地提交和遠程倉庫同步,Git 既保障了代碼安全,又實…

React Native 組件間通信方式詳解

React Native 組件間通信方式詳解 在 React Native 開發中,組件間通信是核心概念之一。以下是幾種主要的組件通信方式及其適用場景: 簡單父子通信:使用 props 和回調函數兄弟組件通信:提升狀態到共同父組件跨多級組件:…

TCP的可靠傳輸機制

TCP通過校驗和、序列號、確認應答、重發控制、連接管理以及窗口控制等機制實現可靠性的傳輸。 先來看第一個可靠性傳輸的方法。 通過序列號和可靠性提供可靠性 TCP是面向字節的。TCP把應用層交下來的報文(可能要劃分為許多較短的報文段)看成一個一個字節…

沒有DBA的敏捷開發管理

前言一家人除了我都去旅游了,我這項請假,請不動啊。既然在家了,閑著也是閑著,就復盤下最近的工作,今天就復盤表結構管理吧,隨系統啟動的,不是flyway,而是另一個liquibase&#xff0c…