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
|