HomeServer/Server/AllServer/RouteServer/RouteServer.lua

419 lines
17 KiB
Lua
Raw Normal View History

2024-11-20 15:41:09 +08:00
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