概述
RPC是一个网际协议,用面向对象的方法实现RPC就是RMI。
RMI:远程方法调用
客户端提供给用户一个API,当用户调用其中某个方法时,客户端把要调用的方法和参数发送到服务器端,服务器根据这个方法名称在服务器端匹配到对应的对象,并且执行对应的方法。再把这个结果返回给客户端。
只有服务器端才真正存在拥有这个方法的对象,中间的网络通信过程用户是不知道的,用户就感觉是调用了一个本地方法而已。这样做的好处就是减轻了服务器的负担。
实现思路
客户端的流程
这个接口中的方法是用户需要调用的方法,真正调用方法的肯定是一个继承这个接口的真实对象,这个真实对象只存在于服务器端。但是为了提供给客户端调用,所以我们通过代理的方式,产生一个此接口的代理对象,为了供用户调用。
//用户需要调用的方法
public interface IDoSomething {
public String doSomething(int a, int b) throws Exception;
}
//通过JDK代理返回一个接口的代理对象,可以看我之前写的代理博客
private <T> T interfaceJDKProxy(Class<?> klass) {
ClassLoader classLoader = klass.getClassLoader();
Class<?>[] interfaces = new Class[]{klass};
return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = JDKProxy.this.methodInvoker.invoke((Object)null, method, args);
return res;
}
});
}
上述的methodInvoker是一个接口,接口中有一个invoke方法,是这个代理类执行某个代理方法时,具体的方法调用流程。
这里用到了很牛逼的思想,面向接口编程。对象是接口类型,在具体需要的时候,set进去一个对应的接口实现类,这就实现了滞后。这也是框架中运用很多的思想。
那么根据我们的需求,这个代理对象执行方法时,要把所要执行的方法和对应参数发送到服务器,并且接受服务器执行结果。所以,我们自己编写的methodInvoke就要完成与服务器通信的部分
下一部分是真正提供给用户产生代理对象的方法,需要传入一个接口类型。其中setMethodInvoker就完成了和服务器的通信任务。我们把方法的全名先发送过去,然后在把参数封装到一个类中,发送这个类的gson字符串,服务器端只需要按照已经定好的规则来解析。
public <T> T getProxy(Class<?> klass) throws InstantiationException, IllegalAccessException
, MethodInvokerNotSetException {
this.startup();
mecProxy.setMethodInvoker(new IMethodInvoker() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
Object result = null;
DataOutputStream dos = null;
DataInputStream dis = null;
try {
dos = new DataOutputStream(socket.getOutputStream());
dis = new DataInputStream(socket.getInputStream());
//先发送方法的全名
dos.writeUTF(method.toString());
//在把参数包装在ArgumentMaker内发送其Gson字符串
ArgumentMaker argumentMaker = new ArgumentMaker();
Class<?>[] parameterTypes = method.getParameterTypes();
for(int i = 0; i < parameterTypes.length;i++){
argumentMaker.add("arg"+i, args[i]);
}
dos.writeUTF(argumentMaker.toString());
Type type = method.getReturnType();
String res = dis.readUTF();
result = RMIServer.gson.fromJson(res, type);
} catch (IOException e) {
if(dos != null){
try {
dos.close();
} catch (IOException ex) {
}
}
if(dis != null){
try {
dis.close();
} catch (IOException ex) {
}
}
if(socket != null){
try {
socket.close();
} catch (IOException ex) {
}
}
}
return result;
}
});
return mecProxy.getProxy(klass);
}
服务器端的大概流程
服务器端对于每一个客户端的解析程序。
解析方法的全名和参数,得到这个方法所在的类,方法的名称以及参数的类型。
方法所在的类必定是这个接口,所以我们还需要在服务器端匹配一个对应关系。就是该接口和具体实现类的对应关系,写在xml中并对其提前解析。然后根据所找到的类,再通过方法找到具体的类中方法,反射调用执行,结果再返回。
//接口和实现类对应关系,在客户端调用之前,就解析这个xml文件,把这个对应关系通过一个map存放好
<?xml version="1.0" encoding="UTF-8" ?>
<RMIMapping>
<Mapping interface="rmi.Main.IDoSomething" impl="rmi.Main.DoSomethingImpl"></Mapping>
</RMIMapping>
public class RMIService implements Runnable{
private Socket socket;
public RMIService(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//获得方法的全名
String fullmethod = dis.readUTF();
//参数封装的gson字符串
String arguGson = dis.readUTF();
//MethodInformation 是对方法的一个封装,包括方法所在类,方法名,变量的类型(暂时用String来表示类型)
MethodInformation methodInformation = new MethodInformation(fullmethod);
RMIDefinition definition = RMIFactory.getDefinition(methodInformation.getClassName());
String[] argTypes = methodInformation.getArgTypes();
//获得变量类型
Class<?>[] parameterTypes = getParameterTypes(argTypes);
//获得变量具体的值
Object[] argValues = getArgValues(arguGson, parameterTypes);
Class klass = definition.getKlass();
Object obj = definition.getObj();
Method method = klass.getDeclaredMethod(methodInformation.getMethodName(),parameterTypes);
//反射调用对应方法
Object res = method.invoke(obj, argValues);
Type returnType = method.getGenericReturnType();
//返回执行结果
dos.writeUTF(RMIServer.gson.toJson(res,returnType));
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private Class<?>[] getParameterTypes(String[] argTypes){
if(argTypes == null || argTypes.length <= 0){
return new Class[]{};
}
Class<?>[] types = new Class[argTypes.length];
for(int i = 0;i < argTypes.length;i++){
types[i] = MecType.toType(argTypes[i]);
}
return types;
}
private Object[] getArgValues(String arguGson, Class<?>[] parameterTypes){
Object[] args = new Object[parameterTypes.length];
ArgumentMaker argumentMaker = new ArgumentMaker(arguGson);
for (int i = 0;i < args.length;i++) {
args[i] = argumentMaker.getValue("arg"+i,parameterTypes[i]);
}
return args;
}
Server完整代码
用到了线程池,对于每一个解析程序都用一个线程new RMIService()完成。RMIService便是上面解析和返回的过程。
package rmi.core;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class RMIServer implements Runnable{
public static int DEFAULT_PORT = 54187;
public static Gson gson = new GsonBuilder().create();
private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
10, 120,
5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
private ServerSocket serverSocket;
private int port;
private volatile boolean goon;
public RMIServer() {
this.port = RMIServer.DEFAULT_PORT;
this.goon = false;
}
public RMIServer(int port) {
this();
this.port = port;
}
public void startup(){
try {
if(null == this.serverSocket && !this.goon) {
this.serverSocket = new ServerSocket(port);
this.goon = true;
new Thread(this,"RMIServer").start();
}
} catch (IOException e) {
}
}
public void shutdown(){
try {
if(this.serverSocket != null && this.goon) {
this.serverSocket.close();
this.goon = false;
this.serverSocket = null;
}
} catch (IOException e) {
this.serverSocket = null;
}
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
@Override
public void run() {
while(this.goon){
try {
Socket client = this.serverSocket.accept();
threadPool.execute(new RMIService(client));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端完整代码
package rmi.core;
import com.mec.util.ArgumentMaker;
import com.mec.util.proxy.IMethodInvoker;
import com.mec.util.proxy.MecProxy;
import com.mec.util.proxy.MethodInvokerNotSetException;
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;
public class RMIClient {
public static String DEFAULT_IP = "127.0.0.1";
public static int PORT = 54187;
private Socket socket;
private String ip;
private int port;
private MecProxy mecProxy;
public RMIClient() {
this.mecProxy = new MecProxy();
this.ip = RMIClient.DEFAULT_IP;
this.port = RMIClient.PORT;
}
public RMIClient(String ip, int port){
this();
this.ip = ip;
this.port = port;
}
/**
* 获取远程代理对象
* @param klass 要代理的接口
* @param <T>
* @return
* @throws InstantiationException
* @throws IllegalAccessException
* @throws MethodInvokerNotSetException
*/
public <T> T getProxy(Class<?> klass) throws InstantiationException, IllegalAccessException
, MethodInvokerNotSetException {
this.startup();
mecProxy.setMethodInvoker(new IMethodInvoker() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
Object result = null;
DataOutputStream dos = null;
DataInputStream dis = null;
try {
dos = new DataOutputStream(socket.getOutputStream());
dis = new DataInputStream(socket.getInputStream());
//先发送方法的全名
dos.writeUTF(method.toString());
//在把参数包装在ArgumentMaker内发送其Gson字符串
ArgumentMaker argumentMaker = new ArgumentMaker();
Class<?>[] parameterTypes = method.getParameterTypes();
for(int i = 0; i < parameterTypes.length;i++){
argumentMaker.add("arg"+i, args[i]);
}
dos.writeUTF(argumentMaker.toString());
Type type = method.getReturnType();
String res = dis.readUTF();
result = RMIServer.gson.fromJson(res, type);
} catch (IOException e) {
if(dos != null){
try {
dos.close();
} catch (IOException ex) {
}
}
if(dis != null){
try {
dis.close();
} catch (IOException ex) {
}
}
if(socket != null){
try {
socket.close();
} catch (IOException ex) {
}
}
}
return result;
}
});
return mecProxy.getProxy(klass);
}
private void startup(){
try {
if(null == this.socket) {
this.socket = new Socket(ip, port);
}
} catch (IOException e) {
this.socket = null;
}
}
private void shutdown(){
try {
this.socket.close();
this.socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
public String getIp() {
return ip;
}
public int getPort() {
return port;
}
public void setIp(String ip) {
this.ip = ip;
}
public void setPort(int port) {
this.port = port;
}
}
框架怎么用
- 在服务器端配置接口对应的实现类(xml文件中)
- 服务器设置port,启动服务器
- 客户端配置服务器的ip和port
- 通过getProxy()方法,传入接口类型,获得代理类
- 调用代理类其中的方法
public class TestForClient {
public static void main(String[] args) {
RMIClient client = new RMIClient();
client.setIp("localhost");
client.setPort(54185);
try {
IDoSomething proxy = client.getProxy(IDoSomething.class);
String res = proxy.doSomething(1,2);
System.out.println(res);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (MethodInvokerNotSetException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class TestForServer {
public static void main(String[] args) {
RMIServer server = new RMIServer();
RMIFactory.scanMapping("/RMIMapping.xml");
server.setPort(54185);
server.startup();
}
}
和java自带框架的区别
基本看了一下java实现的RMI,是根据一个约定的字符串获得服务器上真是存在的对象引用,然后调用这个对象中的方法。比我自己完成的,省去了参数传递和解析的过程。但是感觉内部还是会去传递参数,因为是俩个不同的jvm上的。对象都是不一样的,没有深究,只是猜测。