zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

从王者荣耀看设计模式(远程代理模式)

代理模式设计模式 远程 荣耀 王者
2023-09-11 14:21:44 时间

从王者荣耀看设计模式(远程代理模式)

一.简介

每位王者荣耀玩家都有一个属于自己的游戏账号。为了提升游戏等级或者增加游戏体验感,会存在多个游戏玩家同时共享一个游戏账号的情况。当一位玩家使用账号正在游戏中时,另一玩家登陆同一账号会导致前一玩家强制退出登陆!(ಥ_ಥ)

模式动机
在本实例中,通过远程代理,我们可以实现远程控制。当我处于在线状态时,使用代理让同一账号的使用者下线

二.远程代理(帮助我们控制访问远程对象)

远程代理可以作为另一个JVM上对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果返回给客户。
代理,就是代表某一真实对象,假装它是真正的对象。但是其实一切的动作是代理对象利用网络和真正的对象沟通o(´^`)o 代理之所以需要控制访问,是因为我们的客户不知道如何和远程对象沟通。从某些方面来看,远程代理控制访问,可以帮助我们处理网络上的细节
变量只能引用和当前代码语句在同一堆空间中的对象,Java有内置远程调用的功能可以帮助我们实现远程代理

三.结构图

四.设计类图

五.代码实现

AccountStatus类(远程接口类)

package com.practice.MyRemote;

import java.rmi.Remote;
import java.rmi.RemoteException;
/*
 * 客户端和服务端统一的接口,只需要服务端根据这个接口实现相应的功能,然后注册上去,
 * 客户端就可以根据这个接口来使用相应的功能
 *
 * 定义一个远程接口,必须继承Remote接口,其中需要远程调用的方法必须抛出RemoteException异常
 */
public interface AccountStatus extends Remote {
	public void OnlineStatus() throws RemoteException;//玩家在线状态
	public void OfflineStatus() throws RemoteException;//玩家下线状态
}

AccountStatusHelper(接口实现类<代理>)

package com.practice.MyRemote.Imple;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import com.practice.MyRemote.AccountStatus;

public class AccountStatusHelper extends UnicastRemoteObject implements AccountStatus {
	private static final long serialVersionUID = 1L;
	@SuppressWarnings("unused")
	private boolean Online = false;

	public AccountStatusHelper(boolean _Online) throws RemoteException {
		this.Online = _Online;
	}
	
	@Override
	public void OnlineStatus() throws RemoteException {
		System.out.println("玩家1正使用账号游戏中……");
	}

	@Override
	public void OfflineStatus() throws RemoteException {
		System.out.println("玩家1退出王者荣耀账号!");
	}

}

Server(服务类)

package com.practice.MyRemote.Server;

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

import com.practice.MyRemote.AccountStatus;
import com.practice.MyRemote.Imple.AccountStatusHelper;

/**
 * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
 */

public class Server {
	private static final String HOST = "localhost";
	private static final int PORT = 9090;
	
	public static void main(String [] args) {
		try {
			//创建对象, 准备将这个对象作为远程对象注册
			AccountStatus accountStatusHelper = new AccountStatusHelper(true);
			
			LocateRegistry.createRegistry(PORT);
			//绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
			Naming.bind("rmi://" + HOST + ":" + PORT + "/AccountStatusHelper",accountStatusHelper);
			System.out.println("---->远程对象绑定成功!");
		}catch(RemoteException e) {
			System.out.println("创建远程对象发生异常!");
			e.printStackTrace();
		}catch(AlreadyBoundException e) {
			System.out.println("发生重复绑定对象异常!");
			e.printStackTrace();
		}catch(MalformedURLException e) {
			System.out.println("发生URL畸形异常!");
			e.printStackTrace();
		}
	}
}

Client类(客户类)

package com.practice.Client;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

import com.practice.MyRemote.AccountStatus;
/*
 * 用于调用远程服务
 * 
 * 客户端测试,在客户端调用远程对象上的远程方法,并返回结果
 */

public class Client {
	public static int flag = 0;
	public static void main(String[] args){
		new Thread(new Runnable() {
			public void run() {
				try {
					AccountStatus accountStatus;
					accountStatus = (AccountStatus)Naming.lookup("rmi://127.0.0.1:9090/AccountStatusHelper");
					accountStatus.OnlineStatus();
					Thread.sleep(1000);
					if(flag == 1) {
						accountStatus.OfflineStatus();
					}
				} catch (NotBoundException | MalformedURLException | RemoteException | InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
			}
		}).start();
		System.out.println("玩家2通过QQ登陆王者荣耀");
		flag = 1;
	}
}

六.运行结果

⑴先运行Server类
运行结果:

⑵运行Client类

JAVA RMI(Remote Method Invoke)

RMI提供了客户辅助对象(Stub)和服务器辅助对象(Skeleton),为客户辅助对象创建和服务对象相同的方法。RMI的好处在于你不必亲自写任何网络或I/O的代码。客户程序调用远程方法(即真正的服务所在)就和运行在客户自己的本地JVM上对对象进行正常方法调用一样。RMI提供了所有运行时的基础设施
RMI将客户辅助对象称为stub(桩),服务辅助对象称为skeleton(骨架)

RMI方法调用是如何发生的

①客户对象调用客户辅助对象的doSomeThing()方法。

②客户辅助对象打包调用信息(变量、方法名等),然后通过网络将它运给服务辅助对象

③服务辅助对象把来自客户辅助对象的信息解包,找出被调用的方法(以及在哪个对象内),然后调用真正的服务对象上的真正方法

④服务对象上的方法被调用,将结果返回给服务辅助对象

⑤服务辅助对象把调用的返回信息打包,然后通过网络运回给客户辅助对象

⑥服务辅助对象把返回值解包,返回给客户对象,对于客户对象来说,这是完全透明的

制作远程服务

第一步:制作远程接口

远程接口定义出可以让客户远程调用的方法。客户将用它作为服务的类类型。Stub和实际的服务都实现此接口。
⑴ 扩展java.rmi.Remote。Remote是一个"记号"接口,所以Remote不具有方法,对于RMI来说,Remote接口具有特别的意义,所以我们必须遵守规则

import java.rmi.Remote;
import java.RemoteException;
public interface MyRemote extends Remote{
    ……
}

⑵声明所有的方法都会抛出RemoteException
客户使用远程接口调用服务。换句话说,客户会调用实现远程接口的Stub上的方法,而Stub底层用到了网络和I/O,所以所有坏事情都有可能发生。客户必须认识到此风险,通过处理或声明远程异常来解决。如果接口的方法声明了异常,任何在接口类型的引用上调用方法的代码也必须处理或声明异常。

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface MyRemote extends Remote{
	public String sayHello() throws RemoteException;
}

⑶确定变量和返回值是否属于原语(primitive)类型或者可序列化(Serializable)类型
远程方法的变量和返回值,必须属于源于类型或Serializable类型。远程方法的变量必须被打包并通过网络运送,这要靠序列化实现。如果使用原语类型、字符串和许多API中内定的类型(包括数组和集合),都不会有问题。如果传送的是定义的类,就必须保证类实现了Serializable接口

第二步:制作远程实现

这是做实际工作的类,为远程接口中的定义的远程方法提供真正的实现
⑴扩展UnicastRemoteObject
为了要成为远程对象,对象需要某些"远程的"功能。最简单的方式就是扩展java.rmi.server.UnicastRemoteObject,让超类完成基本工作

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
    ……
}

⑵实现客户想要调用的接口的方法

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class MyRemoteImpl extends UnicastRemoteObject implements MyRemote {
	private static final long serialVersionUID = 1L;
    /*
    /*UnicastRemoteObject的构造器会抛出RemoteException.当类被实例化时
    /*候,如果超类的构造器抛出异常,那么只能声明子类的构造器也抛出异常
    */
	protected MyRemoteImpl() throws RemoteException {}
    
	public String sayHello() throws RemoteException {
		return "server says:'Hello'";
	}	
}

⑶用RMI Registry注册此服务
目前,我们已经有了MyRemoteImpl这个远程服务,必须让它可以被远程客户调用。我们要做的就是将此服务实例化,然后放进RMI Registry中(要先确定RMI Registry正在运行,否则注册失败)。当注册这个对象时,RMI系统其实注册的是Stub,因为这是客户真正需要的。注册服务使用了java.rmi.Naming类的静态bind方法

package com.practice.SayHello;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
 
/**
 * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
 */
public class Server {
    private static final String HOST = "localhost";
    private static final int PORT = 9090;
 
	public static void main(String args[]) {
 
        try {
            //创建2个对象, 准备将这个两个对象作为远程对象注册
			MyRemote MyRemoteHelper = new MyRemoteImpl();
 
            LocateRegistry.createRegistry(PORT);
 
            //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)

            Naming.bind("rmi://" + HOST + ":" + PORT + "/MyRemoteImpl", MyRemoteHelper);
 
            System.out.println("---->远程对象绑定成功!");
        } catch (RemoteException e) {
            System.out.println("创建远程对象发生异常!");
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.out.println("发生重复绑定对象异常!");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("发生URL畸形异常!");
            e.printStackTrace();
        }
    }
}

⑷运行Main程序
main方法首先会实例化一个服务对象,然后到RMI registry中注册,注册后,这个服务就可以供客户调用了。

第三步.客户端程序

客户取得stub对象(我们的代理)以调用其中的方法。所以我们需要RMI Registry的帮
忙。客户从Registry中寻找(lookup)代理,就像根据名字在电话簿里寻找一样

⑴客户到RMI Register中查找

Naming.lookup(“rmi://127.0.0.1/RemoteHelper”);//RemoteHelper是在服务端绑定时用的名称

⑵RMI Register返回Stub对象
作为lookup方法的返回值,RMI会自动对stub反序列化(在客户端必须有stub类)

MyRemote accountStatus=(MyRemote)Naming.lookup(“rmi:/127.0.0.1:9090/MyRemoteImpl”);

⑶客户调用stub的方法,就像stub就是真正的服务对象一样
完整的客户端代码:

import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
 
/**
 * 创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
 */
public class Server {
    private static final String HOST = "localhost";
    private static final int PORT = 9090;
 
	public static void main(String args[]) {
 
        try {
            //创建2个对象, 准备将这个两个对象作为远程对象注册
			MyRemote MyRemoteHelper = new MyRemoteImpl();
 
            LocateRegistry.createRegistry(PORT);
 
            //绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)

            Naming.bind("rmi://" + HOST + ":" + PORT + "/MyRemoteImpl", MyRemoteHelper);
 
            System.out.println("---->远程对象绑定成功!");
        } catch (RemoteException e) {
            System.out.println("创建远程对象发生异常!");
            e.printStackTrace();
        } catch (AlreadyBoundException e) {
            System.out.println("发生重复绑定对象异常!");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("发生URL畸形异常!");
            e.printStackTrace();
        }
    }
}

**⑷运行Main方法

七.源代码下载

从王者荣耀看设计模式(远程代理模式)