419 lines
17 KiB
Lua
419 lines
17 KiB
Lua
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 |