当前位置: 首页 > news >正文

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.xdnf.cn/news/1088137.html

相关文章:

  • 国产飞腾主板,赋能网络安全防御硬手段
  • 【Android】组件及布局介绍
  • 微算法科技(NASDAQ MLGO)研究非标准量子预言机,拓展量子计算边界
  • 【WEB】Polar靶场 16-20题 详细笔记
  • navicat导出数据库的表结构
  • 数据库版本自动管理
  • 订单初版—分布式订单系统的简要设计文档
  • Centos和麒麟系统如何每天晚上2点10分定时备份达梦数据库
  • JAVAEE 代理
  • 3D 演示动画在汽车培训与教育领域中的应用
  • Modbus TCP转Profinet网关实现视觉相机与西门子PLC配置实例研究
  • Anolis OS 23 架构支持家族新成员:Anolis OS 23.3 版本及 RISC-V 预览版发布
  • 面试题--系统如何处理异常
  • SpringAI学习笔记-MCP服务器简单示例
  • 【UE5】虚幻引擎小百科
  • 后台设计指南:系统架构、交互规范与工具实战全流程解析
  • (C++)list列表相关基础用法(C++教程)(STL库基础教程)
  • Android T startingwindow使用总结
  • 深度剖析:向70岁老系统植入通信芯片——MCP注入构建未来级分布式通信
  • 容器技术技术入门与 Docker 环境部署
  • Flutter基础(前端教程④-组件拼接)
  • Python Web应用开发之Flask框架高级应用(三)——蓝图(Blueprints)
  • uniapp b树
  • 【LeetCode 热题 100】142. 环形链表 II——快慢指针
  • FairyGUI 实现 Boss 双层血条动画
  • Crazyflie无人机集群控制笔记(二)通过SDK实时对接Crazyswarm2及NOKOV度量动捕数据
  • jmeter 性能测试步骤是什么?
  • 代码详细注释:C语言实现控制台用户注册登录系统
  • C++进阶—二叉树进阶
  • [2025CVPR]SGC-Net:开放词汇人机交互检测的分层粒度比较网络解析