# 多版本客户端引擎支持

# 应用场景

​ 整包升级期间,网络服支持多个版本客户端同时进入,解决升级后低版本客户端无法进入问题。下面以同时支持1.23版本和1.24版本客户端为例,介绍多版本客户端引擎支持方案

# 解决方案

​ apollo中proxy、master和service是兼容不同版本的,只需解决lobby/game兼容问题,方法是将玩家分配到适配版本lobby/game中,也即1.23版本的玩家进入1.23版本的lobby/game中,1.24版本玩家进入1.24版本lobby/game中。下面详细介绍实现细节

# 网络服配置

​ 大厅服需要同时部署多个版本,这里同时部署1.23和1.24两个版本的lobby

image-20211015153007727

image-20211015153253908

​ 游戏服需要同时部署多个版本,并且每种类型的游戏服都要部署多个版本,这里同时部署1.23和1.24两个版本的game

image-20211015153406700

image-20211015170824022

控制服配置为多个版本中最高版本,这里配置为1.24版本

image-20211015153719169

功能服配置为多个版本中最高版本,这里配置为1.24版本

image-20211015153546589

代理服配置为多个版本中最高版本,这里配置为1.24版本

image-20211015145555301

# 代码支持

​ 将玩家分配到适配lobby/game中,需要考虑登陆和切服两种情况

# 登陆

​ apollo支持将玩家分配到适配的lobby,无需开发者做额外开发,但是若开发者设置玩家登陆策略(参见SetLoginStratege接口),则需要考虑多版本兼容问题,实现步骤是:

  • 获取可用服务器和协议版本号
  • 根据协议版本号将玩家分配到适配版本服务器中

下面示例实现自定义登陆逻辑,将登陆玩家分配到适配版本"gameA"类型游戏服中

#master mod
class testMaster(MasterSystem):
        def __init__(self,namespace,systemName):
				import master.netgameApi as netMasterApi
                MasterSystem.__init__(self, namespace, systemName)
                self.mVersion2TargetServerIds = {} #protocolVersion--> serverIds
                def loginStratege(uid, callback):
                    	# 获取玩家协议版本号
						protocolVersion = netMasterApi.GetProtocolVersionByUID(uid)
						targetIds = self.mVersion2TargetServerIds.get(protocolVersion, [])
                        targetId = random.choice(targetIds) #选择目标服务器
                        #检查目标服务器是否有效。因为滚动更新过程中,服务器会慢慢下线,处于无效状态
                        if not serverManager.IsValidServer(targetId):
                                #若发现有无效服务器,则过滤掉所有无效服务器,然后重新选择目标服务器
                                targetIds = [server for server in targetIds if serverManager.IsValidServer(server)]
								self.mVersion2TargetServerIds[protocolVersion] = targetIds
                                targetId = random.choice(targetIds)
                        callback(targetId) #必须执行,执行登陆后续操作
                netMasterApi.SetLoginStratege(loginStratege)
                self.ListenForEvent('gameANameaspace', 'gameASystem', 'NewLoginServerEvent', self, self.OnNewLoginServer)
        
        def OnNewLoginServer(self, args):
                # 将有效目标服务器记录下来
				protocolVersion = args['protocolVersion']
				serverId = args['serverId]
				serverIds = self.mVersion2TargetServerIds.get(protocolVersion, [])
                if serverId not in serverIds:
                        serverIds.append((serverId, ))
						self.mVersion2TargetServerIds[protocolVersion] = serverIds
#gameA mod
class gameServer(ServerSystem):
        def __init__(self, namespace, systemName):
                ServerSystem.__init__(self, namespace, systemName)
                self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), 'MasterConnectStatusEvent', 
                	self,self.OnMasterConnectStatus)
        def OnMasterConnectStatus(self, args):
                # 同master建立连接后,马上向master注册为有效服务器
                if args['isConnect']:
                        data = {
                            'serverId' : lobbyGameApi.GetServerId(), 
                             'protocolVersion' : lobbyGameApi.GetServerProtocolVersion()#获取服务器协议版本号
                        }
                        self.NotifyToMaster('NewLoginServerEvent', data)

# 切服

​ 切服需要将玩家分配到适配版本的服务器中,实现步骤:

  • lobby/game从master获取目标服务器,master需要通过协议号找到适配服务器列表,然后根据某种算法获取目标服务器
  • lobby/game根据目标服务器进行切服

下面示例实现多版本支持的切服逻辑,将登陆玩家分配到适配版本"gameA"类型游戏服中

#master mod
class testMaster(MasterSystem):
        def __init__(self,namespace,systemName):
				import master.netgameApi as netMasterApi
                MasterSystem.__init__(self, namespace, systemName)
                self.mVersion2TargetServerIds = {} #protocolVersion--> serverIds
				self.InitServerInfo()
				self.ListenForEvent(extraMasterApi.GetEngineNamespace(), extraMasterApi.GetEngineSystemName(), 
                	'NetGameCommonConfChangeEvent',self, self.OnNetGameCommonConfChange)
                self.ListenForEvent('gameNameaspace', 'gameSystem', 'ChooseTargeGameServerRequestEvent', 
                     self, self.ChooseTargeGameAServer)
		
		def OnNetGameCommonConfChange(self, args)
            '''
            每次配置发生变化,需要重新获取目标服务器
            '''
			self.InitServerInfo()
		
		def InitServerInfo(self):
			'''
			获取gameA服务器和它的协议版本号
			'''
			import master.serverManager as serverManager
			commonConf = netMasterApi.GetCommonConfig()
			self.mVersion2TargetServerIds = {}
			serverlist = commonConf['serverlist']
			for conf in serverlist:
				if conf['type'] == 'gameA':
					serverId = conf['serverid']
					protocolVersion = serverManager.GetServerProtocolVersion(serverId)
					ids = self.mVersion2TargetServerIds.get(protocolVersion, [])
					ids.append(serverId)
					self.mVersion2TargetServerIds[protocolVersion] = ids
      
        def ChooseTargeGameAServer(self, args):
            '''
            随机选择一个目标服务器
            '''
			serverId = args['protocolVersion']
			protocolVersion = args['protocolVersion']
			targetIds = self.mVersion2TargetServerIds.get(protocolVersion, [])
            targetId = random.choice(targetIds) #选择目标服务器
            #检查目标服务器是否有效。因为滚动更新过程中,服务器会慢慢下线,处于无效状态
            if not serverManager.IsValidServer(targetId):
                #若发现有无效服务器,则过滤掉所有无效服务器,然后重新选择目标服务器
                targetIds = [server for server in targetIds if serverManager.IsValidServer(server)]
				self.mVersion2TargetServerIds[protocolVersion] = targetIds
                targetId = random.choice(targetIds)
			data = {'uid' : uid, 'targetId' : targetId}
			self.NotifyToServerNode(serverId, "ChooseTargeGameResponseServerEvent", data)
						
#game mod
class gameServer(ServerSystem):
        def __init__(self, namespace, systemName):
                ServerSystem.__init__(self, namespace, systemName)
                self.ListenForEvent('masterNameaspace', 'masterSystem', 'ChooseTargeGameResponseServerEvent', 
                	self,self.ChooseTargeGameResponseServer)
					
        def DoTransferPlayer(self, uid):
            '''
            玩家切服,会从master请求获取适配服务器
            '''
			data = {
				'uid' : uid, 
				'protocolVersion' : lobbyGameApi.GetServerProtocolVersion(), 
				'serverId' : lobbyGameApi.GetServerId()
			}
            self.NotifyToMaster('ChooseTargeGameServerRequestEvent', data)
		
		def ChooseTargeGameResponseServer(self, args):
            '''
            从master获取适配服务器后执行切服操作
            '''
			playerId = lobbyGameApi.GetPlayerIdByUid(args['uid'])
			self.TransferToOtherServerById(playerId, args['uid'], args['targetId'])