Firefly 游戏服务器学习笔记
Firefly 是什么?
Firefly 是一个用 Go 语言(Golang)编写的开源、高性能、分布式游戏服务器框架,它旨在简化游戏服务器的开发,让开发者可以专注于游戏逻辑本身,而不是底层网络、并发、数据存储等复杂问题。

核心特点:
- 高性能: 基于 Go 的 Goroutine 和 Channel 模型,轻松实现高并发,非常适合处理大量客户端连接。
- 分布式: 天然支持分布式部署,通过
Gate(网关)和Hall(大厅/逻辑服)的分离,可以水平扩展,承载海量玩家。 - 组件化: 框架由多个独立的、可插拔的组件构成(如网络、数据库、日志、RPC等),开发者可以根据需求选择和替换。
- 热更新: 支持在不重启服务器的情况下,更新游戏逻辑代码,对于需要持续运营的游戏至关重要。
- 协议友好: 默认支持多种协议(如 TCP、WebSocket、自定义二进制协议),并内置了 Protobuf 的支持,方便进行数据序列化和反序列化。
- 文档与社区: 拥有相对完善的文档和活跃的社区(主要在 QQ 群和 Gitee),遇到问题容易找到帮助。
核心架构设计
理解 Firefly 的架构是学习的第一步,它的设计非常清晰,主要分为以下几个层次和角色:
架构分层
-
Gate (网关服):
- 职责: 负责接收所有玩家的网络连接,它是一个“无状态”的服务,只负责网络数据的收发和转发。
- 工作流程:
- 玩家客户端连接到 Gate。
- Gate 接收玩家的登录请求,验证身份。
- 验证通过后,Gate 将该玩家的连接与一个指定的 Hall 服(逻辑服)绑定。
- 之后,该玩家的所有游戏消息都通过 Gate 转发到绑定的 Hall 服,Hall 服的返回消息也通过 Gate 转发回客户端。
- 优势: Gate 可以无脑地水平扩展,玩家可以随机或根据哈希算法连接到不同的 Gate,再由 Gate 路由到对应的 Hall,这解决了单点登录和连接数瓶颈的问题。
-
Hall (大厅/逻辑服):
(图片来源网络,侵删)- 职责: 游戏的核心逻辑所在地,一个游戏世界或一个逻辑区域通常由一个或多个 Hall 服组成。
- 工作流程:
- 接收来自 Gate 转发过来的玩家消息。
- 执行具体的游戏逻辑(如移动、战斗、聊天等)。
- 与其他 Hall 服或数据库进行交互。
- 将处理结果(如位置更新、战斗结果)通过 Gate 发送给相关玩家。
- 状态: Hall 服是“有状态”的,它维护了其所负责玩家的游戏状态。
-
DB (数据库服):
- 职责: 负责数据持久化,可以是 MySQL、MongoDB 等,Hall 服通过 RPC 调用 DB 服来读写数据。
- 优势: 数据库服务独立部署,可以单独进行优化和维护,不影响游戏逻辑服务的运行。
-
Agent (代理服):
- 职责: 这是一个可选的组件,通常用于处理一些跨服逻辑、排行榜、公会管理等需要全局访问但又不想放在单个 Hall 服中的功能,它也通过 RPC 与其他服务通信。
关键技术组件
- 模块: Firefly 的核心是“模块化”,每个服务(Gate, Hall, DB)都是一个独立的进程,由一个或多个模块组成,模块之间通过
module.Module接口进行解耦。 - 组件: 组件是模块的依赖项,如网络组件、日志组件、数据库组件、RPC 组件等,Firefly 提供了一套依赖注入机制,让模块可以方便地使用这些组件。
- 消息路由: Firefly 使用一个中心化的
dispatcher来处理消息,当一个模块收到消息后,会根据消息的 ID 注册一个处理函数,当该 ID 的消息到来时,对应的处理函数就会被自动调用,这使得消息处理逻辑非常清晰。 - RPC (Remote Procedure Call): Firefly 使用
gRPC作为其默认的 RPC 框架,用于服务间的通信(如 Hall 调用 DB),这使得跨进程的服务调用就像调用本地函数一样简单。
环境搭建与第一个 "Hello World"
环境要求
- Go 1.16+
- Git
- Protocol Buffers 编译器 (
protoc) - Make (可选,但推荐)
安装步骤
# 1. 克隆 Firefly 仓库 git clone https://github.com/firefly-zero/firefly-zero.git cd firefly-zero # 2. 安装依赖 make dep # 3. 编译所有工具和示例 make all
编译成功后,可执行文件会在 bin 目录下。
运行第一个示例
Firefly 自带了许多示例,helloworld 是最好的起点。

# 1. 启动一个简单的 Hall 服 # 这个示例中,Gate 和 Hall 在同一个进程里 bin/hall -f=hall/hall.conf # 2. 在另一个终端,启动一个 Gate 服 # 同样,这个示例将 Gate 和 Hall 合二为一 bin/gate -f=gate/gate.conf
当你看到日志打印出服务启动成功后,一个最基础的 Firefly 游戏服务器就跑起来了,你可以使用 telnet 或 netcat 连接到 Gate 的端口,发送一个预定义的 protobuf 消息来测试。
核心概念深入
消息定义与处理
-
定义消息: 使用
.proto文件定义你的消息结构。syntax = "proto3"; package message; message HelloMessage { string content = 1; } -
生成 Go 代码:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ your_message.proto -
注册和处理消息: 在你的模块中,你需要告诉 Firefly 如何处理某个消息 ID。
// 在模块的 Init 函数中注册 func (m *YourModule) Init() { // 注册处理函数,messageID.HelloMessage 对应 .proto 文件中定义的消息 ID m.RegisterHandler(messageID.HelloMessage, m.HandleHello) } // 处理函数 func (m *YourModule) HandleHello(session znet.Session, msg proto.Message) { helloMsg := msg.(*message.HelloMessage) log.Infof("Received hello message: %s", helloMsg.Content) // 可以给客户端回一个消息 response := &message.HelloResponse{Content: "Server received your message!"} session.Send(messageID.HelloResponse, response) }
模块与生命周期
每个服务(进程)都是由一个或多个模块组成的,模块有清晰的声明周期:
Init(): 模块初始化时调用,通常用于注册消息处理器、加载配置、启动协程等。Run():Init之后调用,模块的主循环,对于大多数模块,这个函数可以留空,因为 Firefly 的网络模型是事件驱动的,不需要你写for循环。OnDestroy(): 服务关闭时调用,用于资源清理。
配置系统
Firefly 使用 ini 格式的配置文件,配置项通过结构体标签与代码绑定,非常方便。
type HallConfig struct {
ListenAddr string `ini:"listen_addr"` // 监听地址
DBAddr string `ini:"db_addr"` // 数据库地址
}
// 在模块中加载配置
var hallConf HallConfig
err := conf.Get("hall", &hallConf)
数据库操作
Firefly 封装了常用的数据库驱动,操作起来非常方便。
// 1. 在模块中获取数据库组件
db := m.GetModule("db").(*dbcomponent.DBComponent)
// 2. 执行查询
rows, err := db.Query("SELECT id, name FROM users WHERE id = ?", playerId)
if err != nil {
// handle error
}
defer rows.Close()
// 3. 遍历结果
for rows.Next() {
var id int64
var name string
if err := rows.Scan(&id, &name); err != nil {
// handle error
}
log.Infof("User: %d, %s", id, name)
}
// 4. 执行更新
result, err := db.Exec("UPDATE users SET name = ? WHERE id = ?", newName, playerId)
if err != nil {
// handle error
}
affected, _ := result.RowsAffected()
log.Infof("Updated %d rows", affected)
实战:开发一个简单的房间服务
假设我们要实现一个功能:玩家可以创建房间,其他玩家可以加入房间,房间内可以聊天。
-
定义消息:
CreateRoomReq: { player_id, room_name }CreateRoomRes: { success, room_id, error_msg }JoinRoomReq: { player_id, room_id }JoinRoomRes: { success, error_msg }ChatMsg: { player_name, content }
-
创建
RoomModule:- 在
Hall服务中新建一个RoomModule。 - 在
Init()中注册CreateRoomReq,JoinRoomReq,ChatMsg的处理器。
- 在
-
实现逻辑:
HandleCreateRoom: 创建一个Room结构体实例,将其存入一个全局的map[int64]*Room中(room_id 为 key),并返回CreateRoomRes。HandleJoinRoom: 根据room_id从map中找到房间,将玩家加入房间的玩家列表,并返回JoinRoomRes。HandleChatMsg: 获取消息发送者的信息,遍历房间内所有玩家的 Session,将聊天消息广播给他们。
-
部署与测试:
- 编译你的
Hall服务,确保RoomModule被正确加载。 - 启动
Gate和Hall服务。 - 编写一个简单的客户端或使用 Postman 等工具,按顺序发送
CreateRoomReq,JoinRoomReq,ChatMsg来验证功能。
- 编译你的
学习路径与资源
- 从官方文档开始: Firefly 官方文档 是最好的入门材料。
- 阅读官方示例:
examples目录下的代码是最好的老师,特别是helloworld,chat,area等示例,涵盖了从简单到复杂的各种场景。 - 理解 Go 语言基础: Firefly 是 Go 写的,深入理解 Goroutine, Channel, Interface, Struct Tag 等是必须的。
- 学习 gRPC 和 Protobuf: Firefly 的服务间通信和数据序列化严重依赖这两者。
- 阅读源码: 当你对框架有基本了解后,可以尝试阅读
firefly目录下的核心源码,module,net,container等包,能让你更深刻地理解其设计思想。
总结与思考
Firefly 是一个非常优秀且现代的游戏服务器框架,它的 Go 语言基础和分布式架构使其非常适合开发中大型、高并发的在线游戏。
优点:
- 开发效率高: 事件驱动和模块化设计让代码结构清晰,迭代快速。
- 性能强大: Go 的并发模型能轻松应对数万甚至数十万的在线连接。
- 扩展性好: 分布式架构支持服务的水平扩展,是应对未来用户增长的有力保障。
- 社区活跃: 对于国内开发者来说,Gitee 上的社区和 QQ 群提供了很好的支持。
挑战:
- 学习曲线: 对于没有 Go 语言和分布式系统经验的开发者来说,需要投入一定时间学习。
- 文档细节: 虽然文档全面,但某些细节和最佳实践可能需要通过阅读源码和社区讨论来挖掘。
- 生态: 相较于一些老牌框架(如 Pomelo, Netty),Firefly 的第三方插件和解决方案生态还在建设中。
建议:
从最简单的 helloworld 示例开始,逐步构建自己的小项目,在开发过程中,遇到问题多翻阅文档、源码和社区讨论,实践是掌握 Firefly 的最快途径,祝你学习顺利!
