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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…