RMI技术 ----(Remote Methed Invoke 远程方法调用)
在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、等待服务器返回执行执行结果;这个结果可以看成“响应”。
第二条我们用代理模式处理。
客户端这边只需要存在接口,用接口.class构造出一个接口代理对象出来,然后调用接口代理对象的对应方法。
被调用的方法实际上并没有完成任何有效的操作,只是单纯地连接服务器 并把要执行的方法(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类中有三个成员:
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;
}
}