local NetManager = {} local private = {} -- 协议超时时间 NetManager.NetMessageTimeOutMax = 10 local EventSystem = require('Event/EventSystem') --动态加载,此处只有一个地方在用 local NetEnum = nil local NetStatistics = require "Main/NetStatistics" -- 协议注册callback local messageRespCB = {} local curAckPassthroughIDs = {} local RECONNECT_WAITING_KEY = "RECONNECT_WAITING" local xpcall = xpcall -- 是否在线 local isOnline = false --切后台的时候标记为true,OnNetConnectedTick的第一次恢复 --用来判断是不是后台断线 local isFirstTickAfterBackground = false NetManager.DisconnectReason = { None = 0, RebootGame = 1, QuitGame = 2, TestGame = 3, PingPongTimeOut = 4, AutoReconnect = 5, KickByServer = 6, ManualReConnect = 7, MsgTimeOut = 1001, ServerError = 1002, KcpPingTimeout = 2001, BackgroundDisconnect = 3001, } NetManager.NetStateCode = { Normal = 0,--联通状态 Disconnect = 1,--断开状态 Reconnect = 2, --重连状态 } --resp的遮罩不关闭,得等到对应的push到了才关闭遮罩 local MsgIdsCloseMaskByPush = { --value可能是值,一对一对应,当value对应的Push到来关闭遮罩 [MessageEnums.MessageConvey] = MessageEnums.MessageS2CRoleEnterScene, [MessageEnums.MessageEnterFuben] = MessageEnums.MessageS2CRoleEnterScene, --value可能是table,一对多,这种情况收到其中一个协议就认为可关闭遮罩 [MessageEnums.MessageRobRebel] = { MessageEnums.MessageG2CInitFubenInfo, MessageEnums.MessageG2CPushHint }, [MessageEnums.MessageEnterGuildBoss] = { MessageEnums.MessageG2CInitFubenInfo, MessageEnums.MessageS2CRoleEnterScene }, [MessageEnums.MessageEnterWorldBoss] = { MessageEnums.MessageG2CInitFubenInfo,MessageEnums.MessageG2CEnterWorldBoss}, } --对于Req和Resp的协议大部分是需要打开一个遮罩,以阻挡玩家快速的点击,还有一些不需要遮罩的协议 local MsgIdsWithoutMask = { [MessageEnums.MessageGetMailBoxInfo] = true, [MessageEnums.MessageHorseAty] = true, [MessageEnums.MessageGuildSetupCheck] = true, [MessageEnums.MessageGetActivityPanel] = true, [MessageEnums.MessageReceiveExpRain] = true, [MessageEnums.MessageAnswerQuestion] = true, [MessageEnums.MessageGetAnswerRank] = true, [MessageEnums.MessageHWActivityGetInfo] = true, [MessageEnums.MessageGetAuctionHouse] = true, [MessageEnums.MessageGetCommandWorldRankAndDevote] = true, [MessageEnums.MessageFreeLineUpGetInfo] = true, [MessageEnums.MessageGameVoiceChatMsg] = true, [MessageEnums.MessageRivalSetting] = true, [MessageEnums.MessageGameChatMsg] = true, [MessageEnums.MessageSceneQuizGetAnswerRank] = true, } local MsgIdDontEnterSendQuene = { [MessageEnums.MessageOnConnection] = true, [MessageEnums.MessageEnterWorld] = true, [MessageEnums.MessageTaskStateUpdate] = true, -- 断线重连已经收到了任务的全量信息,不再补发任务完成请求 [MessageEnums.MessageGetQuestion] = true, [MessageEnums.MessageGetActivityPanel] = true, } local function OnMsgRespError(msg) logging.error(LTAG.NET, "net message response error:", msg) end --添加到发送队列 local function InsertSendQuene(id, msg) if MsgIdDontEnterSendQuene[id] then return end table.insert(curAckPassthroughIDs, id) table.insert(curAckPassthroughIDs, msg) end --发送队列移除 local function RemoveFromSendQuene(id, msg) if MsgIdDontEnterSendQuene[id] then return end if msg.Resp then for i = 2, #curAckPassthroughIDs, 2 do if curAckPassthroughIDs[i].Req.PassthroughID == msg.Resp.PassthroughID then --remove msg and id table.remove(curAckPassthroughIDs, i) table.remove(curAckPassthroughIDs, i-1) break end end end end local function OpenWaitingView(id, msg) if MsgIdsWithoutMask[id] then return end local pushMsgId = MsgIdsCloseMaskByPush[id] if pushMsgId then -- 要等到协议Push到来才关闭 UTL.MessageBox.ShowWaitingWithDelay(pushMsgId) else UTL.MessageBox.ShowWaitingWithDelay(id) end end local function CloseWaitingView(id, msg) if msg.Resp then local pushMsgId = MsgIdsCloseMaskByPush[id] if pushMsgId then -- 需要有对应push才能关闭mask -- 有异常,不用等待push,可关闭遮罩 if msg.Resp.Code ~= 0 then UTL.MessageBox.CloseWaitingWithDelay(pushMsgId) end else UTL.MessageBox.CloseWaitingWithDelay(id) end else -- push UTL.MessageBox.CloseWaitingWithDelay(id) end end function NetManager.DealingWithMsgCode(msgID, msgOk, paramInfo) local msgData = DataSheetManager.ErrorCode:GetDataByID(msgID) if UTL.ErrorCode.IsError(msgID) then logging.error(LTAG.NET, "message code error ", msgID, UIUtils.GetLocalizedTxt(Const_IDS[msgData.IDS])) elseif UTL.ErrorCode.IsWarn(msgID) then logging.warn(LTAG.NET, "message code warn ", msgID, UIUtils.GetLocalizedTxt(Const_IDS[msgData.IDS])) end if msgData.type == 3 then -- IDS飘字提示 local msgStr = "" if not string.isNilOrEmpty(msgOk) then local params = string.split(msgOk, ",") for i, v in ipairs(params) do ---这里做是为了军团身份人上线的提示 有可能要将多个(不固定)ids放在一个%s里 所以规定了一个默认的规则 将/分割的字符串放在一个里 local idss = string.split(v, "/") if #idss > 1 then local str = nil for _, ids in ipairs(idss) do if str == nil then str = UIUtils.GetLocalizedTxt(Const_IDS[ids]) else str = str .. "/" .. UIUtils.GetLocalizedTxt(Const_IDS[ids]) end end params[i] = str end end msgStr = UTL.String.GetMessageFormatStr(msgData.IDS, params) else local params = {} if paramInfo then for _, v in ipairs(paramInfo) do table.insert(params, UTL.String.GetShowStrByParamInfo(v)) end end msgStr = UTL.String.GetMessageFormatStr(msgData.IDS, params) end CTL.Tips.AddTipsMsg(msgStr) elseif msgData.type == 2 then -- 弹框警告 if UNITY_EDITOR or TAI_DEVELOPMENT_BUILD then UTL.MessageBox.ShowOnlyOkBox(Const_IDS[msgData.IDS], msgOk, nil, nil) else UTL.MessageBox.ShowOnlyOkBox(Const_IDS.IDS_WARNING, Const_IDS[msgData.IDS], nil, nil) end elseif msgData.type == 1 then -- 踢下线行为 if UNITY_EDITOR or TAI_DEVELOPMENT_BUILD then UTL.MessageBox.ShowOnlyOkBox(Const_IDS[msgData.IDS], msgOk, function() LuaNetManager.RebootGame() end, nil) else UTL.MessageBox.ShowOnlyOkBox(Const_IDS.IDS_WARNING, Const_IDS[msgData.IDS], function() LuaNetManager.RebootGame() end, nil) end end end local function GetPushError(msg) NetManager.DealingWithMsgCode(msg.Code, msg.MsgOK, msg.msgParams) end local function OnMsgPreprocess(id, msgResp) if msgResp.Resp then -- 检测安装包过期 CTL.Login.CheckPackageTimeExpire(msgResp.Resp.clientExpireTime) -- 检测热更版本 MDL.HotFix.CheckIfHotFix(msgResp) --更新下本地存储的服务器时间 UTL.Time.UpdateServerTimestamp(msgResp.Resp.serverTime) local msgCode = msgResp.Resp.Code if msgCode ~= 0 then NetManager.DealingWithMsgCode(msgCode, msgResp.Resp.MsgOK, msgResp.Resp.msgParams) else MDL.Player.CommonProcessResponse(msgResp, id) end end end local function UnRegisterCsFunc(msg) if msg and msg.Resp and msg.Resp.PassthroughID and string.len(msg.Resp.PassthroughID) > 0 then LuaNetManager.LuaUnregister(msg.Resp.PassthroughID, msg.Resp.Code or 0) end end function NetManager.Init() isFirstTickAfterBackground = false NetStatistics.Init() LuaNetManager.SetPingInterval(10, 15) NetManager.RegisterMsgCB(MessageEnums.MessageG2CPushHint, GetPushError) end function NetManager.SendMessage(id, msg) local passthroughID = "" if msg.Req then msg.Req.TIDs = '' msg.Req.PassthroughID = LuaNetManager.GetPassthroughID() passthroughID = msg.Req.PassthroughID InsertSendQuene(id, msg) OpenWaitingView(id, msg) if NetManager.IsOnline() then -- 网络正常的时候才计入统计 NetStatistics.OnMsgSend(id) end end local data = msg:SerializeToString() LuaNetManager.LuaRequest(data, id, passthroughID) logging.log(LTAG.PROTO, "SendMsg msgId", id, "msgBytes", #data) end function NetManager.OnMessageResp(id, data, receiveTime) logging.log(LTAG.PROTO, "HandleMsg msgId", id, "msgBytes", #data, "recvTime", receiveTime) if NetEnum.MsgIdToProto[id] then BattleSimulator.StoreMessage(id, data) local msgResp = NetEnum.MsgIdToProto[id]() msgResp:ParseFromString(data) RemoveFromSendQuene(id, msgResp) CloseWaitingView(id, msgResp) NetStatistics.OnMsgRecv(id, receiveTime, msgResp.Resp and msgResp.Resp.Code~=0 or false) if id == MessageEnums.MessageEnterWorld then PersistentData.SetPlayerAccid(msgResp.DetailInfo.acid) end OnMsgPreprocess(id, msgResp) UnRegisterCsFunc(msgResp) if messageRespCB[id] then if NetManager.IsLaunch() then if id == MessageEnums.MessageEnterWorld or id == MessageEnums.MessageOnConnection or id == MessageEnums.MessageKick or id == MessageEnums.MessageG2CPushCreateProfile then xpcall(messageRespCB[id], OnMsgRespError, msgResp) else logging.warn(LTAG.WARN, "在收到EnterWorld协议之前,收到了不应该收到的协议", id) end else xpcall(messageRespCB[id], OnMsgRespError, msgResp) end else logging.error(LTAG.NET, id, "此协议没有注册回调") end else logging.error(LTAG.NET, "id", id, "没有注册协议结构") end end local function DefaultMsgCB() end function NetManager.RegisterMsgCB(id, cb) messageRespCB[id] = cb or DefaultMsgCB end ------------断线重连相关逻辑--------------- local _lastAutoReConnectTime = 0 local RECONNECT_INTERVAL = 3 -- 每三秒自动重连一次 local ReconnectState = { Normal = 1, AutoReconnecting = 2, ManulReconnecting = 3 } local _reconnectState = ReconnectState.Normal local curLaunch = true -- 当前是否是在登录界面 local connectFun = nil -- 连接回调 local inTownTime = 0 local inTownMaxTime = 180 -- 玩家在掉线时 在主城最多停留时间 --关掉所有的连接 --gameTCP+Chat+KCP function private.DisconnectAll(disconnectReasion) LuaNetManager.Disconnect(disconnectReasion, true) TCPReconnectManager.OnDisconnected() end local function ResetReConnectState(isFirstConnectSucc) if not isFirstConnectSucc and NetManager.IsOnline() then _lastAutoReConnectTime = 0 if _reconnectState ~= ReconnectState.Normal then logging.crit(LTAG.CRIT, "游戏中,重连成功") UTL.MessageBox.ForceCloseWaiting() CTL.BattleAgent.OnTCPReconnect() if not BattleDataGetter.IsSyncLevel() then MDL.Login.isReConnect = true MDL.Login.SendEnterWorld(BattleDataGetter.IsReconnectLevel(), 0) else -- 在战斗场景中 TCPReconnectManager.OnReconnected() end if _reconnectState == ReconnectState.ManulReconnecting then CTL.Tips.AddTipsMsg(Const_IDS["IDS_RECONTACT_SUC_TIPS"]) end end _reconnectState = ReconnectState.Normal inTownTime = 0 end end -- 1、当前不在线;2、不是在登录阶段;3、不是手动重连状态 local function CheckIsOpenAutoReConnect(isFirstConnectSucc) ResetReConnectState(isFirstConnectSucc) return not NetManager.IsOnline() and not curLaunch and _reconnectState ~= ReconnectState.ManulReconnecting end -- 点击重连按钮 local function DoReConnect() UTL.MessageBox.ShowWaitingWithDelay(RECONNECT_WAITING_KEY, { reconnect = true }) LuaNetManager.ConnectWithToken() end -- 点击登录按钮 local function BtnBackLaunch() NetManager.SetIsLaunch(true) LuaNetManager.RebootGame() end -- 手动重连 local function ManualReConnect() logging.log(LTAG.NET, "ManualReConnect ") local errorStr = UIUtils.GetLocalizedTxt(Const_IDS.IDS_ERROR_NETWORK_90000) _reconnectState = ReconnectState.ManulReconnecting if NetManager.IsOnline() then isOnline = false logging.warn(LTAG.WARN , "[gws] :发生了网络是连接时 ,又要重连的情况。 不阻挡游戏!") end BIManager.DispatchBIClientEvent(BIManager.EventName.NetMessageDisConnectBoxMsg, "levelID", BattleDataGetter.GetCurLevelID()) UTL.MessageBox.ShowOKCancelNoCloseBox(nil, errorStr, DoReConnect, BtnBackLaunch, Const_IDS.IDS_RECONTACT, Const_IDS.IDS_BACKLOADINGUI, UTL.MessageBox.DisplayType.ReconnectTip) EventSystem.Dispatch(EventID.SDKUrlMonitor_ReqUrlFail, LuaConst.SDKMonitorTriggerType.GameConnect) end -- 手动返回登录 local function ManualBackLaunch() local errorStr = UIUtils.GetLocalizedTxt(Const_IDS.IDS_ERROR_NETWORK_90001) _reconnectState = ReconnectState.ManulReconnecting UTL.MessageBox.ShowOnlyOkNoCloseBox("", errorStr, BtnBackLaunch, Const_IDS.IDS_BACKLOADINGUI) end -- 自动重连(每隔三秒一次) function NetManager.AutoReconnect(isForce) _lastAutoReConnectTime = _lastAutoReConnectTime + Time.deltaTime if isForce or _lastAutoReConnectTime > RECONNECT_INTERVAL then _lastAutoReConnectTime = 0 _reconnectState = ReconnectState.AutoReconnecting -- C#端已经存在日志 private.DisconnectAll(NetManager.DisconnectReason.AutoReconnect) LuaNetManager.ConnectWithToken() EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Reconnect, NetManager.NetStateCode.Normal, NetManager.DisconnectReason.None) end end function NetManager.OnNetConnectionSuccess() NetStatistics.OnReconnectSuccess() NetManager.OnNetConnectedTick(true, true) end function NetManager.OnNetConnectedTick(online, isFirstConnectSucc) local lastOnline = isOnline isOnline = online if CheckIsOpenAutoReConnect(isFirstConnectSucc) then if BattleDataGetter.IsNormalLevel() then -- 重连 NetManager.AutoReconnect(false) elseif BattleDataGetter.IsNoBattleLevel() then if CTL.TeamMatch.IsInMatching() then CTL.TeamMatch.CancelAllMatch() CTL.Tips.AddTipsMsg(Const_IDS.IDS_TeamMatchCancel_Offline_Tips) end if UIManager.IsViewOpened(Const_UI_Lua.UIEnum.UISyncTeamPVPSelectHeroPanel) then -- 策划要求3v3选将界面时重连特殊处理 logging.crit(LTAG.CRIT, "在3V3选将界面断线,执行返回登录操作") NetManager.ManualBackLaunch() else -- 主城,自动重连,超过inTownTime则返回登录 NetManager.AutoReconnect(false) inTownTime = inTownTime + Time.deltaTime if inTownTime >= inTownMaxTime then logging.crit(LTAG.CRIT, "主城中长时间连不上网络,执行返回登录操作") BtnBackLaunch() end end elseif _reconnectState == ReconnectState.Normal and MDL.Loading.GetIsLoadingState() then if MDL.Loading.IsEnterWorldLoading() then --首次进入游戏时,没有收到进场景的协议 if BattleDataGetter.IsNotStartLoadingScene() then logging.crit(LTAG.CRIT, "首次进入游戏loading时,没有收到Connect,EnterGame等协议,执行返回登录操作") NetManager.ManualBackLaunch() end end --同步玩法中,在enterlevel阶段,需要等服务器协议推动,但是此时可能断线收不到协议 --如果当前在快速补帧阶段,按照卫松说的,需要服务器再同步过来一帧的数据,才认为是结束补帧,但此时收不到 if BattleDataGetter.IsSyncLevel() then if BattleDataGetter.IsAfterEnterLevelStep() then logging.crit(LTAG.CRIT, "首次进入游戏loading时,同步副本在EnterLevel和快速补帧断线,执行返回登录操作") private.DisconnectAndAutoReconnect() end end elseif BattleDataGetter.IsSyncLevel() and lastOnline == true then -- 战斗内断线了 ,需要只执行一次 --同步战斗中断线,先断掉所有的连接,再重连 logging.crit(LTAG.CRIT, "同步副本中,断线,执行重连逻辑") private.DisconnectAndAutoReconnect() end end if lastOnline == true and not isOnline then if isFirstTickAfterBackground then EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Disconnect, NetManager.NetStateCode.Normal, NetManager.DisconnectReason.BackgroundDisconnect) else EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Disconnect, NetManager.NetStateCode.Normal, NetManager.DisconnectReason.ServerError) end end isFirstTickAfterBackground = false end function private.DisconnectAndAutoReconnect() private.DisconnectAll(NetManager.DisconnectReason.AutoReconnect) NetManager.AutoReconnect(true) UTL.MessageBox.ShowWaitingWithDelay(RECONNECT_WAITING_KEY, {reconnect = true}) end --返回到登录界面 --1、城镇中在开启3v3选将界面 --2、在进入游戏时loading的时候 function NetManager.ManualBackLaunch() logging.log(LTAG.NET, "ManualBackLaunch") UTL.MessageBox.ForceCloseWaiting() private.DisconnectAll(NetManager.DisconnectReason.ManualReConnect) ManualBackLaunch() end function NetManager.ManualReConnect(connectCb) logging.log(LTAG.NET, "ManualReConnect") private.DisconnectAll(NetManager.DisconnectReason.ManualReConnect) UTL.MessageBox.ForceCloseWaiting() if curLaunch then NetManager.ManualBackLaunch() return end connectFun = connectCb ManualReConnect() end function NetManager.OnMsgTimeOutResp(id) if LuaNetManager.IsOnline() then logging.warn(LTAG.NET, "net is online but resp timeout, msgId", id) -- 给TD协议超时 NetStatistics.OnMsgRecv(id, 0, false) end -- 如果resp超时,继续等待WaitingMask结束自动重连 -- 这两种情况没有WaitingMask,手动重连 -- 有可能会误关push的Waiting,这里直接关闭简化处理 -- MsgIdsWithoutMask[id]收不到不重连,不打断客户端 if MsgIdsCloseMaskByPush[id] then EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Disconnect, NetManager.NetStateCode.Normal, NetManager.DisconnectReason.MsgTimeOut) NetManager.ManualReConnect() end end --心跳没有收到 function NetManager.OnPingPongTimeout() if NetManager.IsOnline() then logging.crit(LTAG.CRIT, "收到心跳超时协议,执行返回登录操作") UTL.MessageBox.ForceCloseWaiting() private.DisconnectAll(NetManager.DisconnectReason.PingPongTimeOut) EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Disconnect, NetManager.NetStateCode.Normal, NetManager.DisconnectReason.PingPongTimeOut) NetManager.AutoReconnect(true) UTL.MessageBox.ShowWaitingWithDelay(RECONNECT_WAITING_KEY, {reconnect = true}) end end --是否在登录场景状态,收到enterworld的时候置为true function NetManager.SetIsLaunch(value) curLaunch = value if not curLaunch then NetStatistics.StartUpdate() else NetStatistics.StopUpdate() end end function NetManager.IsLaunch() return curLaunch end function NetManager.IsOnline() return isOnline end function NetManager.OnReConnectOk() UTL.MessageBox.CloseWaitingWithDelay(RECONNECT_WAITING_KEY) for i = 1, #curAckPassthroughIDs, 2 do local id = curAckPassthroughIDs[i] local msg = curAckPassthroughIDs[i+1] LuaNetManager.LuaRequest(msg:SerializeToString(), id, msg.Req.PassthroughID) NetStatistics.OnMsgSend(id) end for k in pairs(curAckPassthroughIDs) do curAckPassthroughIDs[k] = nil end if connectFun then connectFun() connectFun = nil end end function NetManager.OnTcpDisconnectReason(disconnectCode) EventSystem.Dispatch(EventID.BI_ClientNetState, NetManager.NetStateCode.Disconnect, NetManager.NetStateCode.Normal, disconnectCode) end -- 网络是否通畅 function NetManager.IsValidNet(isShowTip) if UNITY_EDITOR then return true end if (UnityEngine.Application.internetReachability == UnityEngine.NetworkReachability.NotReachable) then if isShowTip then CTL.Tips.AddTipsMsg(Const_IDS["IDS_ERROR_NETWORK_AUTH_10005"]) end return false end return true end function NetManager.OnApplicationPause(pause) if pause then isFirstTickAfterBackground = true end end function NetManager.SetNetMsgPaused(paused) LuaNetManager.SetNetMsgPaused(paused) private.isNetMsgPaused = paused EventSystem.Dispatch(EventID.OnNetMsgPaused, paused) end function NetManager.IsNetMsgPaused() return private.isNetMsgPaused end function NetManager.InitPB() if not NetEnum then NetEnum = require("Common/NetEnum") end end function NetManager.GetPB(pbFileName) return NetEnum.ProtoPB[pbFileName] end --将协议定义的map结构转换为lua可用的map --注意:因结构差异,需经过for循环重新定义map,所以如果数据较大并频繁变化,与服务器沟通不使用map定义数据结构。 --该方法不支持嵌套使用Map结构的转化, 协议中也不要用嵌套map。 function NetManager.ChangeProtoMapToLua(protoMap, luaMap) local map if luaMap then table.clear(luaMap) map = luaMap else map = {} end for _, v in ipairs(protoMap) do map[v.key] = v.value end return map end --覆盖协议定义的map结构数据 --oldProtoMap是原数据,newProtoMap是新的数据 function NetManager.ChangeProtoMapData(oldProtoMap, newProtoMap) table.clear(oldProtoMap) for _, v in ipairs(newProtoMap) do table.insert(oldProtoMap,{key = v.key, value = v.value}) end end return NetManager