diff --git a/common/mq/nats.go b/common/mq/nats.go index 696d5dc6..9d624cd8 100644 --- a/common/mq/nats.go +++ b/common/mq/nats.go @@ -61,7 +61,7 @@ func (m *MessageQueue) recvHandler() { switch netMsg.MsgType { case MsgTypeGame: gameMsg := netMsg.GameMsg - if netMsg.EventId == NormalMsg || netMsg.EventId == UserRegNotify { + if netMsg.EventId == NormalMsg { // protobuf PayloadMessage payloadMessage := m.cmdProtoMap.GetProtoObjByCmdId(gameMsg.CmdId) if payloadMessage == nil { @@ -76,6 +76,7 @@ func (m *MessageQueue) recvHandler() { gameMsg.PayloadMessage = payloadMessage } case MsgTypeFight: + case MsgTypeConnCtrl: } m.netMsgOutput <- netMsg } @@ -97,6 +98,7 @@ func (m *MessageQueue) sendHandler() { gameMsg.PayloadMessageData = payloadMessageData } case MsgTypeFight: + case MsgTypeConnCtrl: } // msgpack NetMsg netMsgData, err := msgpack.Marshal(netMsg) diff --git a/common/mq/net_msg.go b/common/mq/net_msg.go index 62789077..ff73266f 100644 --- a/common/mq/net_msg.go +++ b/common/mq/net_msg.go @@ -5,35 +5,45 @@ import pb "google.golang.org/protobuf/proto" const ( MsgTypeGame = iota MsgTypeFight + MsgTypeConnCtrl ) type NetMsg struct { - MsgType uint8 `msgpack:"MsgType"` - EventId uint16 `msgpack:"EventId"` - Topic string `msgpack:"-"` - GameMsg *GameMsg `msgpack:"GameMsg"` - FightMsg *FightMsg `msgpack:"FightMsg"` + MsgType uint8 `msgpack:"MsgType"` + EventId uint16 `msgpack:"EventId"` + Topic string `msgpack:"-"` + GameMsg *GameMsg `msgpack:"GameMsg"` + FightMsg *FightMsg `msgpack:"FightMsg"` + ConnCtrlMsg *ConnCtrlMsg `msgpack:"ConnCtrlMsg"` } const ( NormalMsg = iota - UserRegNotify - UserLoginNotify UserOfflineNotify - ClientRttNotify - ClientTimeNotify ) type GameMsg struct { UserId uint32 `msgpack:"UserId"` CmdId uint16 `msgpack:"CmdId"` ClientSeq uint32 `msgpack:"ClientSeq"` - ClientRtt uint32 `msgpack:"ClientRtt"` - ClientTime uint32 `msgpack:"ClientTime"` PayloadMessage pb.Message `msgpack:"-"` PayloadMessageData []byte `msgpack:"PayloadMessageData"` } +const ( + ClientRttNotify = iota + ClientTimeNotify + KickPlayerNotify +) + +type ConnCtrlMsg struct { + UserId uint32 `msgpack:"UserId"` + ClientRtt uint32 `msgpack:"ClientRtt"` + ClientTime uint32 `msgpack:"ClientTime"` + KickUserId uint32 `msgpack:"KickUserId"` + KickReason uint32 `msgpack:"KickReason"` +} + const ( AddFightRoutine = iota DelFightRoutine diff --git a/gate/app/app.go b/gate/app/app.go index fbb818c8..0f66700d 100644 --- a/gate/app/app.go +++ b/gate/app/app.go @@ -24,6 +24,7 @@ func Run(ctx context.Context, configFile string) error { connectManager := net.NewKcpConnectManager(messageQueue) connectManager.Start() + defer connectManager.Stop() go func() { outputChan := connectManager.GetKcpEventOutputChan() diff --git a/gate/net/forward.go b/gate/net/forward.go index 160addad..0a68d52e 100644 --- a/gate/net/forward.go +++ b/gate/net/forward.go @@ -22,26 +22,23 @@ import ( ) const ( - ConnWaitToken = iota - ConnWaitLogin - ConnAlive + ConnEst = iota + ConnActive ConnClose ) // 发送消息到GS func (k *KcpConnectManager) recvMsgHandle(protoMsg *ProtoMsg, session *Session) { userId := session.userId - headMeta := session.headMeta connState := session.connState if protoMsg.HeadMessage == nil { logger.Error("recv null head msg: %v", protoMsg) } - headMeta.seq = protoMsg.HeadMessage.ClientSequenceId // gate本地处理的请求 switch protoMsg.CmdId { case cmd.GetPlayerTokenReq: // 获取玩家token请求 - if connState != ConnWaitToken { + if connState != ConnEst { return } getPlayerTokenReq := protoMsg.PayloadMessage.(*proto.GetPlayerTokenReq) @@ -56,51 +53,9 @@ func (k *KcpConnectManager) recvMsgHandle(protoMsg *ProtoMsg, session *Session) rsp.HeadMessage = k.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId) rsp.PayloadMessage = getPlayerTokenRsp k.localMsgOutput <- rsp - case cmd.PlayerLoginReq: - // 玩家登录请求 - if connState != ConnWaitLogin { - return - } - playerLoginReq := protoMsg.PayloadMessage.(*proto.PlayerLoginReq) - playerLoginRsp := k.playerLogin(playerLoginReq, session) - if playerLoginRsp == nil { - return - } - // 返回数据到客户端 - rsp := new(ProtoMsg) - rsp.ConvId = protoMsg.ConvId - rsp.CmdId = cmd.PlayerLoginRsp - rsp.HeadMessage = k.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId) - rsp.PayloadMessage = playerLoginRsp - k.localMsgOutput <- rsp - // 登录成功 通知GS初始化相关数据 - gameMsg := new(mq.GameMsg) - gameMsg.UserId = userId - gameMsg.ClientSeq = headMeta.seq - k.messageQueue.SendToGs("1", &mq.NetMsg{ - MsgType: mq.MsgTypeGame, - EventId: mq.UserLoginNotify, - GameMsg: gameMsg, - }) - logger.Info("send to gs user login ok, ConvId: %v, UserId: %v", protoMsg.ConvId, gameMsg.UserId) - case cmd.SetPlayerBornDataReq: - // 玩家注册请求 - if connState != ConnAlive { - return - } - gameMsg := new(mq.GameMsg) - gameMsg.UserId = userId - gameMsg.CmdId = cmd.SetPlayerBornDataReq - gameMsg.ClientSeq = protoMsg.HeadMessage.ClientSequenceId - gameMsg.PayloadMessage = protoMsg.PayloadMessage - k.messageQueue.SendToGs("1", &mq.NetMsg{ - MsgType: mq.MsgTypeGame, - EventId: mq.UserRegNotify, - GameMsg: gameMsg, - }) case cmd.PlayerForceExitReq: // 玩家退出游戏请求 - if connState != ConnAlive { + if connState != ConnActive { return } k.kcpEventInput <- &KcpEvent{ @@ -110,13 +65,9 @@ func (k *KcpConnectManager) recvMsgHandle(protoMsg *ProtoMsg, session *Session) } case cmd.PingReq: // ping请求 - if connState != ConnAlive { - return - } pingReq := protoMsg.PayloadMessage.(*proto.PingReq) logger.Debug("user ping req, data: %v", pingReq.String()) // 返回数据到客户端 - // TODO 记录客户端最后一次ping时间做超时下线处理 pingRsp := new(proto.PingRsp) pingRsp.ClientTime = pingReq.ClientTime rsp := new(ProtoMsg) @@ -125,32 +76,45 @@ func (k *KcpConnectManager) recvMsgHandle(protoMsg *ProtoMsg, session *Session) rsp.HeadMessage = k.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId) rsp.PayloadMessage = pingRsp k.localMsgOutput <- rsp - // 通知GS玩家客户端的本地时钟 - gameMsg := new(mq.GameMsg) - gameMsg.UserId = userId - gameMsg.ClientTime = pingReq.ClientTime - k.messageQueue.SendToGs("1", &mq.NetMsg{ - MsgType: mq.MsgTypeGame, - EventId: mq.ClientTimeNotify, - GameMsg: gameMsg, - }) - // RTT logger.Debug("convId: %v, RTO: %v, SRTT: %v, RTTVar: %v", protoMsg.ConvId, session.conn.GetRTO(), session.conn.GetSRTT(), session.conn.GetSRTTVar()) - rtt := session.conn.GetSRTT() - // 通知GS玩家客户端往返时延 - gameMsg = new(mq.GameMsg) - gameMsg.UserId = userId - gameMsg.ClientRtt = uint32(rtt) - k.messageQueue.SendToGs("1", &mq.NetMsg{ - MsgType: mq.MsgTypeGame, - EventId: mq.ClientRttNotify, - GameMsg: gameMsg, - }) - default: - // 未登录禁止访问 - if connState != ConnAlive { + if connState != ConnActive { return } + // 通知GS玩家客户端的本地时钟 + connCtrlMsg := new(mq.ConnCtrlMsg) + connCtrlMsg.UserId = userId + connCtrlMsg.ClientTime = pingReq.ClientTime + k.messageQueue.SendToGs("1", &mq.NetMsg{ + MsgType: mq.MsgTypeConnCtrl, + EventId: mq.ClientTimeNotify, + ConnCtrlMsg: connCtrlMsg, + }) + // 通知GS玩家客户端往返时延 + rtt := session.conn.GetSRTT() + connCtrlMsg = new(mq.ConnCtrlMsg) + connCtrlMsg.UserId = userId + connCtrlMsg.ClientRtt = uint32(rtt) + k.messageQueue.SendToGs("1", &mq.NetMsg{ + MsgType: mq.MsgTypeConnCtrl, + EventId: mq.ClientRttNotify, + ConnCtrlMsg: connCtrlMsg, + }) + default: + if connState != ConnActive && !(protoMsg.CmdId == cmd.PlayerLoginReq || protoMsg.CmdId == cmd.SetPlayerBornDataReq) { + logger.Error("conn not active so drop packet, cmdId: %v, userId: %v, convId: %v", protoMsg.CmdId, userId, protoMsg.ConvId) + return + } + // 转发到GS + gameMsg := new(mq.GameMsg) + gameMsg.UserId = userId + gameMsg.CmdId = protoMsg.CmdId + gameMsg.ClientSeq = protoMsg.HeadMessage.ClientSequenceId + gameMsg.PayloadMessage = protoMsg.PayloadMessage + k.messageQueue.SendToGs("1", &mq.NetMsg{ + MsgType: mq.MsgTypeGame, + EventId: mq.NormalMsg, + GameMsg: gameMsg, + }) // 转发到FIGHT if protoMsg.CmdId == cmd.CombatInvocationsNotify { gameMsg := new(mq.GameMsg) @@ -164,17 +128,6 @@ func (k *KcpConnectManager) recvMsgHandle(protoMsg *ProtoMsg, session *Session) GameMsg: gameMsg, }) } - // 转发到GS - gameMsg := new(mq.GameMsg) - gameMsg.UserId = userId - gameMsg.CmdId = protoMsg.CmdId - gameMsg.ClientSeq = protoMsg.HeadMessage.ClientSequenceId - gameMsg.PayloadMessage = protoMsg.PayloadMessage - k.messageQueue.SendToGs("1", &mq.NetMsg{ - MsgType: mq.MsgTypeGame, - EventId: mq.NormalMsg, - GameMsg: gameMsg, - }) } } @@ -208,26 +161,40 @@ func (k *KcpConnectManager) sendMsgHandle() { case protoMsg := <-k.localMsgOutput: sendToClientFn(protoMsg) case netMsg := <-k.messageQueue.GetNetMsg(): - if netMsg.MsgType != mq.MsgTypeGame { - logger.Error("recv unknown msg type from game server, msg type: %v", netMsg.MsgType) - continue + switch netMsg.MsgType { + case mq.MsgTypeGame: + if netMsg.EventId != mq.NormalMsg { + logger.Error("recv unknown event from game server, event id: %v", netMsg.EventId) + continue + } + gameMsg := netMsg.GameMsg + convId, exist := userIdConvMap[gameMsg.UserId] + if !exist { + logger.Error("can not find convId by userId") + continue + } + protoMsg := new(ProtoMsg) + protoMsg.ConvId = convId + protoMsg.CmdId = gameMsg.CmdId + protoMsg.HeadMessage = k.getHeadMsg(gameMsg.ClientSeq) + protoMsg.PayloadMessage = gameMsg.PayloadMessage + sendToClientFn(protoMsg) + case mq.MsgTypeConnCtrl: + if netMsg.EventId != mq.KickPlayerNotify { + continue + } + connCtrlMsg := netMsg.ConnCtrlMsg + convId, exist := userIdConvMap[connCtrlMsg.KickUserId] + if !exist { + logger.Error("can not find convId by userId") + continue + } + k.kcpEventInput <- &KcpEvent{ + ConvId: convId, + EventId: KcpConnForceClose, + EventMessage: connCtrlMsg.KickReason, + } } - if netMsg.EventId != mq.NormalMsg { - logger.Error("recv unknown event from game server, event id: %v", netMsg.EventId) - continue - } - gameMsg := netMsg.GameMsg - convId, exist := userIdConvMap[gameMsg.UserId] - if !exist { - logger.Error("can not find convId by userId") - continue - } - protoMsg := new(ProtoMsg) - protoMsg.ConvId = convId - protoMsg.CmdId = gameMsg.CmdId - protoMsg.HeadMessage = k.getHeadMsg(gameMsg.ClientSeq) - protoMsg.PayloadMessage = gameMsg.PayloadMessage - sendToClientFn(protoMsg) } } } @@ -276,15 +243,16 @@ func (k *KcpConnectManager) getPlayerToken(req *proto.GetPlayerTokenReq, session oldSession := k.GetSessionByUserId(tokenVerifyRsp.PlayerID) if oldSession != nil { // 顶号 + kickFinishNotifyChan := make(chan bool) k.kcpEventInput <- &KcpEvent{ ConvId: oldSession.conn.GetConv(), - EventId: KcpConnForceClose, - EventMessage: uint32(kcp.EnetServerRelogin), + EventId: KcpConnRelogin, + EventMessage: kickFinishNotifyChan, } + <-kickFinishNotifyChan } // 关联玩家uid和连接信息 session.userId = tokenVerifyRsp.PlayerID - session.connState = ConnWaitLogin k.SetSession(session, session.conn.GetConv(), session.userId) k.createSessionChan <- session // 返回响应 @@ -344,7 +312,6 @@ func (k *KcpConnectManager) getPlayerToken(req *proto.GetPlayerTokenReq, session timeRand := random.GetTimeRand() serverSeedUint64 := timeRand.Uint64() session.seed = serverSeedUint64 - session.changeXorKey = true seedUint64 := serverSeedUint64 ^ clientSeedUint64 seedBuf := new(bytes.Buffer) err = binary.Write(seedBuf, binary.BigEndian, seedUint64) @@ -369,27 +336,3 @@ func (k *KcpConnectManager) getPlayerToken(req *proto.GetPlayerTokenReq, session } return rsp } - -func (k *KcpConnectManager) playerLogin(req *proto.PlayerLoginReq, session *Session) (rsp *proto.PlayerLoginRsp) { - logger.Debug("player login, info: %v", req.String()) - // TODO 验证token - session.connState = ConnAlive - // 返回响应 - rsp = new(proto.PlayerLoginRsp) - rsp.IsUseAbilityHash = true - rsp.AbilityHashCode = -228935105 - rsp.GameBiz = "hk4e_cn" - rsp.IsScOpen = false - rsp.RegisterCps = "taptap" - rsp.CountryCode = "CN" - rsp.Birthday = "2000-01-01" - rsp.TotalTickTime = 1185941.871788 - rsp.ClientDataVersion = k.regionCurr.RegionInfo.ClientDataVersion - rsp.ClientSilenceDataVersion = k.regionCurr.RegionInfo.ClientSilenceDataVersion - rsp.ClientMd5 = k.regionCurr.RegionInfo.ClientDataMd5 - rsp.ClientSilenceMd5 = k.regionCurr.RegionInfo.ClientSilenceDataMd5 - rsp.ResVersionConfig = k.regionCurr.RegionInfo.ResVersionConfig - rsp.ClientVersionSuffix = k.regionCurr.RegionInfo.ClientVersionSuffix - rsp.ClientSilenceVersionSuffix = k.regionCurr.RegionInfo.ClientSilenceVersionSuffix - return rsp -} diff --git a/gate/net/kcp_connect_manager.go b/gate/net/kcp_connect_manager.go index d793b8df..7bd24a42 100644 --- a/gate/net/kcp_connect_manager.go +++ b/gate/net/kcp_connect_manager.go @@ -16,7 +16,6 @@ import ( "hk4e/pkg/logger" "hk4e/pkg/random" "hk4e/protocol/cmd" - "hk4e/protocol/proto" ) const PacketFreqLimit = 1000 @@ -35,7 +34,6 @@ type KcpConnectManager struct { destroySessionChan chan *Session // 密钥相关 dispatchKey []byte - regionCurr *proto.QueryCurrRegionHttpRsp signRsaKey []byte encRsaKeyMap map[string][]byte } @@ -58,9 +56,6 @@ func NewKcpConnectManager(messageQueue *mq.MessageQueue) (r *KcpConnectManager) func (k *KcpConnectManager) Start() { // 读取密钥相关文件 k.signRsaKey, k.encRsaKeyMap, _ = region.LoadRsaKey() - // region - regionCurr, _, _ := region.InitRegion(config.CONF.Hk4e.KcpAddr, config.CONF.Hk4e.KcpPort) - k.regionCurr = regionCurr // key dispatchEc2bSeedRsp, err := httpclient.Get[controller.DispatchEc2bSeedRsp]("http://127.0.0.1:8080/dispatch/ec2b/seed", "") if err != nil { @@ -89,6 +84,11 @@ func (k *KcpConnectManager) Start() { go k.acceptHandle(listener) } +func (k *KcpConnectManager) Stop() { + k.closeAllKcpConn() + time.Sleep(time.Second * 3) +} + func (k *KcpConnectManager) acceptHandle(listener *kcp.Listener) { logger.Debug("accept handle start") for { @@ -107,16 +107,13 @@ func (k *KcpConnectManager) acceptHandle(listener *kcp.Listener) { logger.Debug("client connect, convId: %v", convId) kcpRawSendChan := make(chan *ProtoMsg, 1000) session := &Session{ - conn: conn, - connState: ConnWaitToken, - userId: 0, - headMeta: &ClientHeadMeta{ - seq: 0, - }, - kcpRawSendChan: kcpRawSendChan, - seed: 0, - xorKey: k.dispatchKey, - changeXorKey: false, + conn: conn, + connState: ConnEst, + userId: 0, + kcpRawSendChan: kcpRawSendChan, + seed: 0, + xorKey: k.dispatchKey, + changeXorKeyFin: false, } go k.recvHandle(session) go k.sendHandle(session) @@ -203,24 +200,19 @@ func (k *KcpConnectManager) enetHandle(listener *kcp.Listener) { } } -type ClientHeadMeta struct { - seq uint32 -} - type Session struct { - conn *kcp.UDPSession - connState uint8 - userId uint32 - headMeta *ClientHeadMeta - kcpRawSendChan chan *ProtoMsg - seed uint64 - xorKey []byte - changeXorKey bool + conn *kcp.UDPSession + connState uint8 + userId uint32 + kcpRawSendChan chan *ProtoMsg + seed uint64 + xorKey []byte + changeXorKeyFin bool } +// 接收 func (k *KcpConnectManager) recvHandle(session *Session) { logger.Debug("recv handle start") - // 接收 conn := session.conn convId := conn.GetConv() pktFreqLimitCounter := 0 @@ -234,14 +226,6 @@ func (k *KcpConnectManager) recvHandle(session *Session) { k.closeKcpConn(session, kcp.EnetServerKick) break } - if session.changeXorKey { - session.changeXorKey = false - keyBlock := random.NewKeyBlock(session.seed) - xorKey := keyBlock.XorKey() - key := make([]byte, 4096) - copy(key, xorKey[:]) - session.xorKey = key - } // 收包频率限制 pktFreqLimitCounter++ now := time.Now().UnixNano() @@ -267,9 +251,9 @@ func (k *KcpConnectManager) recvHandle(session *Session) { } } +// 发送 func (k *KcpConnectManager) sendHandle(session *Session) { logger.Debug("send handle start") - // 发送 conn := session.conn convId := conn.GetConv() for { @@ -292,9 +276,33 @@ func (k *KcpConnectManager) sendHandle(session *Session) { k.closeKcpConn(session, kcp.EnetServerKick) break } + if session.changeXorKeyFin == false && protoMsg.CmdId == cmd.GetPlayerTokenRsp { + logger.Debug("change session xor key, convId: %v", convId) + session.changeXorKeyFin = true + keyBlock := random.NewKeyBlock(session.seed) + xorKey := keyBlock.XorKey() + key := make([]byte, 4096) + copy(key, xorKey[:]) + session.xorKey = key + } + if protoMsg.CmdId == cmd.PlayerLoginRsp { + logger.Debug("session active, convId: %v", convId) + session.connState = ConnActive + } } } +func (k *KcpConnectManager) forceCloseKcpConn(convId uint64, reason uint32) { + // 强制关闭某个连接 + session := k.GetSessionByConvId(convId) + if session == nil { + logger.Error("session not exist, convId: %v", convId) + return + } + k.closeKcpConn(session, reason) + logger.Info("conn has been force close, convId: %v", convId) +} + func (k *KcpConnectManager) closeKcpConn(session *Session, enetType uint32) { if session.connState == ConnClose { return @@ -330,19 +338,14 @@ func (k *KcpConnectManager) closeKcpConn(session *Session, enetType uint32) { } func (k *KcpConnectManager) closeAllKcpConn() { - closeConnList := make([]*kcp.UDPSession, 0) + sessionList := make([]*Session, 0) k.sessionMapLock.RLock() - for _, v := range k.sessionConvIdMap { - closeConnList = append(closeConnList, v.conn) + for _, session := range k.sessionConvIdMap { + sessionList = append(sessionList, session) } k.sessionMapLock.RUnlock() - for _, v := range closeConnList { - // 关闭连接 - v.SendEnetNotify(&kcp.Enet{ - ConnType: kcp.ConnEnetFin, - EnetType: kcp.EnetServerShutdown, - }) - _ = v.Close() + for _, session := range sessionList { + k.closeKcpConn(session, kcp.EnetServerShutdown) } } diff --git a/gate/net/kcp_event.go b/gate/net/kcp_event.go index e02eae40..a89cbadc 100644 --- a/gate/net/kcp_event.go +++ b/gate/net/kcp_event.go @@ -11,6 +11,7 @@ const ( KcpConnForceClose = iota KcpAllConnForceClose KcpGateOpenState + KcpConnRelogin KcpConnCloseNotify KcpConnEstNotify KcpConnAddrChangeNotify @@ -38,23 +39,12 @@ func (k *KcpConnectManager) eventHandle() { logger.Info("kcp manager recv event, ConvId: %v, EventId: %v, EventMessage Type: %v", event.ConvId, event.EventId, reflect.TypeOf(event.EventMessage)) switch event.EventId { case KcpConnForceClose: - // 强制关闭某个连接 - session := k.GetSessionByConvId(event.ConvId) - if session == nil { - logger.Error("session not exist, convId: %v", event.ConvId) - continue - } reason, ok := event.EventMessage.(uint32) if !ok { logger.Error("event KcpConnForceClose msg type error") - continue + return } - session.conn.SendEnetNotify(&kcp.Enet{ - ConnType: kcp.ConnEnetFin, - EnetType: reason, - }) - _ = session.conn.Close() - logger.Info("conn has been force close, convId: %v", event.ConvId) + k.forceCloseKcpConn(event.ConvId, reason) case KcpAllConnForceClose: // 强制关闭所有连接 k.closeAllKcpConn() @@ -70,6 +60,14 @@ func (k *KcpConnectManager) eventHandle() { if openState == false { k.closeAllKcpConn() } + case KcpConnRelogin: + kickFinishNotifyChan, ok := event.EventMessage.(chan bool) + if !ok { + logger.Error("event KcpConnRelogin msg type error") + continue + } + k.forceCloseKcpConn(event.ConvId, kcp.EnetServerRelogin) + kickFinishNotifyChan <- true } } } diff --git a/gs/app/app.go b/gs/app/app.go index db26750a..332a13f1 100644 --- a/gs/app/app.go +++ b/gs/app/app.go @@ -49,7 +49,6 @@ func Run(ctx context.Context, configFile string) error { defer messageQueue.Close() gameManager := game.NewGameManager(db, messageQueue) - gameManager.Start() defer gameManager.Stop() c := make(chan os.Signal, 1) diff --git a/gs/game/game_manager.go b/gs/game/game_manager.go index 429c0e53..f2442982 100644 --- a/gs/game/game_manager.go +++ b/gs/game/game_manager.go @@ -4,6 +4,7 @@ import ( "time" "hk4e/common/mq" + "hk4e/gate/kcp" "hk4e/gs/dao" "hk4e/gs/model" "hk4e/pkg/alg" @@ -41,80 +42,104 @@ func NewGameManager(dao *dao.Dao, messageQueue *mq.MessageQueue) (r *GameManager WORLD_MANAGER = NewWorldManager(r.snowflake) TICK_MANAGER = NewTickManager() COMMAND_MANAGER = NewCommandManager() + r.run() return r } -func (g *GameManager) Start() { +func (g *GameManager) run() { ROUTE_MANAGER.InitRoute() USER_MANAGER.StartAutoSaveUser() - go func() { - intervalTime := time.Second.Nanoseconds() * 60 - lastTime := time.Now().UnixNano() - routeCost := int64(0) - tickCost := int64(0) - localEventCost := int64(0) - commandCost := int64(0) - for { - now := time.Now().UnixNano() - if now-lastTime > intervalTime { - routeCost /= 1e6 - tickCost /= 1e6 - localEventCost /= 1e6 - commandCost /= 1e6 - logger.Info("[GAME MAIN LOOP] cpu time cost detail, routeCost: %vms, tickCost: %vms, localEventCost: %vms, commandCost: %vms", - routeCost, tickCost, localEventCost, commandCost) - totalCost := routeCost + tickCost + localEventCost + commandCost - logger.Info("[GAME MAIN LOOP] cpu time cost percent, routeCost: %v%%, tickCost: %v%%, localEventCost: %v%%, commandCost: %v%%", - float32(routeCost)/float32(totalCost)*100.0, - float32(tickCost)/float32(totalCost)*100.0, - float32(localEventCost)/float32(totalCost)*100.0, - float32(commandCost)/float32(totalCost)*100.0) - logger.Info("[GAME MAIN LOOP] total cpu time cost detail, totalCost: %vms", - totalCost) - logger.Info("[GAME MAIN LOOP] total cpu time cost percent, totalCost: %v%%", - float32(totalCost)/float32(intervalTime/1e6)*100.0) - lastTime = now - routeCost = 0 - tickCost = 0 - localEventCost = 0 - commandCost = 0 - } - select { - case netMsg := <-g.messageQueue.GetNetMsg(): - // 接收客户端消息 - start := time.Now().UnixNano() - ROUTE_MANAGER.RouteHandle(netMsg) - end := time.Now().UnixNano() - routeCost += end - start - case <-TICK_MANAGER.ticker.C: - // 游戏服务器定时帧 - start := time.Now().UnixNano() - TICK_MANAGER.OnGameServerTick() - end := time.Now().UnixNano() - tickCost += end - start - case localEvent := <-LOCAL_EVENT_MANAGER.localEventChan: - // 处理本地事件 - start := time.Now().UnixNano() - LOCAL_EVENT_MANAGER.LocalEventHandle(localEvent) - end := time.Now().UnixNano() - localEventCost += end - start - case command := <-COMMAND_MANAGER.commandTextInput: - // 处理传入的命令 (普通玩家 GM命令) - start := time.Now().UnixNano() - COMMAND_MANAGER.HandleCommand(command) - end := time.Now().UnixNano() - commandCost += end - start - } + go g.gameMainLoopD() +} + +func (g *GameManager) gameMainLoopD() { + for times := 1; times <= 1000; times++ { + logger.Warn("start game main loop, times: %v", times) + g.gameMainLoop() + logger.Warn("game main loop stop") + } +} + +func (g *GameManager) gameMainLoop() { + defer func() { + if err := recover(); err != nil { + logger.Error("!!! GAME MAIN LOOP PANIC !!!") + logger.Error("error: %v", err) + logger.Error("stack: %v", logger.Stack()) } }() + intervalTime := time.Second.Nanoseconds() * 60 + lastTime := time.Now().UnixNano() + routeCost := int64(0) + tickCost := int64(0) + localEventCost := int64(0) + commandCost := int64(0) + for { + now := time.Now().UnixNano() + if now-lastTime > intervalTime { + routeCost /= 1e6 + tickCost /= 1e6 + localEventCost /= 1e6 + commandCost /= 1e6 + logger.Info("[GAME MAIN LOOP] cpu time cost detail, routeCost: %vms, tickCost: %vms, localEventCost: %vms, commandCost: %vms", + routeCost, tickCost, localEventCost, commandCost) + totalCost := routeCost + tickCost + localEventCost + commandCost + logger.Info("[GAME MAIN LOOP] cpu time cost percent, routeCost: %v%%, tickCost: %v%%, localEventCost: %v%%, commandCost: %v%%", + float32(routeCost)/float32(totalCost)*100.0, + float32(tickCost)/float32(totalCost)*100.0, + float32(localEventCost)/float32(totalCost)*100.0, + float32(commandCost)/float32(totalCost)*100.0) + logger.Info("[GAME MAIN LOOP] total cpu time cost detail, totalCost: %vms", + totalCost) + logger.Info("[GAME MAIN LOOP] total cpu time cost percent, totalCost: %v%%", + float32(totalCost)/float32(intervalTime/1e6)*100.0) + lastTime = now + routeCost = 0 + tickCost = 0 + localEventCost = 0 + commandCost = 0 + } + select { + case netMsg := <-g.messageQueue.GetNetMsg(): + // 接收客户端消息 + start := time.Now().UnixNano() + ROUTE_MANAGER.RouteHandle(netMsg) + end := time.Now().UnixNano() + routeCost += end - start + case <-TICK_MANAGER.ticker.C: + // 游戏服务器定时帧 + start := time.Now().UnixNano() + TICK_MANAGER.OnGameServerTick() + end := time.Now().UnixNano() + tickCost += end - start + case localEvent := <-LOCAL_EVENT_MANAGER.localEventChan: + // 处理本地事件 + start := time.Now().UnixNano() + LOCAL_EVENT_MANAGER.LocalEventHandle(localEvent) + end := time.Now().UnixNano() + localEventCost += end - start + case command := <-COMMAND_MANAGER.commandTextInput: + // 处理传入的命令 (普通玩家 GM命令) + start := time.Now().UnixNano() + COMMAND_MANAGER.HandleCommand(command) + end := time.Now().UnixNano() + commandCost += end - start + } + } } func (g *GameManager) Stop() { + // 下线玩家 + userList := USER_MANAGER.GetAllOnlineUserList() + for _, player := range userList { + g.DisconnectPlayer(player.PlayerID, kcp.EnetServerShutdown) + } + time.Sleep(time.Second * 5) // 保存玩家数据 LOCAL_EVENT_MANAGER.localEventChan <- &LocalEvent{ EventId: RunUserCopyAndSave, } - time.Sleep(time.Second * 3) + time.Sleep(time.Second * 5) // g.worldManager.worldStatic.SaveTerrain() } @@ -197,6 +222,14 @@ func (g *GameManager) ReconnectPlayer(userId uint32) { g.SendMsg(cmd.ClientReconnectNotify, userId, 0, new(proto.ClientReconnectNotify)) } -func (g *GameManager) DisconnectPlayer(userId uint32) { - g.SendMsg(cmd.ServerDisconnectClientNotify, userId, 0, new(proto.ServerDisconnectClientNotify)) +func (g *GameManager) DisconnectPlayer(userId uint32, reason uint32) { + g.messageQueue.SendToGate("1", &mq.NetMsg{ + MsgType: mq.MsgTypeConnCtrl, + EventId: mq.KickPlayerNotify, + ConnCtrlMsg: &mq.ConnCtrlMsg{ + KickUserId: userId, + KickReason: reason, + }, + }) + // g.SendMsg(cmd.ServerDisconnectClientNotify, userId, 0, new(proto.ServerDisconnectClientNotify)) } diff --git a/gs/game/route_manager.go b/gs/game/route_manager.go index 17879a4c..2713f973 100644 --- a/gs/game/route_manager.go +++ b/gs/game/route_manager.go @@ -2,6 +2,7 @@ package game import ( "hk4e/common/mq" + "hk4e/gate/kcp" "hk4e/gs/model" "hk4e/pkg/logger" "hk4e/protocol/cmd" @@ -37,8 +38,7 @@ func (r *RouteManager) doRoute(cmdId uint16, userId uint32, clientSeq uint32, pa player := USER_MANAGER.GetOnlineUser(userId) if player == nil { logger.Error("player is nil, uid: %v", userId) - // 临时为了调试便捷搞的重连 生产环境请务必去除 不然新用户会一直重连不能进入 - // GAME_MANAGER.ReconnectPlayer(userId) + GAME_MANAGER.DisconnectPlayer(userId, kcp.EnetNotFoundSession) return } player.ClientSeq = clientSeq @@ -119,22 +119,30 @@ func (r *RouteManager) InitRoute() { } func (r *RouteManager) RouteHandle(netMsg *mq.NetMsg) { - if netMsg.MsgType != mq.MsgTypeGame { - return - } - gameMsg := netMsg.GameMsg - switch netMsg.EventId { - case mq.NormalMsg: - r.doRoute(gameMsg.CmdId, gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage) - case mq.UserRegNotify: - GAME_MANAGER.OnReg(gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage) - case mq.UserLoginNotify: - GAME_MANAGER.OnLogin(gameMsg.UserId, gameMsg.ClientSeq) - case mq.UserOfflineNotify: - GAME_MANAGER.OnUserOffline(gameMsg.UserId) - case mq.ClientRttNotify: - GAME_MANAGER.ClientRttNotify(gameMsg.UserId, gameMsg.ClientRtt) - case mq.ClientTimeNotify: - GAME_MANAGER.ClientTimeNotify(gameMsg.UserId, gameMsg.ClientTime) + switch netMsg.MsgType { + case mq.MsgTypeGame: + gameMsg := netMsg.GameMsg + switch netMsg.EventId { + case mq.NormalMsg: + if gameMsg.CmdId == cmd.PlayerLoginReq { + GAME_MANAGER.PlayerLoginReq(gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage) + return + } + if gameMsg.CmdId == cmd.SetPlayerBornDataReq { + GAME_MANAGER.SetPlayerBornDataReq(gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage) + return + } + r.doRoute(gameMsg.CmdId, gameMsg.UserId, gameMsg.ClientSeq, gameMsg.PayloadMessage) + case mq.UserOfflineNotify: + GAME_MANAGER.OnUserOffline(gameMsg.UserId) + } + case mq.MsgTypeConnCtrl: + connCtrlMsg := netMsg.ConnCtrlMsg + switch netMsg.EventId { + case mq.ClientRttNotify: + GAME_MANAGER.ClientRttNotify(connCtrlMsg.UserId, connCtrlMsg.ClientRtt) + case mq.ClientTimeNotify: + GAME_MANAGER.ClientTimeNotify(connCtrlMsg.UserId, connCtrlMsg.ClientTime) + } } } diff --git a/gs/game/user_fight_sync.go b/gs/game/user_fight_sync.go index 2b22b58c..03d45f92 100644 --- a/gs/game/user_fight_sync.go +++ b/gs/game/user_fight_sync.go @@ -18,6 +18,9 @@ func DoForward[IET model.InvokeEntryType](player *model.Player, req pb.Message, } cmdId := cmdProtoMap.GetCmdIdByProtoObj(req) world := WORLD_MANAGER.GetWorldByID(player.WorldId) + if world == nil { + return + } if invokeHandler.AllLen() == 0 && invokeHandler.AllExceptCurLen() == 0 && invokeHandler.HostLen() == 0 { ntf := cmdProtoMap.GetProtoObjByCmdId(cmdId) for _, fieldName := range copyFieldList { @@ -90,6 +93,9 @@ func (g *GameManager) CombatInvocationsNotify(player *model.Player, payloadMsg p return } world := WORLD_MANAGER.GetWorldByID(player.WorldId) + if world == nil { + return + } scene := world.GetSceneById(player.SceneId) for _, entry := range req.InvokeList { switch entry.ArgumentType { diff --git a/gs/game/user_login.go b/gs/game/user_login.go index b387dce3..a6c8ccd4 100644 --- a/gs/game/user_login.go +++ b/gs/game/user_login.go @@ -14,6 +14,20 @@ import ( pb "google.golang.org/protobuf/proto" ) +func (g *GameManager) PlayerLoginReq(userId uint32, clientSeq uint32, payloadMsg pb.Message) { + logger.Info("user login req, uid: %v", userId) + req := payloadMsg.(*proto.PlayerLoginReq) + logger.Debug("login data: %v", req) + g.OnLogin(userId, clientSeq) +} + +func (g *GameManager) SetPlayerBornDataReq(userId uint32, clientSeq uint32, payloadMsg pb.Message) { + logger.Info("user reg req, uid: %v", userId) + req := payloadMsg.(*proto.SetPlayerBornDataReq) + logger.Debug("reg data: %v", req) + g.OnReg(userId, clientSeq, req) +} + func (g *GameManager) OnLogin(userId uint32, clientSeq uint32) { logger.Info("user login, uid: %v", userId) player, asyncWait := USER_MANAGER.OnlineUser(userId, clientSeq) @@ -117,6 +131,17 @@ func (g *GameManager) LoginNotify(userId uint32, player *model.Player, clientSeq g.SendMsg(cmd.PlayerStoreNotify, userId, clientSeq, g.PacketPlayerStoreNotify(player)) g.SendMsg(cmd.AvatarDataNotify, userId, clientSeq, g.PacketAvatarDataNotify(player)) g.SendMsg(cmd.OpenStateUpdateNotify, userId, clientSeq, g.PacketOpenStateUpdateNotify()) + playerLoginRsp := &proto.PlayerLoginRsp{ + IsUseAbilityHash: true, + AbilityHashCode: -228935105, + GameBiz: "hk4e_cn", + IsScOpen: false, + RegisterCps: "taptap", + CountryCode: "CN", + Birthday: "2000-01-01", + TotalTickTime: 1185941.871788, + } + g.SendMsg(cmd.PlayerLoginRsp, userId, clientSeq, playerLoginRsp) } func (g *GameManager) PacketPlayerDataNotify(player *model.Player) *proto.PlayerDataNotify { diff --git a/gs/game/user_multiplayer.go b/gs/game/user_multiplayer.go index 9f71dc9d..1d32c65a 100644 --- a/gs/game/user_multiplayer.go +++ b/gs/game/user_multiplayer.go @@ -140,7 +140,7 @@ func (g *GameManager) JoinPlayerSceneReq(player *model.Player, payloadMsg pb.Mes g.SendMsg(cmd.LeaveWorldNotify, player.PlayerID, player.ClientSeq, new(proto.LeaveWorldNotify)) - // g.LoginNotify(player.PlayerID, player, 0) + g.LoginNotify(player.PlayerID, player, 0) if hostPlayer.SceneLoadState == model.SceneEnterDone { delete(hostWorld.waitEnterPlayerMap, player.PlayerID) @@ -268,7 +268,6 @@ func (g *GameManager) UserLeaveWorld(player *model.Player) bool { return false } } - g.UserWorldRemovePlayer(oldWorld, player) g.ReconnectPlayer(player.PlayerID) return true } @@ -318,12 +317,13 @@ func (g *GameManager) UserWorldRemovePlayer(world *World, player *model.Player) world.RemovePlayer(player) player.WorldId = 0 - if world.multiplayer && world.GetWorldPlayerNum() > 0 { - g.UpdateWorldPlayerInfo(world, player) - } if world.owner.PlayerID == player.PlayerID { // 房主离开销毁世界 WORLD_MANAGER.DestroyWorld(world.id) + return + } + if world.multiplayer && world.GetWorldPlayerNum() > 0 { + g.UpdateWorldPlayerInfo(world, player) } } diff --git a/gs/game/user_stamina.go b/gs/game/user_stamina.go index b3ad5ff8..259e9d67 100644 --- a/gs/game/user_stamina.go +++ b/gs/game/user_stamina.go @@ -1,10 +1,11 @@ package game import ( - gdc "hk4e/gs/config" "strings" "time" + gdc "hk4e/gs/config" + "hk4e/gdconf" "hk4e/gs/constant" "hk4e/gs/model" @@ -529,7 +530,7 @@ func (g *GameManager) SetPlayerStamina(player *model.Player, stamina uint32) { // 设置玩家的耐力 prop := constant.PlayerPropertyConst.PROP_CUR_PERSIST_STAMINA player.PropertiesMap[prop] = stamina - //logger.Debug("player stamina set, stamina: %v", stamina) + // logger.Debug("player stamina set, stamina: %v", stamina) // PacketPlayerPropNotify g.PlayerPropNotify(player, prop) diff --git a/gs/game/world_manager.go b/gs/game/world_manager.go index ab05eb1f..f713ba9a 100644 --- a/gs/game/world_manager.go +++ b/gs/game/world_manager.go @@ -1,10 +1,11 @@ package game import ( - "hk4e/protocol/cmd" "math" "time" + "hk4e/protocol/cmd" + "hk4e/common/mq" "hk4e/gs/constant" "hk4e/gs/game/aoi" @@ -64,7 +65,7 @@ func (w *WorldManager) CreateWorld(owner *model.Player) *World { playerFirstEnterMap: make(map[uint32]int64), waitEnterPlayerMap: make(map[uint32]int64), multiplayerTeam: CreateMultiplayerTeam(), - peerMap: make(map[uint32]*model.Player), + peerList: make([]*model.Player, 0), } if world.IsBigWorld() { world.aoiManager = aoi.NewAoiManager( @@ -132,7 +133,7 @@ type World struct { playerFirstEnterMap map[uint32]int64 // 玩家第一次进入世界的时间 key:uid value:进入时间 waitEnterPlayerMap map[uint32]int64 // 等待进入世界的列表 key:uid value:开始时间 multiplayerTeam *MultiplayerTeam - peerMap map[uint32]*model.Player // key:玩家编号 value:player对象 + peerList []*model.Player // 玩家编号列表 } func (w *World) GetNextWorldEntityId(entityType uint16) uint32 { @@ -157,17 +158,23 @@ func (w *World) GetNextWorldEntityId(entityType uint16) uint32 { // GetPlayerPeerId 获取当前玩家世界内编号 func (w *World) GetPlayerPeerId(player *model.Player) uint32 { - for peerId, worldPlayer := range w.peerMap { + peerId := uint32(0) + for peerIdIndex, worldPlayer := range w.peerList { if worldPlayer.PlayerID == player.PlayerID { - return peerId + peerId = uint32(peerIdIndex) + 1 } } - return 0 + logger.Debug("get player peer id is: %v, uid: %v", peerId, player.PlayerID) + return peerId } -// GetNextPeerId 获取下一个世界内玩家编号 -func (w *World) GetNextPeerId() uint32 { - return uint32(len(w.playerMap) + 1) +// GetPlayerByPeerId 通过世界内编号获取玩家 +func (w *World) GetPlayerByPeerId(peerId uint32) *model.Player { + peerIdIndex := int(peerId) - 1 + if peerIdIndex >= len(w.peerList) { + return nil + } + return w.peerList[peerIdIndex] } // GetWorldPlayerNum 获取世界中玩家的数量 @@ -176,7 +183,7 @@ func (w *World) GetWorldPlayerNum() int { } func (w *World) AddPlayer(player *model.Player, sceneId uint32) { - w.peerMap[w.GetNextPeerId()] = player + w.peerList = append(w.peerList, player) w.playerMap[player.PlayerID] = player // 将玩家自身当前的队伍角色信息复制到世界的玩家本地队伍 team := player.TeamConfig.GetActiveTeam() @@ -203,7 +210,8 @@ func (w *World) AddPlayer(player *model.Player, sceneId uint32) { } func (w *World) RemovePlayer(player *model.Player) { - delete(w.peerMap, w.GetPlayerPeerId(player)) + peerId := w.GetPlayerPeerId(player) + w.peerList = append(w.peerList[:peerId-1], w.peerList[peerId:]...) scene := w.sceneMap[player.SceneId] scene.RemovePlayer(player) delete(w.playerMap, player.PlayerID) @@ -416,7 +424,7 @@ func (w *World) SetPlayerLocalTeam(player *model.Player, avatarIdList []uint32) } func (w *World) copyLocalTeamToWorld(start int, end int, peerId uint32) { - player := w.peerMap[peerId] + player := w.GetPlayerByPeerId(peerId) localTeam := w.GetPlayerLocalTeam(player) localTeamIndex := 0 for index := start; index <= end; index++ { @@ -438,6 +446,10 @@ func (w *World) copyLocalTeamToWorld(start int, end int, peerId uint32) { // UpdateMultiplayerTeam 整合所有玩家的本地队伍计算出世界队伍 func (w *World) UpdateMultiplayerTeam() { + _, exist := w.playerMap[w.owner.PlayerID] + if !exist { + return + } w.multiplayerTeam.worldTeam = make([]*WorldAvatar, 4) switch w.GetWorldPlayerNum() { case 1: