模拟实现RMI

概念:

    什么是RMI?

    RMI(Remote Method Invocation,远程方法调用)是用Java在JDK1.2中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。(定义来自百度百科)。

    很多时候客户机需要执行某个方法,但是不含有该方法的具体内容,(或者说客户机根本不应该拥有的方法的具体实现)。方法的实现存在于服务器中,客户端又如何获取其想要的结果的,RMI技术解决的正是这类问题。

    RMI是一种网络技术,基于服务器客户机模式。RMI客户端执行某一个方法的时候,其执行内部的过程是:连接RMI服务器,将想要执行的方法信息以及参数通过网络的方式发送给RMI服务器,在服务器端收到方法信息以及参数后,在本地执行该方法,再通过网络传输的方式将方法执行的结果发送回客户端。

    RMI涉及到的技术:

    动态代理技术(JDK代理)

    服务器客户机短连接

    网络传输

    反射机制

    发送对象(序列化与反序列化)

    为什么要使用动态代理呢?RMI客户端与服务器都必须含有相同的远程接口,接口中有可供客户端调用的方法,方法的实现类在客户端之中。RMI客户端调用方法可以使用这个远程接口的代理对象来调用方法,在代理执行方法的实现过程,就可以完成发送——取得结果这一过程,这样在客户端调用接口方法就仿佛是在执行本地方法一样。

    服务端接收到客户端发送的信息(方法信息、参数)后,通过发送的信息找到对应的类的方法反射执行。将得到的result返回。

    RMI过程非常短暂,服务器与客户端采用短连接即可,即,客户端得到方法的返回结果就可以关闭通信了。

    网络传输的类的对象必须是序列化的(基本类型已经是序列化的了),保证其唯一性,这里我们选择使用Gson发送Json字符串(已序列化),但是如果远程方法的参数中含有类类型参数,这个类也是必须序列化的。

    介于RMI服务器有可能会处理大量的请求,会频繁地创建线程执行方法,我们这里加入线程池。

下面是代码:

    RMIClient:

public class RMIClient {
	MethodInvoker methodInvoker;
	
	public RMIClient() {
		this.methodInvoker = new MethodInvoker();
	}
	
	public RMIClient setRMIServerIp(String ip) {
		methodInvoker.setRmiServerIp(ip);
		return this;
	}
	
	public RMIClient setRMIServerPort(int port) {
		methodInvoker.setRmiServerPort(port);
		return this;
	}
	
        //连接时间
	public RMIClient setConnectDelay(int delay) {
		methodInvoker.setConnectDelay(delay);
		return this;
	}
	
        //获得想要执行的方法的接口的代理对象
	public <T> T getProxy(Class<?> klass) {
		MyProxy myproxy = new MyProxy();
		myproxy.setMethodInvoker(this.methodInvoker);
		return myproxy.getProxy(klass);
	}
}

    这里作说明:代理我用的是我编写代理工具,MethodInvoker是代理工具里的一个接口IMethodInvoker的实现类,我们知道代理对象执行方法的时,其方法真正实现是在InvocationHandler参数(这是一个接口)里的invoke方法里。我们在这个invoke方法里调用我们IMethodInvoker的方法。那么如果我们向代理对象注入我们编写的IMethodInvoker实现类对象。那么编写这个实现类对象的方法即是在编写代理类对象执行的方法。下面也贴上我写的关于代理的博文链接。

代理机制详解

代理工具

关于Gson以及ArgumentMaker类:Gson工具

MethodInvoker:

public class MethodInvoker implements IMethodInvoker{
	public static final int DEFAULT_CONNECT_DELAY = 20000;
	private String rmiServerIp;
	private int rmiServerPort;
	private int connectDelay;
	
	public MethodInvoker() {
		this.connectDelay = DEFAULT_CONNECT_DELAY;
	}
	public void setRmiServerIp(String rmiServerIp) {
		this.rmiServerIp = rmiServerIp;
	}

	public void setRmiServerPort(int rmiServerPort) {
		this.rmiServerPort = rmiServerPort;
	}

	public void setConnectDelay(int delay) {
		this.connectDelay = delay;
	}

	private String getArgs(Object[] args) {
		if (args == null) {
			return "";
		}
		
		ArgumentMaker am  = new ArgumentMaker();
		for (int i = 0; i < args.length; i++) {
			am.addArgument("arg"+i, args[i]);
		}
		
		return am.toString();
	}


	@Override
	public <T> T methodInvoke(Object object, Method method, Object[] args)
			throws ConnectServerFailureException {	
			Socket socket = null;
			DataInputStream dis = null;
			DataOutputStream dos = null;
			boolean ok = true;
			
                        //这里可以通过负载均衡策略选择服务器实现负载均衡
			String ip = this.rmiServerIp;
			int port = this.rmiServerPort;
		try {

			socket = new Socket();
			SocketAddress address = new InetSocketAddress(ip, port);
			socket.connect(address, connectDelay);
			
			dos = new DataOutputStream(socket.getOutputStream());
                        //发送方法信息以及参数信息
			dos.writeUTF(method.toString());
			dos.writeUTF(getArgs(args));
			
			dis = new DataInputStream(socket.getInputStream());
			String returnString = dis.readUTF();
			
			Type returnType = method.getGenericReturnType(); //可以处理泛型
			if ("void".equals(returnType.toString())) {
				return null;
			}
			@SuppressWarnings("unchecked")
			T result = (T) ArgumentMaker.getGson().fromJson(returnString, returnType);
			
			return result;
		} catch (IOException e) {
			ok = false;
		} finally {
			if (dis != null) {
				try {
					dis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if (dos != null) {
				try {
					dos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			
			if (socket != null && !socket.isClosed()) {
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		if (!ok) {
			throw new ConnectServerFailureException("连接异常!");
		}
		
		return null;
	}
}

ConnectServerFailureException是我编写的一个异常,继承自Exception,没什么可讲的。

让我们看核心代码:

dos.writeUTF(method.toString());
dos.writeUTF(getArgs(args));


String returnString = dis.readUTF();

Type returnType = method.getGenericReturnType(); //可以处理泛型
if ("void".equals(returnType.toString())) {
	return null;
}
@SuppressWarnings("unchecked")
T result = (T) ArgumentMaker.getGson().fromJson(returnString, returnType);			
return result;
			

   这里完成了发送方法信息和参数的方法。method.toString包含了一个方法的详细信息,(类名,方法名、参数类型信息等),客户端可以根据其找到对应的方法。

让我们看服务器端:

RMI服务器:

public class RMIServer implements Runnable{
	public static final int CORE_POOL_SIZE = 10;
	public static final int  MAXIMUM_POOL_SIZE= 20;
	public static final long KEEP_ALIVE_TIME = 5000;
	public static final int CAPACITY = 20;
	
	private ServerSocket server;
	private int port;
	private volatile boolean startup;
	private ThreadPoolExecutor threadPool;
	
	public RMIServer(int port) {
		this.port = port;
	}
	
	public void setPort(int port) {
		this.port = port;
	}
	
	public void startup() {
		if (startup == true) {
			System.out.println("服务器已启动, 请勿重复启动!");
			return;
		}
		
		try {
			threadPool = new ThreadPoolExecutor(
					CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, 
					TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(CAPACITY));
			this.server = new ServerSocket(port);
			startup = true;
			new Thread(this, "RMIServer").start();
			System.out.println("RMI服务器启动成功");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void close() {
		this.startup = false;
		if (server == null && !server.isClosed()) {
			try {
				this.server.close();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				this.server = null;
			}
		}
	}

	@Override
	public void run() {
		while (startup == true) {
			try {
				Socket client = this.server.accept();
				threadPool.execute(new RMIService(client));
			} catch (IOException e) {
				if (startup == true) {
					startup = false;
				}
			}
		}
	}
	
}

  RMI服务器接受到有客户端请求后,创建一个RMIService线程去执行方法。

  在看RMIService类前,让我们先看看几个辅助的类

  RMI工厂(用以记录远程接口以及实现类的映射关系,这里用的xml配置扫描方式,也可换为注解配置),其中维护一张map,以远程接口名为键,RMIDefinition为值。

public class RMIFactory {
	private static final Map<String, RMIDefinition> rmiPool;
	
	static {
		rmiPool = new HashMap<String, RMIDefinition>();
	}
	
	public RMIFactory() {
	}
	
	public static void scanRMIMapping(String xmlPath) {
		new XMLParser() {
			
			@Override
			public void dealElement(Element element, int index) {
				String interfaceStr = element.getAttribute("interface");
				String className = element.getAttribute("class");
				
				try {
					Class<?> klass = Class.forName(className);
					Object object = klass.newInstance();
					
					RMIDefinition definition = new RMIDefinition(object, klass);
					rmiPool.put(interfaceStr, definition);
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
				
			}
		}.parseTag(XMLParser.getDocument(xmlPath), "mapping");
	}

	public static RMIDefinition getDefinition(String interfaceName) {
		return rmiPool.get(interfaceName);
	}
}

 RMIDefinition是一个包装类,用以存储我们需要反射的方法的类信息和一个对象:

public class RMIDefinition {
	private Object object;
	private Class<?> klass;
	
	public RMIDefinition() {
	}
	
	public RMIDefinition(Object object, Class<?> klass) {
		this.object = object;
		this.klass = klass;
	}

	public Object getObject() {
		return object;
	}

	public void setObject(Object object) {
		this.object = object;
	}

	public Class<?> getKlass() {
		return klass;
	}

	public void setKlass(Class<?> klass) {
		this.klass = klass;
	}
}

   MethodInfomation类:用以解析方法客户端发来的方法信息,里面用字符串裁切的手段去获得类名、方法名、参数类型的名称等,用反射机制获取method对象和参数的类型:

public class MethodInformation {
	private String className;
	private String methodName;
	private String[] paraTypesStr;
	
	public MethodInformation() {
	}
	
	public MethodInformation(String methodInfo) {
		parseMethodInfomation(methodInfo);
	}

	private Class<?>[] getParameterTypes() throws ClassNotFoundException{
		int count = paraTypesStr.length;
		
		if (count <= 0) {
			return new Class<?>[] {};
		} else {
			Class<?>[] result = new Class<?> [count];
			for (int i = 0; i < count; i++) {
				result[i] = StringToClass.getKlass(paraTypesStr[i]);
			}
			return result;
		}		
	}
	
	public Method getMethod(Class<?> klass) throws 
		ClassNotFoundException, NoSuchMethodException, SecurityException{
		Class<?>[] parameterTypes = getParameterTypes();
		Method method = klass.getDeclaredMethod(methodName, parameterTypes);
		return method;
	}
	
	private void parseParameterTypes(String str) {
		if (str.length() <= 0) {
			paraTypesStr = new String[] {};
		}
		
		paraTypesStr = str.split(",");
	}
	private String getMethodString(String[] strs) {
		for (int i = 0; i < strs.length; i++) {
			if (strs[i].endsWith(")")) {
				return strs[i];
			}
		}
		
		return null;
	}
	
	public MethodInformation parseMethodInfomation(String methodInfo) {
		String[] strs = methodInfo.split(" ");
		
		String methodString = getMethodString(strs);
		int leftBracketIndex = methodString.indexOf("(");
		String methodFullName = methodString.substring(0, leftBracketIndex);
		
		int lastDotIndex = methodFullName.lastIndexOf(".");
		this.className = methodFullName.substring(0, lastDotIndex);
		this.methodName = methodFullName.substring(lastDotIndex+1);
		
		String ParaStr = methodString.substring(leftBracketIndex+1, methodString.length()-1);
		parseParameterTypes(ParaStr);
		
		return this;
	}
	
	public String getClassName() {
		return className;
	}

	public String getMethodName() {
		return methodName;
	}

	public String[] getParaTypesStr() {
		return paraTypesStr;
	}	
}

里面有一个StringToClass类是我编写的工具包的一个类,内容也非常简单。

//基本类型直接在map中获得Class<?>对象,非基本类型通过Class.forName方法获得
public class StringToClass {
	private static final Map<String, Class<?>> primeClassPool = new HashMap<String, Class<?>>();
	static {
		primeClassPool.put("byte", byte.class);
		primeClassPool.put("boolean", boolean.class);
		primeClassPool.put("char", char.class);
		primeClassPool.put("short", short.class);	
		primeClassPool.put("int", int.class);
		primeClassPool.put("long", long.class);
		primeClassPool.put("float", float.class);
		primeClassPool.put("double", double.class);		
	}
	public StringToClass() {		
	}
	
	public static Class<?> getKlass(String className) {
		try {
			return (primeClassPool.containsKey(className)) ?
					(primeClassPool.get(className)) :
						Class.forName(className);
		} catch (ClassNotFoundException e) {
		e.printStackTrace();
		}
		
		return null;
	}
}

下面看我们真正处理客户端请求的类RMIService:

public class RMIService implements Runnable{
	private Socket socket;
	
	public RMIService(Socket socket) {
		this.socket = socket;
	}
	
	private Object[] getparameterValues(Method method, String argString) {
		Object[] result = null;
		
		Parameter[] parameters = method.getParameters();
		int count = parameters.length;
		if (count <= 0) {
			return new Object[]{};
		}
		
		result = new Object[count];
		
		ArgumentMaker am = new ArgumentMaker(argString);
		for (int i = 0; i < count; i++) {
			result[i] = am.getValue("arg"+i, parameters[i].getParameterizedType());
		}
		return result;
	}
	
	private Object invokeMethod(String methodInfo, String argString) {
		MethodInformation mi = new MethodInformation(methodInfo);
		
		String interfaceName = mi.getClassName();
		RMIDefinition rmiDefinition = RMIFactory.getDefinition(interfaceName);
		Object object = rmiDefinition.getObject();
		Class<?> klass = rmiDefinition.getKlass();
		
		Object result = null;
		
		try {
			Method method = mi.getMethod(klass);
			Object[] args = getparameterValues(method, argString);
			result = method.invoke(object, args);
		} catch (ClassNotFoundException e) {
				e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	 
	@Override
	public void run() {
		try {
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			
			String methodInfo = dis.readUTF();
			String argString = dis.readUTF();
			Object result = invokeMethod(methodInfo, argString);
			
			dos.writeUTF(ArgumentMaker.getGson().toJson(result));
			
			dis.close();
			dos.close();
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

核心代码:

String methodInfo = dis.readUTF();
String argString = dis.readUTF();
Object result = invokeMethod(methodInfo, argString);
			
dos.writeUTF(ArgumentMaker.getGson().toJson(result));

下面进行测试:

  xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<RMIMapping>
	<mapping interface="com.mec.rmi.test.IAppInterface"
	class="com.mec.rmi.test.AppImpl"></mapping>
</RMIMapping>

  远程接口:

public interface IAppInterface {
	String getInfo(StuInfo info);
}

 其中涉及的类StuInfo

public class StuInfo implements Serializable{
	private static final long serialVersionUID = -926878547988260203L;
	private String stuName;
	private int serialNum;
	private boolean status;
	
	public StuInfo() {
	}

	public StuInfo(String stuName, int serialNum, boolean status) {
		this.stuName = stuName;
		this.serialNum = serialNum;
		this.status = status;
	}

	public String getStuName() {
		return stuName;
	}

	public void setStuName(String stuName) {
		this.stuName = stuName;
	}

	public int getSerialNum() {
		return serialNum;
	}

	public void setSerialNum(int serialNum) {
		this.serialNum = serialNum;
	}

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
	}
	@Override
	public String toString() {
		return "姓名: " + stuName + " , "
		+ "班级序号: " + serialNum + " , "
		+ (status ? "在读。" : "已毕业。");
	}
}

  实现类:

public class AppImpl implements IAppInterface {

	@Override
	public String getInfo(StuInfo info) {
		return "["  + info.toString() + "]";
	}
}

 服务端测试:

public class TestForRmIServer {
	
	public static void main(String[] args) {
		RMIFactory.scanRMIMapping("/RMIMapping.xml");
		new RMIServer(54188).startup();	
	}
}

客户端测试:

 

public class TestForRMIClient {
	
	public static void main(String[] args) {
		RMIClient client = new RMIClient().setRMIServerIp("127.0.0.1")
				.setRMIServerPort(54188);
		IAppInterface i = client.getProxy(IAppInterface.class);
		StuInfo info = new StuInfo("张三", 28, true);
		System.out.println(i.getInfo(info));
	}
}

  测试结果:

    分析:我们可以看到,在客户端,生成一个远程接口的代理对象,调用接口里的方法,我们获得的结果是在实现类的方法执行的结果,前面也谈到了,实现类是在服务端才存在的,也就是说,看起来这里是调用了本地的一个方法,实际上我们调用到了远程(即客户端)的远程接口实现类方法。我们的RMI模拟成功!

    感悟:工具思想是java编程非常重要的思想,我在这里也用到了非常多以前编写的工具类,RMI本身也是一个非常犀利的工具,我在后序的一些大型项目(CSFramework(服务器客户机模式网络框架), 服务发现,分布式多文件云传输框架中都会用到)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值