package user import ( "encoding/binary" cherryApp "f1-game/internal/cherry_app" "f1-game/internal/code" "f1-game/internal/component/rate" "f1-game/internal/constant" "f1-game/internal/data" "f1-game/internal/extend/utils" nameActor "f1-game/internal/name/actor" nameRemote "f1-game/internal/name/remote" nameRoute "f1-game/internal/name/route" "f1-game/internal/pb" "f1-game/internal/sessions" "f1-game/nodes/gate/cache" "slices" "time" cstring "github.com/cherry-game/cherry/extend/string" ctime "github.com/cherry-game/cherry/extend/time" cfacade "github.com/cherry-game/cherry/facade" clog "github.com/cherry-game/cherry/logger" cconnector "github.com/cherry-game/cherry/net/connector" "github.com/cherry-game/cherry/net/parser/pomelo" pmessage "github.com/cherry-game/cherry/net/parser/pomelo/message" pomeloPacket "github.com/cherry-game/cherry/net/parser/pomelo/packet" cproto "github.com/cherry-game/cherry/net/proto" cprofile "github.com/cherry-game/cherry/profile" "go.uber.org/zap/zapcore" ) var ( upa *userParserActor ) type ( // 网关专用用户解析器 userParserActor struct { *pomelo.Actor nodeRouteMap map[string]routeFunc // 节点路由函数映射 nodeSyncMap map[string]SyncFunc // 节点数据同步函数映射 duplicateLoginCode []byte // 已序列化好的重复登录响应数据 notLoginCode []byte // 已序列化好的未登录响应数据 kickCode []byte // 已序列化好的踢人响应数据 } // 节点路由函数 routeFunc func(pomeloAgent *pomelo.Agent, session *cproto.Session, route *pmessage.Route, msg *pmessage.Message) // 节点数据同步函数 SyncFunc func(actor cfacade.IActor, session *cproto.Session, isBind bool) ) func NewUserActorParser() *pomelo.Actor { upa = &userParserActor{ Actor: pomelo.NewActor(nameActor.GateUser), nodeRouteMap: make(map[string]routeFunc), nodeSyncMap: make(map[string]SyncFunc), } upa.init() return upa.Actor } func (p *userParserActor) init() { // 当actor OnInit()时,增加hook进2个远程调用接口 p.SetOnInitFunc(func() { p.Remote().Register(nameRemote.User_PlayerPush, p.playerPush) // 玩家消息推送 p.Remote().Register(nameRemote.User_PlayerKick, p.playerKick) // 玩家踢掉 p.initNodeKick() }) // 初始化状态码 p.initStatusCode() // 初始心跳设置 p.initHeartBeat() // 初始用户新连接处理函数 p.initNewAgent() // 设置数据包处理函数 p.SetOnDataRoute(p.onDataRoute) // 初始化节点路由函数 p.initNodeRoute() // 初始化节点数据同步函数 p.initNodeSync() // 添加网络连接器 //p.AddConnector(cconnector.NewWS(p.app.Address())) p.AddConnector(cconnector.NewTCP(cherryApp.App().Address())) } func (p *userParserActor) initHeartBeat() { // 心跳超时时间,过该时间未往网关发送心跳则网关主动断开连接 heartBeatTimeout := cherryApp.App().Settings().GetDuration("heartbeat", 30) * time.Second p.SetHeartbeat(heartBeatTimeout) // 自定义设置心跳包处理函数 p.SetOnPacket(pomeloPacket.Heartbeat, p.onHeartbeat) } func (p *userParserActor) initNewAgent() { // 有新连接时触发创建pomelo.Agent实例 p.SetOnNewAgent(func(newAgent *pomelo.Agent) { // 为每个新连接创建child actor agent childAgent := newChildAgent(newAgent) _, err := p.Child().Create(childAgent.AliasID(), childAgent) if err != nil { clog.Warnf("Create agent fail. err = %v", err) } }) } func (p *userParserActor) initNodeRoute() { p.nodeRouteMap[constant.NodeGame] = gameNodeRoute p.nodeRouteMap[constant.NodeMap] = mapNodeRoute } func (p *userParserActor) initNodeSync() { p.nodeSyncMap[sessions.GameNodeID] = sync2Game p.nodeSyncMap[sessions.MapNodeID] = sync2Map } func (p *userParserActor) initStatusCode() { p.duplicateLoginCode, _ = cherryApp.App().Serializer().Marshal(&cproto.I32{ Value: code.PlayerDuplicateLogin, }) p.notLoginCode, _ = cherryApp.App().Serializer().Marshal(&cproto.I32{ Value: code.PlayerNotLogin, }) } // 监听节点移除事件,当接收到(game/map)节点移除的消息时踢掉当前gate的相关session func (p *userParserActor) initNodeKick() { p.App().Discovery().OnRemoveMember(func(member cfacade.IMember) { if member.GetNodeType() == constant.NodeGame { clog.Infof("Kick Sessions with [NodeType = %v, NodeID = %v]", member.GetNodeType(), member.GetNodeID()) p.playerKick(&pb.GateKick{ Type: pb.GateKick_GameNode, StrValue: member.GetNodeID(), Reason: p.kickCode, Closed: true, }) } if member.GetNodeType() == constant.NodeMap { clog.Infof("Kick Sessions with [NodeType = %v, NodeID = %v]", member.GetNodeType(), member.GetNodeID()) p.playerKick(&pb.GateKick{ Type: pb.GateKick_MapNode, StrValue: member.GetNodeID(), Reason: p.kickCode, Closed: true, }) } }) } func (p *userParserActor) onHeartbeat(pomeloAgent *pomelo.Agent, _ *pomeloPacket.Packet) { var ( now = ctime.Now().ToMillisecond() nowBytes = utils.Int64ToBytesFixed(now, binary.BigEndian) ) pomeloAgent.SendPacket(pomeloPacket.Heartbeat, nowBytes) if clog.Enable(zapcore.DebugLevel) { clog.Debugf("[heartbeat] sid = %s, now = %d", pomeloAgent.SID(), now) } } func (p *userParserActor) playerPush(req *pb.GatePusher) { switch req.Type { case pb.GatePusher_Player: for _, playerID := range req.I64List { if agent, found := cache.GetPlayerAgent(playerID); found { if agent != nil { agent.Push(req.Route, req.Data) } } } case pb.GatePusher_GameNode: if gameMap, found := cache.GetGameAgentsMap(req.StrValue); found { gameMap.Range(func(_ int64, agent *pomelo.Agent) bool { if agent != nil { agent.Push(req.Route, req.Data) } return true }) } case pb.GatePusher_Map: if mapMap, found := cache.GetMapAgentsMap(req.StrValue); found { mapMap.Range(func(_ int64, agent *pomelo.Agent) bool { if agent != nil { agent.Push(req.Route, req.Data) } return true }) } } } func (p *userParserActor) playerKick(req *pb.GateKick) { // 检测是否过滤当前节点 if len(req.FilterNodeIDS) > 0 && slices.Contains(req.FilterNodeIDS, cherryApp.App().NodeID()) { return } switch req.Type { case pb.GateKick_Player: for _, playerID := range req.I64List { if agent, found := cache.GetPlayerAgent(playerID); found { if agent != nil { agent.Kick(req.Reason, req.Closed) } } } case pb.GateKick_GameNode: if gameMap, found := cache.GetGameAgentsMap(req.StrValue); found { gameMap.Range(func(_ int64, agent *pomelo.Agent) bool { if agent != nil { agent.Kick(req.Reason, req.Closed) } return true }) } case pb.GateKick_UID: for _, uid := range req.I64List { if agent, found := pomelo.GetAgentWithUID(uid); found { if agent != nil { agent.Kick(req.Reason, req.Closed) } } } case pb.GateKick_MapNode: if mapMap, found := cache.GetMapAgentsMap(req.StrValue); found { mapMap.Range(func(_ int64, agent *pomelo.Agent) bool { if agent != nil { agent.Kick(req.Reason, req.Closed) } return true }) } } } // onDataRoute 数据路由规则 // // 登录逻辑: // 1.(建立连接)客户端建立连接,服务端对应创建一个agent用于处理玩家消息,actorID == sid // 2.(用户登录)客户端进行帐号登录验证,通过uid绑定当前sid // 3.(角色登录)客户端通过'beforeLoginRoutes'中的协议完成角色登录 func (p *userParserActor) onDataRoute(pomeloAgent *pomelo.Agent, route *pmessage.Route, msg *pmessage.Message) { if clog.PrintLevel(zapcore.DebugLevel) { clog.Debugf("[sid = %s,uid = %d] Request Data Message. [mid = %d, route = %s]", pomeloAgent.SID(), pomeloAgent.UID(), msg.ID, msg.Route, ) } session := pomelo.BuildSession(pomeloAgent, msg) var errCode int32 // 如果不是debug模式,则检查路由规则 if !cprofile.Debug() { errCode = data.ProtoRoute.Contain(msg.Route) if code.IsFail(errCode) { pomeloAgent.ResponseCode(session, errCode, true) return } } // rate check,有uid时则优先uid进行限速检查 if session.Uid > 0 { errCode = rate.Check(cstring.ToString(session.Uid)) } else { errCode = rate.Check(session.Ip) } if code.IsFail(errCode) { pomeloAgent.ResponseCode(session, errCode, true) return } if !session.IsBind() && msg.Route != nameRoute.GateUser_Login { pomeloAgent.Kick(p.notLoginCode, true) return } if pomeloAgent.NodeType() == route.NodeType() { targetPath := cfacade.NewChildPath(pomeloAgent.NodeID(), route.HandleName(), session.Sid) pomelo.LocalDataRoute(pomeloAgent, session, route, msg, targetPath) return } // uid未绑定则踢掉 if !session.IsBind() { pomeloAgent.Kick(p.notLoginCode, true) return } // 如果不是首条路由,则需要验证playerID if msg.Route != nameRoute.GameLogin_Enter { playerID := session.GetString(sessions.PlayerID) if playerID == "" { pomeloAgent.Kick(p.notLoginCode, true) return } } // 找到路由函数并执行 if routeFn, found := p.GetNodeRoute(route.NodeType()); found { routeFn(pomeloAgent, session, route, msg) } } func (p *userParserActor) GetNodeRoute(nodeType string) (routeFunc, bool) { route, found := p.nodeRouteMap[nodeType] return route, found } func (p *userParserActor) GetSyncParser(nodeID string) (SyncFunc, bool) { parser, found := p.nodeSyncMap[nodeID] return parser, found }