local skynet = require "skynet" local oo = require "Class" local gameCmd = require "GameCmd" local json =require "json" local log = require "Log" local sqlUrl = require "SqlUrl" local playerFields = require "PlayerFields" local errorInfo = require "ErrorInfo" local serverId = tonumber(skynet.getenv "serverId") local clusterServer = require "ClusterServer" local redisKeyUrl = require "RedisKeyUrl" local dataType = require "DataType" local serverManage = require "ServerManage" local RouteServer = oo.class(clusterServer) RouteServer.TokenMaxOutTime = 259200 --暂时设为3天过期,如果玩家下线后会过期 --玩家登陆tok--2700 --token最大失效时间(不能乱改,只能2700) --初始化 function RouteServer:Init() self.accountList = {} self.lastQueryGameServerInfoTime = skynet.GetTime() --上一次查询大厅服时间 self.accountWhiteList = {} --白名单列表 self.accountBlackList = {} --黑名单列表 self.listRollLocalGS = {} --当前硬件轮询游戏服列表 self.listRollRemoteGS = {} --远程硬件轮询游戏服列表 self.curLocalRollIndex = 1 --当前硬件轮询索引 self.curRemoteRollIndex = 1 --远程硬件轮询索引 self:SyncWhiteList() self:SyncBlackList() end --同步账号白名单列表 function RouteServer:SyncWhiteList() self.accountWhiteList = {} local redisWhiteKey = redisKeyUrl.RouteWhiteList local listAccount = skynet.server.redis:smembers( redisWhiteKey ) for k, account in pairs( listAccount ) do self.accountWhiteList[ account ] = true end log.info("同步账号白名单列表 数量" , #listAccount) end --同步账号黑名单列表 function RouteServer:SyncBlackList() self.accountBlackList = {} local redisBlackKey = redisKeyUrl.RouteBlackList local listAccount = skynet.server.redis:smembers( redisBlackKey ) for k, account in pairs( listAccount ) do self.accountBlackList[ account ] = true end log.info("同步账号黑名单列表 数量" , #listAccount) end --跨天 function RouteServer:OnNewDay() end --1秒Timer function RouteServer:On1SecTimer() --如果本硬件上存在需要重新的游戏服,那么就帮它重启下 --[[if self.Status_Restart == v.status and self:IsLocalGameServer( k ) then os.execute(string.format("./restart.sh %d", k )) log.info(string.format("已经关闭相关进程,开始重启服务器 %d",k )) end]] end --5秒Timer function RouteServer:On5SecTimer() function Do() --每5秒新增令牌桶的数量 if self.curServerConfig.TokenBucketCurCount then self.curServerConfig.TokenBucketCurCount = self.curServerConfig.TokenBucketCurCount + self.curServerConfig.TokenBucketAddCount if self.curServerConfig.TokenBucketCurCount > self.curServerConfig.TokenBucketCurCount then self.curServerConfig.TokenBucketCurCount = self.curServerConfig.TokenBucketMaxCount end end return true end local isDo,callData = pcall( Do ) if not isDo then log.error("内部错误 RouteServer:On5SecTimer",callData) end end --执行重启计划(暂时不用) function RouteServer:OpRestartPlan() local planInfo = skynet.server.redis:hget( redisKeyUrl.RestartPlanHSet , serverId) if planInfo then planInfo = json:decode( planInfo ) local nowTime = skynet.GetTime() local startServerId = planInfo.startServerId --游戏服起始ID local endServerId = planInfo.endServerId --游戏服结束ID local opTime = planInfo.opTime --操作时间 if nowTime >= opTime then skynet.server.redis:hdel( redisKeyUrl.RestartPlanHSet , serverId ) local hardId = ( serverId % 10 ) + 1 --硬件ID local tmpStartServerId = hardId * 100 --起始ID local startGameServerId = tmpStartServerId + startServerId local endGameServerId = tmpStartServerId + endServerId for i = startGameServerId, endGameServerId , 1 do local isSuc = false for k, v in pairs(self.clusterInfo) do if v.serverId == i and self:IsGameServer( k ) then --and v.status == self.Status_Stop local serverInfo = v.serverInfo local playerCount = serverInfo.playerCount.playing + serverInfo.playerCount.offline if 0 == playerCount then isSuc = true os.execute(string.format("./backRestart.sh %d", i )) log.info(string.format("重启游戏服成功 %d", i )) else log.info(string.format("重启游戏服失败 %d 当前游戏服人数 %d", i , playerCount )) end end end if not isSuc then log.info(string.format("重启游戏服失败 %d", i )) end end log.info(string.format("成功执行重启计划 游戏服ID 起始 %d 结束 %d 开始时间 %s", startGameServerId , endGameServerId, skynet.server.common:GetStrTime(opTime))) end end end --接收集群数据 function RouteServer:ClusterRecv(...) local cmd , c2sData = ... local s2sData = {} s2sData.code = errorInfo.Suc if self.Center2All_ServerManageCmd == cmd then skynet.server.serverManage:DoManageCmd( c2sData , s2sData ) elseif self.Center2All_SyncClusterInfo == cmd then self:RecvSyncClusterInfo( c2sData ) self:CalcRollGS() elseif self.Center2All_SyncServerConfig == cmd then --同步服务器配置 self:RecvSyncServerConfig( c2sData ) else log.info(string.format("集群服务器 消息接口 %d 不存在", cmd)) s2sData.code = errorInfo.ErrorCode.NoExistInterface end log.info(string.format("集群服 执行命令 %d 返回消息状态 %d " , cmd , s2sData.code)) return s2sData end --接收HTTP数据 function RouteServer:HttpRecv( c2sData , url , addr ) local s2cData = {} s2cData.code = errorInfo.Suc c2sData.addr = addr --禁止进入 if not self.curServerConfig.IsCanUserEnter then s2cData.code = errorInfo.ErrorCode.ForbidUserLogin return s2cData end --令牌桶数量不足 if 0 == self.curServerConfig.TokenBucketCurCount then s2cData.code = errorInfo.ErrorCode.TokenCountNotEnough return s2cData end if "GetGateUrl" == url then self:GetGateUrl( c2sData , s2cData ) elseif "health" == url then self:Health( c2sData , s2cData ) else log.info(string.format("Http服务器 消息接口 %s 不存在", url)) s2cData.code = errorInfo.ErrorCode.NoExistInterface end log.info("Http服务器 消息接口 ",url ,"返回信息",s2cData.code) return s2cData end --获取网关地址 function RouteServer:GetGateUrl( c2sData , s2cData ) --验证登陆接口参数 if nil == c2sData or nil == c2sData.platform or nil == c2sData.loginAccount then s2cData.code = errorInfo.ErrorCode.ErrRequestParam return s2cData end if not c2sData.netType or dataType.NetType_WebSocket ~= c2sData.netType then c2sData.netType = dataType.NetType_WebSocket end --非白名单玩家禁止登陆(只有外网测试服才开启白名单功能) if skynet.server.gameConfig:IsWhiteList() and not self.accountWhiteList[ c2sData.loginAccount ] then s2cData.code = errorInfo.ErrorCode.NoWhiteList return s2cData end --黑名单玩家禁止登陆 if self.accountBlackList[ c2sData.loginAccount ] then s2cData.code = errorInfo.ErrorCode.AccountBlackList return s2cData end --3秒内只能请求一次登录 local t1 = skynet.GetTime() if self.accountList[ c2sData.loginAccount ] and t1 - self.accountList[ c2sData.loginAccount ] <= 3 then s2cData.code = errorInfo.ErrorCode.RequestMsgTooFast return s2cData end --记录下成功获取的时间 self.accountList[ c2sData.loginAccount ] = skynet.GetTime() local gameServerId = nil local loginToken = skynet.server.common:RandToken() --获取登录token local redisKey = string.format( redisKeyUrl.RouteServerLoginInfoHash , c2sData.loginAccount ) local isExistLogin = skynet.server.redis:exists( redisKey ) if nil == isExistLogin then s2cData.code = errorInfo.ErrorCode.NoExistServer local errorText = string.format("路由服 %d 找不到玩家 %s 数据" , serverId or 0 , c2sData.loginAccount or "" ) skynet.server.clusterServer:SendErrorInfoToCenter( c2sData.loginAccount , s2cData.code , errorText) return s2cData end if isExistLogin then --存在玩家的登录信息 gameServerId = tonumber(skynet.server.redis:hget( redisKey , "GameServerID")) if not gameServerId or not self.clusterInfo[ gameServerId ] then s2cData.code = errorInfo.ErrorCode.GetGateUrlFailed skynet.server.redis:del(redisKey) log.info(string.format("平台 %s 玩家 %s 未找到该游戏服信息1", c2sData.platform , c2sData.loginAccount )) return end skynet.server.redis:hset( redisKey , "LoginToken" , loginToken ) else --[[ local function GetMinCountGS( isLocal ) --找个人数最少的游戏服 local minOnlineCount = -1 local curCount = 0 for k, v in pairs(self.clusterInfo) do local isAllow = true --是否允许 if isLocal and not self:IsLocalGameServer( k ) then isAllow = false end if isAllow and self:IsGameServer( k ) and v.status == self.Status_Running then --分配玩家到运行状态的游戏服 curCount = v.serverInfo.playerCount.offline + v.serverInfo.playerCount.playing local pingInterval = v.pingInterval or 5 if ( -1 == minOnlineCount or curCount < minOnlineCount ) and pingInterval <= 6 and curCount <= 2000 then --self.curServerConfig.MaxPlayerCountPerGS gameServerId = v.serverId minOnlineCount = curCount end end end end ]] --获取轮询游戏服 local function GetRollGS( isLocal ) local curListGS = {} --当前游戏服列表 local curRollIndex = 0 --当前轮服索引 if isLocal then --当前硬件 curListGS = self.listRollLocalGS curRollIndex = self.curLocalRollIndex else --非当前硬件 curListGS = self.listRollRemoteGS curRollIndex = self.curRemoteRollIndex end local curGS = curListGS[ curRollIndex ] if curGS then local maxRoll = #curListGS --最大轮询索引 if isLocal then self.curLocalRollIndex = self.curLocalRollIndex + 1 if self.curLocalRollIndex > maxRoll then --轮询索引大于最大轮询索引,重新从1开始 self.curLocalRollIndex = 1 end else self.curRemoteRollIndex = self.curRemoteRollIndex + 1 if self.curRemoteRollIndex > maxRoll then --轮询索引大于最大轮询索引,重新从1开始 self.curRemoteRollIndex = 1 end end return curGS.serverId end return nil end --优先取本地游戏服 gameServerId = GetRollGS( true ) if not gameServerId or not self.clusterInfo[ gameServerId ] then --没有本地游戏服,再取其它硬件游戏服 gameServerId = GetRollGS( false ) if not gameServerId or not self.clusterInfo[ gameServerId ] then log.info(string.format("平台 %s 玩家 %s 未找到该游戏服信息2 ", c2sData.platform , c2sData.loginAccount )) s2cData.code = errorInfo.ErrorCode.GetGateUrlFailed return end end skynet.server.redis:hmset( redisKey , "GameServerID" , gameServerId , "Status" , dataType.LoginStatus_GetToken , "LoginToken" , loginToken , "RecordTime" , skynet.GetTime()) end skynet.server.redis:expire( redisKey , self.TokenMaxOutTime ) --设置45分钟过期时间 --获取相关服务器配置 local cfgServer = nil for k, v in pairs( skynet.server.gameConfig.ClusterServerConfig ) do if gameServerId == v.serverId then cfgServer = v break end end if not cfgServer then log.info("未加找相关服的配置" , gameServerId) s2cData.code = errorInfo.ErrorCode.GetGateUrlFailed return end --s2cData.loginIP = cfgServer.externalIp if dataType.NetType_Tcp == c2sData.netType then --s2cData.loginPort = cfgServer.tcpPort elseif dataType.NetType_WebSocket == c2sData.netType then s2cData.loginPort = cfgServer.webSocketPort s2cData.loginUrl = cfgServer.webSocketUrl end s2cData.loginToken = loginToken --令牌数量减1 self.curServerConfig.TokenBucketCurCount = self.curServerConfig.TokenBucketCurCount - 1 --log.info(string.format("玩家 %s 获得游戏服登陆许可 游戏服 %d 登录Token %s",c2sData.loginAccount , gameServerId , loginToken)) return end --健康管理 function RouteServer:Health( c2sData , s2cData ) s2cData.code = 200 end --发送登陆TOKEN到登陆服务器 function RouteServer:SendTokenToGameServer( serverId , platform , loginAccount , loginToken) local clusterMsg = {} clusterMsg.platform = platform clusterMsg.loginAccount = loginAccount clusterMsg.loginToken = loginToken self:SendMsgToServer(serverId , self.Route2Game_UserLoginToken , clusterMsg) end --是否为为同一台硬件下的服务器 function RouteServer:IsLocalGameServer( gameServerId ) if serverId ~= gameServerId and serverId == (10 + math.floor(tonumber(gameServerId) / 100) - 1) then return true else return false end end --计算游戏硬件上的游戏服轮询顺序 function RouteServer:CalcRollGS() self.listRollLocalGS = {} --当前硬件轮询游戏服列表 self.listRollRemoteGS = {} --远程硬件轮询游戏服列表 self.curLocalRollIndex = 1 --当前硬件轮询索引 self.curRemoteRollIndex = 1 --远程硬件轮询索引 for k, v in pairs(self.clusterInfo) do if self:IsGameServer( k ) then local pingInterval = v.pingInterval or 10 --ping值 , 一般没有ping值可能是该游戏服还未向中心服发送心跳包,或者这台服被卡了,所以就不考虑该把新玩家分配到该游戏服。 local playerCount = 0 --当前在线人数 if v.serverInfo and v.serverInfo.playerCount and v.serverInfo.playerCount.offline and v.serverInfo.playerCount.playing then playerCount = v.serverInfo.playerCount.offline + v.serverInfo.playerCount.playing end --条件为动作状态,并且为游戏服 ,延时小于6秒,人数小于配置中的人数 if v.status == self.Status_Running and pingInterval <= 6 and playerCount <= 1500 then if self:IsLocalGameServer( k ) then --当前硬件 table.insert( self.listRollLocalGS , { serverId = k , playerCount = playerCount } ) else --远程硬件 table.insert( self.listRollRemoteGS , { serverId = k , playerCount = playerCount } ) end end end end --当前硬件游戏服人数按升序排列,有两个以上的游戏服才排序 if #self.listRollLocalGS > 1 then table.sort( self.listRollLocalGS , function ( a , b) return a.playerCount < b.playerCount end) end --远程硬件游戏服人数按升序排列,有两个以上的游戏服才排序 if #self.listRollRemoteGS > 1 then table.sort( self.listRollRemoteGS , function ( a , b) return a.playerCount < b.playerCount end) end end skynet.server.routeServer = RouteServer return RouteServer