{"id":28399897,"url":"https://github.com/fish-tennis/gserver","last_synced_at":"2025-08-09T06:06:25.210Z","repository":{"id":53764636,"uuid":"430034469","full_name":"fish-tennis/gserver","owner":"fish-tennis","description":"a distributed game server framework,based on gnet and gentity 分布式游戏服务器框架","archived":false,"fork":false,"pushed_at":"2025-05-12T08:23:03.000Z","size":17521,"stargazers_count":39,"open_issues_count":1,"forks_count":9,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-06-01T15:52:03.088Z","etag":null,"topics":["distributed","game","gnet","go","golang","mongodb","protobuf","redis","server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fish-tennis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-11-20T07:08:57.000Z","updated_at":"2025-06-01T08:41:13.000Z","dependencies_parsed_at":"2024-02-07T10:43:13.692Z","dependency_job_id":"78a1f998-d826-4b29-ba3f-5ac575b0b86a","html_url":"https://github.com/fish-tennis/gserver","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fish-tennis/gserver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fish-tennis%2Fgserver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fish-tennis%2Fgserver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fish-tennis%2Fgserver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fish-tennis%2Fgserver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fish-tennis","download_url":"https://codeload.github.com/fish-tennis/gserver/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fish-tennis%2Fgserver/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262363924,"owners_count":23299487,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["distributed","game","gnet","go","golang","mongodb","protobuf","redis","server"],"created_at":"2025-06-01T08:11:36.768Z","updated_at":"2025-08-09T06:06:25.161Z","avatar_url":"https://github.com/fish-tennis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gserver\n分布式游戏服务器框架\n\n## 设计思路\n- 类似gorm的数据绑定,使业务逻辑和数据库操作解耦\n- 采用Entity-Component模式,尽可能使模块解耦\n- 使用protobuf做通讯协议和数据序列化\n- 玩家数据的存储接口可替换(mongodb,mysql,redis)\n- 工具生成辅助代码,提供更友好的调用接口\n- 网络库使用[github.com/fish-tennis/gnet](https://github.com/fish-tennis/gnet)\n\n## 演示功能\n- 服务器自动组网\n- 服务器负载均衡\n- 客户端直连模式和网关模式可选,网关模式支持WebSocket\n- 一个账号同时只能登录一个服务器(数据一致性前提)\n- 游戏服宕机后重启,自动修复缓存数据,防止玩家数据回档\n- 通过反射自动注册消息回调,事件响应接口(业务代码和网络库解耦)\n- 采用Entity-Component设计,模块解耦\n- Entity事件分发\n- 业务层和数据层分离,业务代码无需操作数据库和缓存\n- 通过公会功能演示如何开发分布式的功能\n- 通过公会功能演示服务器动态扩缩容的处理方式\n- 服务器之间的rpc调用(类似grpc)\n- 任务模块,演示了如何实现一个通用且扩展性强的[任务系统](https://github.com/fish-tennis/gserver/blob/main/Design_Quest.md)\n- 活动模块,演示了如何设计一个通用且支持扩展的活动模块\n- 配置数据管理模块,同时支持csv和json,支持热更新\n- 网络协议的消息号自动生成\n\n## 数据方案\n玩家数据落地使用mongodb,玩家上线时,从mongodb拉取玩家数据,玩家下线时,把玩家数据保存到mongodb\n\n缓存使用redis,玩家在线期间修改的数据,即时保存到redis,防止服务器crash导致数据丢失\n\ngserver提供了数据绑定的方案,业务层只需要标记哪些数据需要保存,无需自己写代码操作数据库和redis\n\n## 数据绑定\n类似gorm(go Object Relation Mapping)对SQL进行对象映射,gserver使用的数据绑定对组件进行数据库和缓存的映射\n\n使用go的struct tag,设置对象组件的字段,框架接口会自动对这些字段进行数据库读取保存和缓存更新,极大的简化了业务代码对数据库和缓存的操作\n\n设置组件保存数据\n```go\n// 玩家的一个组件\ntype Money struct {\n  PlayerDataComponent\n  // 该字段必须导出(首字母大写)\n  // 使用struct tag来标记该字段需要存数据库\n  Data *pb.Money `db:\"\"`\n}\n```\n\n支持明文方式保存数据\n```go\n// 玩家基础信息组件\ntype BaseInfo struct {\n  PlayerDataComponent\n  // plain表示明文存储,在保存到mongo时,不会进行proto序列化,以便于mongo语句直接操作\n  Data *pb.BaseInfo `db:\"plain\"`\n}\n```\n\n支持多个保存字段\n```go\n// 玩家的任务组件\ntype Quest struct {\n  BasePlayerComponent\n  // 保存数据的子模块:已完成的任务 使用明文保存方式\n  // wrapper of []int32\n  Finished *gentity.SliceData[int32] `child:\"Finished;plain\"`\n  // 保存数据的子模块:当前任务列表\n  // wrapper of map[int32]*pb.QuestData\n  Quests *gentity.MapData[int32, *pb.QuestData] `child:\"Quests\"`\n}\n```\n\n## 消息回调,事件响应\n支持自动注册消息回调,事件响应\n```go\n// 客户端发给服务器的完成任务的消息回调\n// 这种格式写的函数可以自动注册客户端消息回调\nfunc (q *Quest) OnFinishQuestReq(req *pb.FinishQuestReq) (*pb.FinishQuestRes, error) {\n  // logic code ...\n  return \u0026pb.FinishQuestRes{ QuestCfgId: id, }, nil\n}\n```\n```go\n// 这种格式写的函数可以自动注册非客户端的消息回调\nfunc (b *BaseInfo) HandlePlayerEntryGameOk(msg *pb.PlayerEntryGameOk) { \n  // logic code ...\n}\n```\n```go\n// 这种格式写的函数可以自动注册事件响应接口\n// 当执行player.FireEvent(\u0026EventPlayerEntryGame{})时,该响应接口会被调用\nfunc (q *Quest) TriggerPlayerEntryGame(event *EventPlayerEntryGame) {\n  // logic code ...\n}\n```\n\n## rpc\n```go\n// 客户端请求查看自己所在公会的信息\nfunc (g *Guild) OnGuildDataViewReq(req *pb.GuildDataViewReq) (*pb.GuildDataViewRes, error) {\n  if 玩家还没加入公会 {\n    return nil, errors.New(\"not a guild member\")\n  }\n  // 向公会所在服务器发起rpc\n  reply := new(pb.GuildDataViewRes)\n  err := g.RouteRpcToSelfGuild(req, reply)\n  return reply, err\n}\n\n// 公会服务响应rpc请求\nfunc (g *GuildBaseInfo) HandleGuildDataViewReq(m *GuildMessage, req *pb.GuildDataViewReq) (*pb.GuildDataViewRes, error) {\n  if 请求玩家不是本公会成员 {\n    return nil, errors.New(\"not a member\")\n  }\n  return \u0026pb.GuildDataViewRes{...}, nil\n}\n```\n\n## 协程\n每个玩家分配一个独立的逻辑协程,玩家在自己的逻辑协程中执行只涉及自身数据的代码,无需加锁\n\n## 运行\n安装mongodb\n\n安装redis,单机模式和集群模式均可\n\n修改config目录下的配置文件\n\n编译运行\n\n## 测试\n测试客户端[gtestclient](https://github.com/fish-tennis/gtestclient)\n\n## 编码规范参考\n[https://github.com/uber-go/guide](https://github.com/uber-go/guide)\n\n[https://github.com/xxjwxc/uber_go_guide_cn](https://github.com/xxjwxc/uber_go_guide_cn)\n\n## 客户端网络库\nC#: [gnet_csharp](https://github.com/fish-tennis/gnet_csharp)\n\n## Excel表导出工具\n[excelexporter](https://github.com/fish-tennis/excelexporter)\n\ngserver的excel/tool目录下有编译好的执行程序\n\n## proto预处理工具\n[proto_code_gen](https://github.com/fish-tennis/proto_code_gen)\n\n在gserver项目中负责生成配置数据的只读接口,自动生成网络协议号\n\nproto文件修改后,运行proto\\tool\\generate_proto_go.bat (linux下运行generate_proto_go.sh)\n\ngenerate_proto_go集成了protoc和proto_code_gen\n\n## 讨论\nQQ群: 764912827\n\n欢迎有如下兴趣的小伙伴加入\n\n- 客户端demo\n- 服务器框架改进\n- mysql的db接口实现\n- 非redis的缓存cache接口实现\n- 文档和示例\n- 工具demo(如配置编辑等)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffish-tennis%2Fgserver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffish-tennis%2Fgserver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffish-tennis%2Fgserver/lists"}