文章目录
前言
《RMI》框架的实现是本人的一个编程训练项目,为了提升本人的编程能力、JAVA 编程思想,基于框架的角度出发,实现对远程过程调用(RPC)协议,对《RMI》的个人理解和底层实现方法进行阐述。
此《RMI》框架“不重复造方轮子”,只是自我的编程训练的项目。
RMI
RMI 概述
RMI(Remote Method Invocation)是一种 Java 编程语言中的远程过程调用(RPC)协议,用于在不同的Java虚拟机(JVM)之间进行通信和交互。它允许远程计算机上的Java对象像本地对象一样进行访问和操作,从而使分布式应用程序的开发变得更加容易和方便。
一般市面上的 RMI 框架是建立在 Java 远程调用 JRMP(Java Remote Method Protocol)之上的。
这里博主对 RMI 进行过思考之后,感觉将所要执行的方法、参数等信息用二进制字节的传递方式效率太过低下,同时还得考虑相关类的序列号,而且传递的大量二进制信息降低了内部效率,所以本人想出了将对象、方法以及参数转换成 JSON 字符串的形式进行保存和传递。
RMI 框架技术难点
- 动态代理模式的实现
- 实现参数进行传递和恢复的工具(Gson)
- XML文件的解析工具
- 将所要执行方法的对象和方法进行封装
- RMI 工厂模式(方法池)的建立
RMI 基本思想
RMI 基本思想是远程方法调用,即客户端调用某个方法,其本质是将这个方法的调用请求,发送给服务器,由服务器代为执行,且,
服务器将执行结果回送客户端。
- 对于客户端而言,RMI 只要求客户端针对方法本身,产生一种错觉:方法是在本地被调用的;
- 对于服务器而言,RMI 相当于要处理一个来自客户端的“请求”;这个请求针对某个方法。
RMI 框架思考
RMI 相较于其他服务器客户短模式(B/S,NIO)来看,它是属于一种短连接(由客户端向服务器端发起“请求”,服务器连接客户端,并解析客户端传来的请求内容,并执行相关操作以得到“响应”信息,并将响应回送给该客户端,然后就立刻切断网络连接,这是一个短而快的”请求/响应“过程。)
其本质是也就是远程方法调用:Java 应用程序调用在远程 Java 虚拟机上运行的对象上的方法,就像调用本地对象的方法一样。
RMI 需求分析
客户端功能:
- 连接 RMI 服务器;
- 向服务器发送要执行的方法名称、实参等;
- 等待服务器返回这个方法在服务器端执行的结果
RMI服务器端功能:
- 建立 RMI 服务器;
- 侦听客户端连接请求;
- 连接 RMI 客户端;
- 接受客户端发送过来的要执行的方法名称、实参等信息;
- 找到这个需要代理执行方法,并反射机制执行该方法,并将方法执行的结果回传给客户端,断开与客户端的连接。
RMI CS模式相关思考
由于 RMI 框架建立涉及CS模式,所以需要要建立,并且进行连接,实现传输信息等等。
对于客户端而言,上述功能要能实现,就必须依赖“代理”技术。即,在客户端的代理执行相关方法时,实现:
- 1、连接服务器;
- 2、传递方法及实参数据;
- 3、等待并接收服务器回传的计算结果。
RMI 代理模式相关思考
首先 JDKProxy 和 CglibProxy 两者的差别是在是否提供接口
- CglibProxy 不提供接口,没有接口则不能让双方知道各自同步的地方。
- JDKProxy 提供了接口,客户端和服务器都按接口来走,规范了双方的类。
因此我们使用 JDKProxy。
在 RMI 实际的工作过程中,客户端只有接口,而不存在也不需要接口的实现类,客户端对 RMI 方法的执行,实质上都是传递方法信息和方法参数,再从服务器端接受执行的结果。因此,在客户端获取 proxy 的时候,能提供的仅仅是接口,没有可能提供该接口的实现类,因为它根本没有也不需要。
最终决定使用 JDKProxy(即,基于接口)。
基于 JDKProxy 代理模式下的客户端和服务器:首先客户端和服务器端相互建立连接,然后客户端将执行方法所需要的参数和方法传递给服务器端,服务器接收了相关数据后,通过反射机制执行相关方法,再将执行的结果传递给客户端。
RMI 框架实现
实现思路分析
RMI 框架的核心思想是客户端调用某个方法,其本质是将这个方法的调用请求,发送给服务器,由服务器代为执行实现相关功能,并且服务器将执行结果回送客户端。
一端“执行”一个方法,而这个方法的实际执行是在对端进行的。要实现这种模式,前提是服务器与客户端拥有相同的类,同时也会拥有相同的方法,想要实现这种客户端与服务器端达到同步的模式,我们就需要面向接口编程,接口作为服务器端与客户端共同遵守的约定,并且对于相关操作好控制,规范了双方的方法调用。
诚然客户端执行的方法,其实是通过调用这个类的代理对象的方法,在这个方法中实际上是将执行这个方法的参数和类名称、方法名称,通过网络通讯传输给服务器端;服务器端根据接收到的方法名称、类名称和参数,实际执行那个方法,再将方法执行结果回传给对端。
RmiProxy 的具体实现
这里客户端获取 JDK 代理,用代理执行所要执行的方法,并且将所执行的结果返回。
package com.mec.rmi.core;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class RmiProxy {
private RmiClient rmiClient;
public RmiProxy() {
this.rmiClient = new RmiClient();
}
public RmiClient getClient() {
return this.rmiClient;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<?> interfaze) {
ClassLoader classLoader = interfaze.getClassLoader();
Class<?>[] interfaces = new Class<?>[] { interfaze };
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
rmiClient.doRemoteInvoker(method, args);
return rmiClient.getResult();
}
};
return (T) Proxy.newProxyInstance(classLoader, interfaces, h);
}
}
RMI 服务器基础工厂准备
MethodBeanDefinition
将所要执行的方法所需要的:要执行方法的对象 object
和方法 method
封装成一个类 MethodBeanDefinition
。
public class MethodBeanDefinition {
private Object object;
private Method method;
MethodBeanDefinition() {
}
…………
}
RmiImplClassFactory
建立生成 bean 对象的工厂模式。定义单例的存放 bean 对象的方法池,根据扫描到的XML文件,获得所要执行的方法名称、实参等信息,放入到 map 中;
package com.mec.rmi.core;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Element;
import com.mec.util.XMLParser;
public class RmiImplClassFactory {
private static final Map<Integer, MethodBeanDefinition> methodPool;
static {
methodPool = new HashMap<Integer, MethodBeanDefinition>();
}
public RmiImplClassFactory() {
}
public static void initRmi(String interfaceConfigXml) throws Exception {
new XMLParser() {
@Override
public void dealElement(Element element, int index) throws Exception {
String interfaceName = element.getAttribute("name");
String className = element.getAttribute("class");
Class<?> interfaze = Class.forName(interfaceName);
Class<?> klass = Class.forName(className);
Object object = klass.newInstance();
Method[] methodsInInterface = interfaze.getDeclaredMethods();
for (Method methodInInterface : methodsInInterface) {
int methodKey = methodInInterface.toString().hashCode();
String methodName = methodInInterface.getName();
Parameter[] parameters = methodInInterface.getParameters();
Class<?>[] parameterTypes = new Class<?>[parameters.length];
int i = 0;
for (Parameter parameter : parameters) {
Class<?> parameterType = parameter.getType();
parameterTypes[i++] = parameterType;
}
Method method = klass.getDeclaredMethod(methodName, parameterTypes);
MethodBeanDefinition bean = new MethodBeanDefinition();
bean.setObject(object);
bean.setMethod(method);
methodPool.put(methodKey, bean);
}
}
}.parse(XMLParser.getDocument(interfaceConfigXml), "interface");
}
static MethodBeanDefinition getMethodBean(int methodKey) {
return methodPool.get(methodKey);
}
}
这里涉及到对 XML 文件的解析,具体的工具实现附在这里:HB 个人博客:https://blog.csdn.net/SwaggerHB/article/details/129969784
RMI 服务器端的具体实现
对外提供的主要API,包括对服务器的设置,以及侦听客户端连接,启动 RequestDealer 去处理客户端连接请求。
package com.mec.rmi.core;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import com.mec.util.IListener;
import com.mec.util.ISpeaker;
public class RmiServer implements Runnable, ISpeaker {
private int port;
private ServerSocket server;
private volatile boolean goon;
private Object lock;
private List<IListener> listenerList;
public RmiServer() {
this.listenerList = new ArrayList<IListener>();
this.lock = new Object();
this.port = IAddressManager.DEFAULT_PORT;
this.goon = false;
}
public boolean isStartup() {
return this.goon;
}
public void startup() throws IOException {
if (this.goon) {
publish("RMI服务器已启动!");
return;
}
publish("开始启动RMI服务器……");
this.server = new ServerSocket(this.port);
publish("RMI服务器启动成功!");
synchronized (this.lock) {
this.goon = true;
new Thread(this).start();
try {
this.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void shutdown() {
if (!this.goon) {
publish("RMI服务器尚未启动!");
return;
}
closeServer();
publish("RMI服务器已宕机!");
}
private void closeServer() {
this.goon = false;
try {
if (this.server != null && !this.server.isClosed()) {
this.server.close();
}
} catch (IOException e) {
} finally {
this.server = null;
}
}
@Override
public void run() {
synchronized (this.lock) {
this.lock.notify();
}
publish("开始侦听客户端连接请求……");
while (this.goon) {
try {
Socket client = server.accept();
new RequestDealer(client);
} catch (IOException e) {
this.goon = false;
}
}
closeServer();
}
public void setPort(int port) {
this.port = port;
}
@Override
public void addListener(IListener listener) {
if (!this.listenerList.contains(listener)) {
this.listenerList.add(listener);
}
}
@Override
public void removeListener(IListener listener) {
if (this.listenerList.contains(listener)) {
this.listenerList.remove(listener);
}
}
@Override
public void publish(String message) {
for (IListener listener : this.listenerList) {
listener.acceptMessage(message);
}
}
}
RMI 客户端的具体实现
RMI 客户端对外提供的主要 API,只需要用户使用这个类得到相应的代理,便可以使用这个代理远端执行已经扫描填充好的方法。doRemoteInvoker 方法是内部的,里面有和服务器的连接,以及传递和接受对端信息。
package com.mec.rmi.core;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.Socket;
import javax.swing.JFrame;
import com.mec.util.ArgumentMaker;
import com.mec.util.io.NodeAddress;
public class RmiClient {
private JFrame parentView;
private String tooltip;
private Object result;
private IAddressManager nodeAddressManager;
public RmiClient() {
this.nodeAddressManager = new ServerAddress();
}
public void setServerAddress(String ip, int port) {
this.nodeAddressManager.setAddress(new NodeAddress(ip, port));
}
public void setModalDialog(JFrame parentView, String tooltip) {
this.parentView = parentView;
this.tooltip = tooltip;
}
public void setAddressManager(IAddressManager nodeAddressManager) {
this.nodeAddressManager = nodeAddressManager;
}
Object getResult() {
return this.result;
}
void doRemoteInvoker(Method method, Object[] args) throws Throwable {
if (this.parentView != null) {
ModalDialog modalDialog = new ModalDialog(this.parentView, this.tooltip);
modalDialog.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
try {
result = doMethod(method, args);
modalDialog.closeDialog();
} catch (Throwable e1) {
}
}
});
modalDialog.showDialog();
} else {
this.result = doMethod(method, args);
}
}
private Object doMethod(Method method, Object[] args) throws Throwable {
NodeAddress serverAddress = this.nodeAddressManager.getAddress();
String ip = serverAddress.getIp();
int port = serverAddress.getPort();
Socket socket = new Socket(ip, port);
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(String.valueOf(method.toString().hashCode()));
dos.writeUTF(argsToString(args));
try {
if (method.getReturnType().equals(void.class)) {
return null;
}
String resultString = dis.readUTF();
RmiResult rmiResult = ArgumentMaker.gson.fromJson(resultString, RmiResult.class);
if (rmiResult.isOk()) {
String result = rmiResult.getResString();
Type returnType = method.getGenericReturnType();
Object res = ArgumentMaker.gson.fromJson(result, returnType);
return res;
} else {
throw new Exception(rmiResult.getResString());
}
} catch (Exception e) {
throw e;
} finally {
dis.close();
dos.close();
socket.close();
}
}
private String argsToString(Object[] args) {
int argCount = args.length;
if (argCount <= 0) {
return "";
}
ArgumentMaker maker = new ArgumentMaker();
for (int index = 0; index < argCount; index++) {
maker.add("arg" + index, args[index]);
}
return maker.toString();
}
}
这里涉及到参数进行传递和恢复的工具 ArugumentMaker 的实现,具体的工具实现过程以及代码附在这里:HB 个人博客:基于 Gson 的 ArgumentMaker 工具实现