【题记】近一段时间一直想学几种不是那么主流的(C/C++/JAVA/.NET)语言,于是便把自己不熟悉的脚本语言拿来学习,从Perl到PHP、 Python,总感觉有种不是很亲切的感觉,直到遇见GO。遇见GO后立马喜欢上了它,简洁不简单,强大不臃肿,美妙不可言喻。
看完GO的基本语法,便想找个开源项目边研究边巩固基础,于是在Git上找了找,很有幸看到GoNet项目,项目规模不大但功能却很完善,实现了一个基础的游戏服务器通信框架,是入门的不可多得之选。
一、项目介绍
地址:https://github.com/xtaci/gonet
介绍直接取用官网:
部署:
- Game Server(GS):
玩家直接连接GS, 处理玩家逻辑,并与 HUB/SS 通信,GS存在若干个。 - Hub Server(HUB):
若干个GS 连接到一个HUB, 只存在一个HUB,维护基础的全局信息,以及 GS<--->GS 的消息转发. - Stats Server(SS):
统计服务器,根据玩家的行为,记录策划需要的数据,以便于后期统计。
统计属于事后分析,数据量较大,性能需求不同, 故单独列为一个服务器。
通信原则:
- GS到HUB/SS的通信,都是Call同步调用,即GS必须等待ACK。
- HUB到GS的通信,只有forward数据包。
- 单播消息在玩家离线时会存入db, 登录后的启动过程 GS 直接读取db,并forward给玩家goroutine。(持久化)
- 多播消息会发送给所有的在线玩家(非持久化)
- 广播消息会发送给所有的在线玩家(非持久化)
服务器状态一致性
- GS节点可以单独重启
- HUB 重启后,GS必须全部重启
- SS 可随意重启,不影响业务
本文着重分析GS、HUB、Timer等模块的启动,以及典型的业务流程。
二、模块启动分析
一)HUB模块启动分析
请结合代码阅读
1、启动时,先初始化导入的包:
1) Package cfg初始化。----加载所有配置项到map容器。
2) Package core初始化
a) 初始化导入的包misc/timer (定时器模块初始化)
----初始化定时器资源并启动定时器routine,守护定时器队列。
b) 初始化db模块
----连接数据库服务器
c) 初始化导入的包 hub/core有限状态机初始化
----初始化玩家信息;启动状态机协程维护玩家状态
2、启动Signal routine,处理SIGHUP信号,重新加载配置。
3、启动Sys routine,主要用于定期强制内存回收
4、启动TCP监听,用于接收客户端连接
5、从数据库加载所有用户
----根据用户id构造玩家信息,记录到全局map(user.id, PlayerInfo)
6、循环接收客户端(GameServer)连接,一个连接一个routine。
启动流程时序图:
上图中
- Agent Routine启动时会为每个客户端(Game server)生成唯一HOST ID(图中标为Client ID)并创建转发队列,然后将HOST ID和转发队列加入到全局Map中。转发队列用来接收其它Game server发过来的消息,全局Map以便于索引到转发队列。那么问题来了,用户的User ID是如何和HOST ID发生关系的?例如在GS1上登陆的用户A向在GS5上登陆的用户B发消息,HUB是如何转发到GS5上的?我认为这一块相对较模糊,起码在流程上没有贯穿,系统中倒是有相关的实现:在用户登陆时HUB会记录该用户所在的HOST ID,当转发用户消息时,根据目标用的ID查找到HOST ID。
- 在接收到客户端的消息后,交给Proxy处理,最后到具体的协议处理函数中处理,完成后根据情况回响应消息。