actor.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. package user
  2. import (
  3. "encoding/binary"
  4. cherryApp "f1-game/internal/cherry_app"
  5. "f1-game/internal/code"
  6. "f1-game/internal/component/rate"
  7. "f1-game/internal/constant"
  8. "f1-game/internal/data"
  9. "f1-game/internal/extend/utils"
  10. nameActor "f1-game/internal/name/actor"
  11. nameRemote "f1-game/internal/name/remote"
  12. nameRoute "f1-game/internal/name/route"
  13. "f1-game/internal/pb"
  14. "f1-game/internal/sessions"
  15. "f1-game/nodes/gate/cache"
  16. "slices"
  17. "time"
  18. cstring "github.com/cherry-game/cherry/extend/string"
  19. ctime "github.com/cherry-game/cherry/extend/time"
  20. cfacade "github.com/cherry-game/cherry/facade"
  21. clog "github.com/cherry-game/cherry/logger"
  22. cconnector "github.com/cherry-game/cherry/net/connector"
  23. "github.com/cherry-game/cherry/net/parser/pomelo"
  24. pmessage "github.com/cherry-game/cherry/net/parser/pomelo/message"
  25. pomeloPacket "github.com/cherry-game/cherry/net/parser/pomelo/packet"
  26. cproto "github.com/cherry-game/cherry/net/proto"
  27. cprofile "github.com/cherry-game/cherry/profile"
  28. "go.uber.org/zap/zapcore"
  29. )
  30. var (
  31. upa *userParserActor
  32. )
  33. type (
  34. // 网关专用用户解析器
  35. userParserActor struct {
  36. *pomelo.Actor
  37. nodeRouteMap map[string]routeFunc // 节点路由函数映射
  38. nodeSyncMap map[string]SyncFunc // 节点数据同步函数映射
  39. duplicateLoginCode []byte // 已序列化好的重复登录响应数据
  40. notLoginCode []byte // 已序列化好的未登录响应数据
  41. kickCode []byte // 已序列化好的踢人响应数据
  42. }
  43. // 节点路由函数
  44. routeFunc func(pomeloAgent *pomelo.Agent, session *cproto.Session, route *pmessage.Route, msg *pmessage.Message)
  45. // 节点数据同步函数
  46. SyncFunc func(actor cfacade.IActor, session *cproto.Session, isBind bool)
  47. )
  48. func NewUserActorParser() *pomelo.Actor {
  49. upa = &userParserActor{
  50. Actor: pomelo.NewActor(nameActor.GateUser),
  51. nodeRouteMap: make(map[string]routeFunc),
  52. nodeSyncMap: make(map[string]SyncFunc),
  53. }
  54. upa.init()
  55. return upa.Actor
  56. }
  57. func (p *userParserActor) init() {
  58. // 当actor OnInit()时,增加hook进2个远程调用接口
  59. p.SetOnInitFunc(func() {
  60. p.Remote().Register(nameRemote.User_PlayerPush, p.playerPush) // 玩家消息推送
  61. p.Remote().Register(nameRemote.User_PlayerKick, p.playerKick) // 玩家踢掉
  62. p.initNodeKick()
  63. })
  64. // 初始化状态码
  65. p.initStatusCode()
  66. // 初始心跳设置
  67. p.initHeartBeat()
  68. // 初始用户新连接处理函数
  69. p.initNewAgent()
  70. // 设置数据包处理函数
  71. p.SetOnDataRoute(p.onDataRoute)
  72. // 初始化节点路由函数
  73. p.initNodeRoute()
  74. // 初始化节点数据同步函数
  75. p.initNodeSync()
  76. // 添加网络连接器
  77. //p.AddConnector(cconnector.NewWS(p.app.Address()))
  78. p.AddConnector(cconnector.NewTCP(cherryApp.App().Address()))
  79. }
  80. func (p *userParserActor) initHeartBeat() {
  81. // 心跳超时时间,过该时间未往网关发送心跳则网关主动断开连接
  82. heartBeatTimeout := cherryApp.App().Settings().GetDuration("heartbeat", 30) * time.Second
  83. p.SetHeartbeat(heartBeatTimeout)
  84. // 自定义设置心跳包处理函数
  85. p.SetOnPacket(pomeloPacket.Heartbeat, p.onHeartbeat)
  86. }
  87. func (p *userParserActor) initNewAgent() {
  88. // 有新连接时触发创建pomelo.Agent实例
  89. p.SetOnNewAgent(func(newAgent *pomelo.Agent) {
  90. // 为每个新连接创建child actor agent
  91. childAgent := newChildAgent(newAgent)
  92. _, err := p.Child().Create(childAgent.AliasID(), childAgent)
  93. if err != nil {
  94. clog.Warnf("Create agent fail. err = %v", err)
  95. }
  96. })
  97. }
  98. func (p *userParserActor) initNodeRoute() {
  99. p.nodeRouteMap[constant.NodeGame] = gameNodeRoute
  100. p.nodeRouteMap[constant.NodeMap] = mapNodeRoute
  101. }
  102. func (p *userParserActor) initNodeSync() {
  103. p.nodeSyncMap[sessions.GameNodeID] = sync2Game
  104. p.nodeSyncMap[sessions.MapNodeID] = sync2Map
  105. }
  106. func (p *userParserActor) initStatusCode() {
  107. p.duplicateLoginCode, _ = cherryApp.App().Serializer().Marshal(&cproto.I32{
  108. Value: code.PlayerDuplicateLogin,
  109. })
  110. p.notLoginCode, _ = cherryApp.App().Serializer().Marshal(&cproto.I32{
  111. Value: code.PlayerNotLogin,
  112. })
  113. }
  114. // 监听节点移除事件,当接收到(game/map)节点移除的消息时踢掉当前gate的相关session
  115. func (p *userParserActor) initNodeKick() {
  116. p.App().Discovery().OnRemoveMember(func(member cfacade.IMember) {
  117. if member.GetNodeType() == constant.NodeGame {
  118. clog.Infof("Kick Sessions with [NodeType = %v, NodeID = %v]", member.GetNodeType(), member.GetNodeID())
  119. p.playerKick(&pb.GateKick{
  120. Type: pb.GateKick_GameNode,
  121. StrValue: member.GetNodeID(),
  122. Reason: p.kickCode,
  123. Closed: true,
  124. })
  125. }
  126. if member.GetNodeType() == constant.NodeMap {
  127. clog.Infof("Kick Sessions with [NodeType = %v, NodeID = %v]", member.GetNodeType(), member.GetNodeID())
  128. p.playerKick(&pb.GateKick{
  129. Type: pb.GateKick_MapNode,
  130. StrValue: member.GetNodeID(),
  131. Reason: p.kickCode,
  132. Closed: true,
  133. })
  134. }
  135. })
  136. }
  137. func (p *userParserActor) onHeartbeat(pomeloAgent *pomelo.Agent, _ *pomeloPacket.Packet) {
  138. var (
  139. now = ctime.Now().ToMillisecond()
  140. nowBytes = utils.Int64ToBytesFixed(now, binary.BigEndian)
  141. )
  142. pomeloAgent.SendPacket(pomeloPacket.Heartbeat, nowBytes)
  143. if clog.Enable(zapcore.DebugLevel) {
  144. clog.Debugf("[heartbeat] sid = %s, now = %d", pomeloAgent.SID(), now)
  145. }
  146. }
  147. func (p *userParserActor) playerPush(req *pb.GatePusher) {
  148. switch req.Type {
  149. case pb.GatePusher_Player:
  150. for _, playerID := range req.I64List {
  151. if agent, found := cache.GetPlayerAgent(playerID); found {
  152. if agent != nil {
  153. agent.Push(req.Route, req.Data)
  154. }
  155. }
  156. }
  157. case pb.GatePusher_GameNode:
  158. if gameMap, found := cache.GetGameAgentsMap(req.StrValue); found {
  159. gameMap.Range(func(_ int64, agent *pomelo.Agent) bool {
  160. if agent != nil {
  161. agent.Push(req.Route, req.Data)
  162. }
  163. return true
  164. })
  165. }
  166. case pb.GatePusher_Map:
  167. if mapMap, found := cache.GetMapAgentsMap(req.StrValue); found {
  168. mapMap.Range(func(_ int64, agent *pomelo.Agent) bool {
  169. if agent != nil {
  170. agent.Push(req.Route, req.Data)
  171. }
  172. return true
  173. })
  174. }
  175. }
  176. }
  177. func (p *userParserActor) playerKick(req *pb.GateKick) {
  178. // 检测是否过滤当前节点
  179. if len(req.FilterNodeIDS) > 0 && slices.Contains(req.FilterNodeIDS, cherryApp.App().NodeID()) {
  180. return
  181. }
  182. switch req.Type {
  183. case pb.GateKick_Player:
  184. for _, playerID := range req.I64List {
  185. if agent, found := cache.GetPlayerAgent(playerID); found {
  186. if agent != nil {
  187. agent.Kick(req.Reason, req.Closed)
  188. }
  189. }
  190. }
  191. case pb.GateKick_GameNode:
  192. if gameMap, found := cache.GetGameAgentsMap(req.StrValue); found {
  193. gameMap.Range(func(_ int64, agent *pomelo.Agent) bool {
  194. if agent != nil {
  195. agent.Kick(req.Reason, req.Closed)
  196. }
  197. return true
  198. })
  199. }
  200. case pb.GateKick_UID:
  201. for _, uid := range req.I64List {
  202. if agent, found := pomelo.GetAgentWithUID(uid); found {
  203. if agent != nil {
  204. agent.Kick(req.Reason, req.Closed)
  205. }
  206. }
  207. }
  208. case pb.GateKick_MapNode:
  209. if mapMap, found := cache.GetMapAgentsMap(req.StrValue); found {
  210. mapMap.Range(func(_ int64, agent *pomelo.Agent) bool {
  211. if agent != nil {
  212. agent.Kick(req.Reason, req.Closed)
  213. }
  214. return true
  215. })
  216. }
  217. }
  218. }
  219. // onDataRoute 数据路由规则
  220. //
  221. // 登录逻辑:
  222. // 1.(建立连接)客户端建立连接,服务端对应创建一个agent用于处理玩家消息,actorID == sid
  223. // 2.(用户登录)客户端进行帐号登录验证,通过uid绑定当前sid
  224. // 3.(角色登录)客户端通过'beforeLoginRoutes'中的协议完成角色登录
  225. func (p *userParserActor) onDataRoute(pomeloAgent *pomelo.Agent, route *pmessage.Route, msg *pmessage.Message) {
  226. if clog.PrintLevel(zapcore.DebugLevel) {
  227. clog.Debugf("[sid = %s,uid = %d] Request Data Message. [mid = %d, route = %s]",
  228. pomeloAgent.SID(),
  229. pomeloAgent.UID(),
  230. msg.ID,
  231. msg.Route,
  232. )
  233. }
  234. session := pomelo.BuildSession(pomeloAgent, msg)
  235. var errCode int32
  236. // 如果不是debug模式,则检查路由规则
  237. if !cprofile.Debug() {
  238. errCode = data.ProtoRoute.Contain(msg.Route)
  239. if code.IsFail(errCode) {
  240. pomeloAgent.ResponseCode(session, errCode, true)
  241. return
  242. }
  243. }
  244. // rate check,有uid时则优先uid进行限速检查
  245. if session.Uid > 0 {
  246. errCode = rate.Check(cstring.ToString(session.Uid))
  247. } else {
  248. errCode = rate.Check(session.Ip)
  249. }
  250. if code.IsFail(errCode) {
  251. pomeloAgent.ResponseCode(session, errCode, true)
  252. return
  253. }
  254. if !session.IsBind() && msg.Route != nameRoute.GateUser_Login {
  255. pomeloAgent.Kick(p.notLoginCode, true)
  256. return
  257. }
  258. if pomeloAgent.NodeType() == route.NodeType() {
  259. targetPath := cfacade.NewChildPath(pomeloAgent.NodeID(), route.HandleName(), session.Sid)
  260. pomelo.LocalDataRoute(pomeloAgent, session, route, msg, targetPath)
  261. return
  262. }
  263. // uid未绑定则踢掉
  264. if !session.IsBind() {
  265. pomeloAgent.Kick(p.notLoginCode, true)
  266. return
  267. }
  268. // 如果不是首条路由,则需要验证playerID
  269. if msg.Route != nameRoute.GameLogin_Enter {
  270. playerID := session.GetString(sessions.PlayerID)
  271. if playerID == "" {
  272. pomeloAgent.Kick(p.notLoginCode, true)
  273. return
  274. }
  275. }
  276. // 找到路由函数并执行
  277. if routeFn, found := p.GetNodeRoute(route.NodeType()); found {
  278. routeFn(pomeloAgent, session, route, msg)
  279. }
  280. }
  281. func (p *userParserActor) GetNodeRoute(nodeType string) (routeFunc, bool) {
  282. route, found := p.nodeRouteMap[nodeType]
  283. return route, found
  284. }
  285. func (p *userParserActor) GetSyncParser(nodeID string) (SyncFunc, bool) {
  286. parser, found := p.nodeSyncMap[nodeID]
  287. return parser, found
  288. }