RMI远程方法调用技术的实现

RMI技术 ----(Remote Methed Invoke 远程方法调用)
RMI(Remote Method Invocation)为远程方法调用,是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。

Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

今天我们模拟以下RMI远程方法调用技术,从而去更加深入的了解RMI。

在CSFramework中,存在大量客户端向服务器端发出的请求;而这个请求到了服务器端,实质上是要执行一些服务器端的方法,并得到一个“响应”。

那么,如果把请求当成一个“本地方法”,在客户端执行,而实质上,该方法只在服务器端存在真正的本体。

RMI技术的核心:
1、代理技术;
2、反射机制;
3、网络传递参数;
4、网络传递返回值;
5、短连接。

当实现RMI后,将用RMI实现NetFramework,并在其中实现服务器端对客户端socket的轮询、在客户端通过模态框阻止用户无效操作。

客户端使用代理;执行相关方法时,实质上是通过代理对象执行的;在具体被执行的方法中,实际的操作是向服务器发出“请求”:请求客户端原本要执行的方法。
在客户端,当代理对象调用相关方法时,其内部实际执行的是以下操作:

1、客户端需要连接RMI服务器;
2、向服务器发送要执行的方法的名称、参数类型、参数值;
3、等待服务器返回执行执行结果;这个结果可以看成“响应”。**

服务器端必须建立服务器,而且不断侦听来自客户端的连接请求;这个连接请求实质上是客户端发出请求的第一步。接着,服务器开始接收客户端发来的,对应那个“方法”的名称、参数类型、参数值;服务器根据客户端发来的上述信息,找到相关方法,并执行;并且,将执行结果,返回给客户端。

在RMI的实际工作过程中,客户端只有接口,而不存在,也不需要存在该接口的实现类!
客户端对RMI的方法的执行,实质上都是传递方法信息和方法参数,再从服务器端接收结果!因此,在客户端获取proxy的时候,能提供的仅仅是接口,没有可能提供该接口实现类。


首先我们新建一个接口叫IUserLogin,它里面有一个getNike方法,这个方法主要用于测试。

package com.mec.rmi.some;

public interface IUserLogin {
	String getNike(String id,String password);
}


再新建一个类去继承 IUserLogin 接口,此时需要复写getNike方法。

注意:这里是服务器端的代码。是App开发者根据自己需求完成的部分。客户端只有接口。

getNike方法功能其实是当客户端使用自己账号及密码登陆时,若信息与数据库中信息一直,则返回客户的昵称,这里为了测试,做了简化。

package com.mec.rmi.some;

public class UserLogin implements IUserLogin {

	public UserLogin() {
	}
	
	@Override
	public String getNike(String id, String password) {
		System.out.println("id: " + id + ", password: " + password);
		return "honey";
	}

}


RMIClient类 ---- 客户端代码
我们先来明确客户端要做的事:

1、客户端需要连接RMI服务器;
2、向服务器发送要执行的方法的名称、参数类型、参数值;
3、等待服务器返回执行执行结果;这个结果可以看成“响应”。

第二条我们用代理模式处理。

客户端这边只需要存在一些接口,用接口的类类型构造出一个个接口代理对象出来,然后调用接口代理对象的对应方法。

被调用的方法实际上并没有完成任何有效的操作,只是单纯地连接服务器 并把要执行的方法(Method.toString)和 方法参数 这些数据 传给服务器,之后等待服务器的返回即可。具体有效的操作是在服务器端完成的!

MecProxy代码以及讲解,链接跳转处。

package com.mec.rmi.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.Socket;
import java.net.UnknownHostException;

import com.mec.util.ArgumentMaker;
import com.mec.util.proxy.IMethdInvoker;
import com.mec.util.proxy.MecProxy;

public class RMIClient {
	private String rmiServerip;
	private int rmiServerPort;
	
	public RMIClient() {
	}

	public String getRmiServerip() {
		return rmiServerip;
	}

	public void setRmiServerip(String rmiServerip) {
		this.rmiServerip = rmiServerip;
	}

	public int getRmiServerPort() {
		return rmiServerPort;
	}

	public void setRmiServerPort(int rmiServerPort) {
		this.rmiServerPort = rmiServerPort;
	}
	
	private String getParaGson(Object[] args) {
		ArgumentMaker am = new ArgumentMaker();
		for(int i = 0; i < args.length;i++) {
			am.add("arg" + i, args[i]);
		}
		return am.toString();
	}
	
	//这个方法就是获取代理对象的方法,代理对象调用相应方法时,
	//执行的是methodInvoke方法,我们可以看到这个方法里面实现的功能就是:
	//与服务器建立通信信道,并将被执行方法(getNike方法)的Method字符串化 以及
	//执行该方法所需要的参数 发送给服务器端。并且等待服务器的返回。
	public <T> T getProxy(Class<?> interfance) {
		MecProxy mecProxy = new MecProxy();
		IMethdInvoker methodInvoker = new IMethdInvoker() {
			
			@SuppressWarnings({ "hiding", "unchecked" })
			@Override
			public <T> T methodInvoke(Object object, Method method, Object[] args) {
				Socket socket = null;
				DataOutputStream dos = null;
				DataInputStream dis = null;
				try {
					socket = new Socket(rmiServerip, rmiServerPort);
					dos = new DataOutputStream(socket.getOutputStream());
					
					dos.writeUTF(method.toString());
					dos.writeUTF(getParaGson(args));
					dis = new DataInputStream(socket.getInputStream());
					
					String resultString = dis.readUTF();
					Type returnType = method.getGenericReturnType();
					T result = (T) ArgumentMaker.gson.fromJson(resultString, returnType);
					
					return (T) result;
				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}  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();
						}
					}
				}
				return null;
			}
		};
		mecProxy.setMethodInvoker(methodInvoker);
		return mecProxy.getProxy(interfance);
	}
	
	
}

这条语句传递的字符串如下图:
dos.writeUTF(method.toString());
在这里插入图片描述


RMIDefinition类

在编写服务器前,我们需要考虑,服务器是怎么找到要执行的方法的呢,方法与接口之间又有什么关系呢?为什么要用接口?不要着急,接下来我会慢慢讲述哒!

客户端拥有接口,而服务器端拥有与客户端相同的接口和接口实现类,所以我们可以在服务器端建立一个XML配置文件,用来存储接口与其对应实现类的全称。
在开启服务器前,我们首先扫描XML文件,并生成一个Map,该Map以接口全称作为键,以RMIDefinition对象作为值。RMIDefinition类将接口对应实现类的Class 以及 其对象封装在一起,以便后续使用。

package com.mec.rmi.core;

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

	void setKlass(Class<?> klass) {
		this.klass = klass;
	}
	
	Object getObject() {
		return object;
	}

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

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

	@Override
	public String toString() {
		return "MethodDefinition [klass=" + klass + ", object=" + object + "]";
	}

}

RMIFactory

package com.mec.rmi.core;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Element;

import com.mec.util.XMLParser;

public class RMIFactory {
	private static final Map<String, RMIDefinition> methodPool = new HashMap<>();
	
	public RMIFactory() {
	}
	
	static void scanXML(String path) {
		new XMLParser() {
			
			@Override
			public void dealElement(Element element, int index) {
				String interfanceName = element.getAttribute("interfance");
				String className = element.getAttribute("class");
				try {
					Class<?> klass = Class.forName(className);
					Object object = klass.newInstance();
					RMIDefinition md = new RMIDefinition(klass, object);
					methodPool.put(interfanceName, md);
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
				
			}
		}.parseTag(XMLParser.getDocument(path), "mapping");
	}
	
	static RMIDefinition getRMIDefinition(String interfance) {
		if(methodPool == null) {
			return null;
		}
		return methodPool.get(interfance);
	}
	
}


RMIServer类 ---- 服务器端代码

服务器端最主要的功能就是不间断地监听客户端的连接请求!
侦听到一个客户端连接,就new 一个RMIService对象,与客户端进行通信。RMIService类,下面马上介绍。

package com.mec.rmi.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class RMIServer implements Runnable {
	private int port;
	private volatile boolean startup;
	private ServerSocket server;
	
	public RMIServer() {
		RMIFactory.scanXML("/RMIMapping.xml");
	}
	
	public void setPort(int port) {
		this.port = port;
	}
	
	public void startup() {
		if(startup) {
			return;
		}
		try {
			server = new ServerSocket(port);
			startup = true;
			new Thread(this,"RMI Server").start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void close() {
		startup = false;
		if(server != null && !server.isClosed()) {
			try {
				server.close();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				server = null;
			}
		}
	}
	
	public void shutdown() {
		if(startup == false) {
			return;
		}
		close();
	}

	@Override
	public void run() {
		while(startup) {
			try {
				Socket client = server.accept();
				new RMIService(client);
			} catch (IOException e) {
				if(startup) {
					//服务器异常宕机;
					System.out.println("服务器异常宕机");
				}
				close();
			}
		}
	}
	
}


RMIService类

主要负责与客户端单次通信,是一次短连接的体现。

创建一个线程,将通信信道建立起来,接收客户端发来的数据信息后,甄别出要执行的方法,执行并将方法返回值发回给客户端,完成后与客户端断开连接。

package com.mec.rmi.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.Socket;

import com.mec.util.ArgumentMaker;

public class RMIService implements Runnable {
	private Socket socket;
	
	public RMIService(Socket client) {
		socket = client;
		new Thread(this, "短连接线程").start();
	}
	
	private Object[] getParas(String paras,Method method){
		ArgumentMaker am = new ArgumentMaker(paras);
		Type[] parasKlass = method.getGenericParameterTypes();
		Object[] parasObject = new Object[parasKlass.length];
		for(int i = 0; i < parasKlass.length;i++) {
			parasObject[i] = am.getValue("arg" + i, parasKlass[i]);
		}
		return parasObject;
	}
	
	//这个方法就是具体执行方法
	//这里主要是获得反射机制执行方法的一系列参数
	//MethodInformation类下面讲解
	private String invokeMethod(String meth,String paras) {
		
		MethodInformation mi = new MethodInformation(meth);
		String interfanceString = mi.getClassName();
		
		RMIDefinition rmid = RMIFactory.getRMIDefinition(interfanceString);
		Class<?> methodKlass = rmid.getKlass();
		Object object = rmid.getObject();
		try {
			Method method = mi.getMethod(methodKlass);
			Object[] parasKlass = getParas(paras,method);
			Object result = method.invoke(object, parasKlass);
			return  ArgumentMaker.gson.toJson(result);
		} 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 null;
	}

	//这里可以看到通信信道在执行完方法并发送完返回值后就立刻关闭了
	//也就是说RMI服务器在这里支持的是短连接。不会一直保持与某个客户端的连接
	@Override
	public void run() {
		try {
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			String methodString = dis.readUTF();
			String paras = dis.readUTF();
			
			String res = invokeMethod(methodString,paras);
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			dos.writeUTF(res);
			
			dis.close();
			dos.close();
			socket.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	
	
}


MethodInformation类

主要功能就是解析Method字符串,即在客户端时,我们曾向服务器发送的方法字符串。该类是为了方便我们使用所创建。如下图:
在这里插入图片描述
MethodInformation类中有三个成员:

  • className (接口全称)
  • methodName(方法名称)
  • strParameterTypes(字符串形式的形参类型数组)
package com.mec.rmi.core;

import java.lang.reflect.Method;

public class MethodInformation {
	private String className;
	private String methodName;
	private String[] strParameterTypes;
	
	public MethodInformation() {
	}

	public MethodInformation(String methodInfo) {
		parse(methodInfo);
	}
	
	//把字符串形式的参数类型,转换成对应的类类型。并生成该方法的参数类型数组。
	private Class<?>[] getParameterTypes() throws ClassNotFoundException {
		int cnt = strParameterTypes.length;
		if (cnt <= 0) {
			return new Class<?>[] {};
		}
		Class<?>[] res = new Class<?>[cnt];
		for (int i = 0; i < cnt; i++) {
			Class<?> klass = Class.forName(strParameterTypes[i]);
			res[i] = klass;
		}
		
		return res;
	}
	
	//在对应的类中通过方法名称以及该方法的参数类型数组,反射机制找到对应方法并返回。
	public Method getMethod(Class<?> klass) 
			throws ClassNotFoundException, NoSuchMethodException, SecurityException {
		Class<?>[] paraTypes = getParameterTypes();
		Method method = klass.getDeclaredMethod(methodName, paraTypes);
		return method;
	}
	
	//处理形参部分字符串
	private void parseParameterTypes(String str) {
		if (str.length() <= 0) {
			strParameterTypes = new String[] {};
			return;
		}
		//将str字符串以“,”分割成多个字符串并组成一个字符串数组。
		strParameterTypes = str.split(",");
	}
	
	//这里处理的就是完整的字符串。是核心。
	public MethodInformation parse(String methodInfo) {
		String[] strs = methodInfo.split(" ");
		String methodString = strs[strs.length - 1];
		int leftBracketIndex = methodString.indexOf("(");
		String methodFullName = methodString.substring(0, leftBracketIndex);
		
		int lastDotIndex = methodFullName.lastIndexOf(".");
		className = methodFullName.substring(0, lastDotIndex);
		methodName = methodFullName.substring(lastDotIndex + 1);
		
		String strParameterTypes = methodString.substring(leftBracketIndex + 1, methodString.length() - 1);
		parseParameterTypes(strParameterTypes);
		
		return this;
	}

	public String getClassName() {
		return className;
	}

	public String getMethodName() {
		return methodName;
	}

	public String[] getStrParameterTypes() {
		return strParameterTypes;
	}
	
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现远程调用 JDK 17,你可以使用 Java RMI远程方法调用)或者使用其他远程调用框架,比如 gRPC 或 Apache Dubbo。这些框架都可以帮助你在分布式系统中实现远程调用。 如果你选择使用 Java RMI,你需要按照以下步骤进行设置: 1. 创建接口:定义远程调用的接口,该接口中声明了需要远程调用方法。 ```java public interface MyRemoteInterface extends Remote { public String sayHello() throws RemoteException; } ``` 2. 实现接口:实现上述接口的一个类,该类包含了对远程方法的具体实现。 ```java public class MyRemoteImplementation implements MyRemoteInterface { public String sayHello() throws RemoteException { return "Hello, World!"; } } ``` 3. 注册远程对象:在服务器端,将实现类注册为远程对象。 ```java MyRemoteImplementation obj = new MyRemoteImplementation(); MyRemoteInterface stub = (MyRemoteInterface) UnicastRemoteObject.exportObject(obj, 0); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("MyRemoteObject", stub); ``` 4. 获取远程对象:在客户端,通过 RMI 注册表获取远程对象。 ```java Registry registry = LocateRegistry.getRegistry(serverHostname, 1099); MyRemoteInterface stub = (MyRemoteInterface) registry.lookup("MyRemoteObject"); ``` 5. 调用远程方法:通过远程对象调用远程方法。 ```java String result = stub.sayHello(); System.out.println(result); ``` 这样就可以在分布式系统中实现远程调用了。记得在编译和运行代码时,要确保正确配置了类路径和相关的依赖项。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值