【Java】RPC与RMI框架

概念

RMI概念

RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制。使用这种机制,某一台计算机上的对象可以调用另外 一台计算机上的对象来获取远程数据。RMI是Enterprise JavaBeans的支柱,是建立分布式Java应用程序的方便途径。在过去,TCP/IP套接字通讯是远程通讯的主要手段,但此开发方式没有使用面向对 象的方式实现开发,在开发一个如此的通讯机制时往往令程序员感觉到乏味,对此RPC(Remote Procedure Call)应运而生,它使程序员更容易地调用远程程序,但在面对复杂的信息传讯时,RPC依然未能很好的支持,而且RPC未能做到面向对象调用的开发模 式。针对RPC服务遗留的问题,RMI出现在世人面前,它被设计成一种面向对象的通讯方式,允许程序员使用远程对象来实现通信,并且支持多线程的服务,这 是一次远程通讯的革命,为远程通信开辟新的里程碑。

RPC概念

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底>层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络>通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发>送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为>止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户>端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
有多种 RPC模式和执行。最初由 Sun 公司提出。IETF ONC 宪章重新修订了 Sun 版本,使得 ONC RPC 协议成为 >IETF 标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算环境(DCE)。

RMI框架

网络上很多RMI框架的实现都不同,而博主这个使用的是大家较少用的使用json技术来实现的RMI框架;在服务器端保存接口和接口实现类,是通过注解的方式将实现类的对象和方法注册在容器中,当客户端远程调用方法时,将执行的结果返回给客户端。在客户端只需要保存接口方法而不需要接口的实现类,通过代理机制得到接口的实现类对象,在方法拦截中实现远程服务器的连接、参数的传递及其结果的接收,当接收完成后断开与服务器的连接。

接口方法及接口实现对象的注册及其方法的调用

服务器端接口实现类和方法的注册是RMI框架形成的前提条件,注册方式可以采用注解或者文件配置,而本框架当中博主采用的是注解扫描的方式,当服务器端启动时,先采用包扫描的方式对带有注解的接口实现类进行扫描,注解是该类所实现的接口类型的数组,然后将数组中的每个接口方法进行扫描,找到每个接口方法的实现方法,形成一一映射的关系,将每个映射关系形成一个RPCDefinition,以这个形成的类为value,以该接口方法的完整名的hashCode为key保存在一个Map当中。
本框架当中博主在客户端采用的是直接将接口方法的hashCode发送给服务器端,然后服务器端通过在Map中查找对应的的RPCDefintion,而RPCDefintion由class、object和method组成,直接可以进行方法的调用,为远程调用大大节省了时间,也就是刚开始进行包扫描进行注册时用的时间长一点

在这里插入图片描述

RPCFactory

package com.wh.server.core;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.mec.util.PackageScanner;
import com.wh.annotation.Scanning;

/**
 * RPC加工类,用于生成以接口方法的hashCode为键,RPCDefinition为值的Map
 * ,并从中获得指定的RPCDefinition
 * @author 闹闹小丫头
 *
 */
public class RPCFactory {
	// 以接口方法的hashCode为键,RPCDefinition为值的静态Map
	public static final Map<String, RPCDefinition> rpcMap;

	static{
		rpcMap = new HashMap<>();
	}
	
	/**
 	* 无参构造
 	*/
	public RPCFactory() {
	}

	/**
	 * 扫描指定包下的所有类
	 * @param packagePath
	 */
	public void scanerBeanByPackage(String packagePath) {
		PackageScanner scanner = new PackageScanner() {
			// 需要覆盖的方法dealClass,对类进行判断处理
			@Override
			public void dealClass(Class<?> klass) {
				if(klass.isPrimitive()
						|| klass.isInterface()
						|| klass.isAnnotation()
						|| klass.isEnum()
						|| klass.isArray()
						|| !klass.isAnnotationPresent(Scanning.class)) {
					return;
				}
				Scanning scann = klass.getAnnotation(Scanning.class);
				Class<?>[] interfaces = scann.klass();
				for(Class<?> aInterface : interfaces) {
					try {
						register(klass, aInterface);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		};
		scanner.scannerPackage(packagePath);
	}


	/**
	 * 根据指定类,指定接口和指定对象进行注册,将正确的放入Map中
	 * @param klass 指定类 
	 * @param interfaces 指定接口
	 * @param object 指定对象
	 * @return 返回这个类实例
	 * @throws Exception
	 */
	public RPCFactory register(Class<?> klass, Class<?> interfaces, Object object) throws Exception{
		if(!interfaces.isInterface()) {
			System.out.println("[" + interfaces.getName() + "]不是接口!");
		}
		if(!interfaces.isAssignableFrom(klass)) {
			System.out.println("类[" + klass.getName() + "]不是接口[" + interfaces.getName() + "]的实现类!");
		}
		Object obj = object == null ? klass.newInstance() : object;
	
		Method[] interfacesMethods = interfaces.getDeclaredMethods();
		for(Method interfaceMethod : interfacesMethods) {
			String methodName = interfaceMethod.getName();
			Class<?>[] parasType = interfaceMethod.getParameterTypes();
			Method method = klass.getMethod(methodName, parasType);
			String rpcName = String.valueOf(interfaceMethod.toString().hashCode());
			RPCDefinition rpcData = new RPCDefinition();
			rpcData.setKlass(klass);
			rpcData.setMethod(method);
			rpcData.setObject(obj);
		
			rpcMap.put(rpcName, rpcData);
		}
	
		return this;
	} 

	/**
	 * 根据指定类和接口将其进行注册
	 * @param klass 指定类
	 * @param interfaces 指定接口
	 * @return 返回这个类的实例
	 * @throws Exception
	 */
	public RPCFactory register(Class<?> klass, Class<?> interfaces) throws Exception {
		return register(klass, interfaces, null);
	}

	/**
	 * 根据指定接口和指定对象进行注册
	 * @param interfaces 指定接口
	 * @param object 指定对象
	 * @return 返回这个类的实例
	 * @throws Exception
	 */
	public RPCFactory register(Class<?> interfaces, Object object) throws Exception {
		return register(object.getClass(), interfaces, object);
	}

	/**
	 * 从Map中获得以指定id为键的RPCDefinition
	 * @param id 指定id
	 * @return
	 */
	RPCDefinition getRPCDefinition(String id) {
		return rpcMap.get(id);
	}

}



RPCDefinition

package com.wh.server.core;

import java.lang.reflect.Method;

/**
 * RPC定义基本结构类,用于RPCFactory的组装
 * 包含类类型,类的对象以及方法
 * @author 闹闹小丫头
 *
 */
public class RPCDefinition {
	private Class<?> klass;
	private Object object;
	private Method method;
	
	public RPCDefinition() {
	}

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

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

	Object getObject() {
		return object;
	}

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

	Method getMethod() {
		return method;
	}

	void setMethod(Method method) {
		this.method = method;
	}

	@Override
	public String toString() {
		return "类:" + klass + "\n对象:" + object + "\n方法:" + method + "";
	}
}

注解Scanning

package com.wh.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;

/**
 * 注解,进行扫描时使用
 * @author 闹闹小丫头
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface Scanning {
	// 接口
	Class<?>[] klass();
}

服务器端

package com.wh.server.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 java.util.Map;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * RPC执行类,继承了Runnable
 * @author 闹闹小丫头
 *
 */
public class RPCInvoker implements Runnable{
	private static final Type type = new TypeToken<Map<String, String>>() {}.getType();
	
	private Socket socket;
	private DataInputStream dis;
	private DataOutputStream dos;
	private Gson gson;
	
	/**
	 * 双参构造
	 * @param socket 指定socket
	 * @param gson 指定gson
	 */
	public RPCInvoker(Socket socket, Gson gson) {
		this.socket = socket;
		this.gson = gson;
		try {
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void setSocket(Socket socket) {
		this.socket = socket;
	}


	public void setGson(Gson gson) {
		this.gson = gson;
	}

    /**
     * 将指定参数字符串根据指定参数类型数组转化为指定参数对象
     * @param paraString 指定参数字符串
     * @param paraTypes 指定参数类型数组
     * @return
     */
	private Object[] getParas(String paraString, Class<?>[] paraTypes) {
		// 将指定参数字符串转化为Map
		Map<String, String> paraStringMap = gson.fromJson(paraString, type);
		int paraCount = paraStringMap.size();
		if(paraCount <= 0) {
			return new Object[] {};
		}
		Object[] paras = new Object[paraCount];
		// 遍历整个Map,将Map中的参数一个一个解析出来
		for(int index = 0; index < paraStringMap.size(); index++) {
			String key = "arg" + index;
			String value = paraStringMap.get(key);
			paras[index] = gson.fromJson(value, paraTypes[index]);
		}
		
		return paras;
	}
	

	@Override
	public void run() {
		try {
			// 获得方法的hashCode值
			String methodId = dis.readUTF();
			// 获得参数字符串
			String paraString =dis.readUTF();
			
			// 从RPC工厂中获得指定方法的RPCDefinition
			RPCDefinition rpcDefinition = new RPCFactory().getRPCDefinition(methodId);
			if(rpcDefinition == null) {
				System.out.println(methodId + "未找到匹配的类!");
			}
			Object object = rpcDefinition.getObject();
			Method method = rpcDefinition.getMethod();
			
			Class<?>[] paraTypes = method.getParameterTypes();
			// 通过获得的参数字符串得到指定的参数类型
			Object[] paras = getParas(paraString, paraTypes);
			// 执行该方法并获得返回值
			Object result = method.invoke(object, paras);
			
			// 将返回值通过输出通信信道发送给对端服务器
			dos.writeUTF(gson.toJson(result));
		} catch (IOException 
				| IllegalAccessException 
				| IllegalArgumentException 
				| InvocationTargetException e) {
			e.printStackTrace();
		} finally {
			try {
				// 无论如何最后关闭socket
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}



package com.wh.server.core;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.ThreadPoolExecutor;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mec.util.PropertiesParser;
import com.wh.threadpool.MThreadPool;

/**
 * RPC服务器类,继承了Runnable
 * @author 闹闹小丫头
 *
 */
public class RPCServer implements Runnable{
	// 服务器默认的端口
	private static final int DEFAULT_SERER_PORT = 51778;
	// gson
	private static final Gson gson = new GsonBuilder().create();
	
	// RPC服务器
	private ServerSocket serverSocket;
	// RPC服务器端口
	private int port;
	private volatile boolean goon;
	
	// 线程池类
	private MThreadPool mThreadPool;
	private ThreadPoolExecutor threadPool;
	
	public RPCServer() {
		this.port = DEFAULT_SERER_PORT;
		readConfig("/RPCUserConfig.properties");
	}
	
	public void setPort(int port) {
		this.port = port;
	}
	
	/**
	 * 读取指定路径下的配置文件
	 * @param configPath  配置文件的指定路线
	 */
	public void readConfig(String configPath)  {
		try {
			PropertiesParser.loadProperties(configPath);
			
			String portStr = PropertiesParser.value("RPCPort");
			if(portStr != null && !portStr.equals("")) {
				int port = Integer.valueOf(portStr);
				if (port > 0 && port < 65536) {
					this.port = port;
				}
			}
			
		} catch (Exception e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		
	}
    
	/**
	 * 启动RPC服务器
	 */
	public void startup() {
		try {
			if(serverSocket != null) {
				return;
			}
			serverSocket = new ServerSocket(port);
			goon = true;
			mThreadPool = new MThreadPool();
			threadPool = mThreadPool.newInstance(false);
			new Thread(this, "RPC_SERVER").start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 强制关闭RPC服务器
	 */
	public void shutdown() {
		if(goon == false) {
			return;
		}
		goon =false;
		if(serverSocket != null) {
			try {
				if(!serverSocket.isClosed()) {
					serverSocket.close();
					threadPool.shutdown();
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				serverSocket = null;
			}
		}
	}
	
	/**
	 * 判断服务器是否已启动
	 * @return
	 */
	public boolean isStartup() {
		return goon;
	}
	
	@Override
	public void run() {
		while(goon) {
			try {
				Socket client = serverSocket.accept();
				// 将侦听到的客户端放入线程池中
				threadPool.execute(new RPCInvoker(client, gson));
			} catch (IOException e) {
				if(goon == true) {
					goon = false;
				}
			}
		}
	}
	
}



package com.mec.util;

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

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class ArgumentMaker {
	private Map<String, String> paraMap;
	private static final Gson gson = new GsonBuilder().create();
	private int paraIndex;
	
	public ArgumentMaker() {
		this.paraIndex = 0;
		paraMap = new HashMap<>();
	}
	
	public ArgumentMaker addArg(String paraName, Object paraValue) {
		paraMap.put(paraName, gson.toJson(paraValue));
		return this;
	}
	
	public ArgumentMaker addArg(Object paraValue) {
		paraMap.put("arg" + paraIndex++, gson.toJson(paraValue));
		return this;
	}
	
	@Override
	public String toString() {
		return gson.toJson(paraMap);
	}
	
}

客户端

package com.wh.client.core;

/**
 * 客户端代理类
 * @author 闹闹小丫头
 *
 */
public class ClientProxy {
	// 短连接客户端
	private RPCClient client;
	
	/**
	 * 无参构造
	 */
	public ClientProxy() {
		client = new RPCClient();
	}
	
	public void setRPCClient(RPCClient rpcClient) {
		this.client = rpcClient;
	}
	
	public ClientProxy(String serverIp, int serverPort) {
		client = new RPCClient(serverIp, serverPort);
	}
	
	public ClientProxy(RPCClient client) { 
		this.client = client;
	}
	
	/**
	 * 获得指定接口的代理
	 * @param klass 指定接口
	 * @return 返回指定接口的代理
	 */
	public <T> T jdkProxy(Class<?> klass) {
		return client.getProxy(klass);
	}
	
	
}

package com.wh.client.core;

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

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mec.util.ArgumentMaker;

/**
 * 短连接客户端
 * @author 闹闹小丫头
 *
 */
public class RPCClient {
	public static final String DEFAULT_SERVER_IP = "192.168.1.29";
	public static final int DEFAULT_SERVER_PORT = 51778;
	private static final Gson gson = new GsonBuilder().create();
	
	private String serverIp;
	private int serverPort;
	private DataInputStream dis;
	private DataOutputStream dos;
	
	/**
	 * 无参构造,需要连接的注册中心服务器使用默认ip和port
	 */
	public RPCClient() {
		this.serverIp = DEFAULT_SERVER_IP;
		this.serverPort = DEFAULT_SERVER_PORT;
	}
	
	/**
	 * 双参构造,需要连接的注册中心服务器使用指定IP和port
	 * @param serverIp 指定IP
	 * @param serverPort 指定port
	 */
	public RPCClient(String serverIp, int serverPort) {
		this.serverIp = serverIp;
		this.serverPort = serverPort;
	}
	
	public void setServerIp(String serverIp) {
		this.serverIp = serverIp;
	}

	public void setServerPort(int serverPort) {
		this.serverPort = serverPort;
	}

	/**
	 * 将指定数组参数转化为gson字符串进行返回
	 * @param args 指定数组参数
	 * @return 返回gson字符串
	 */
	private String parasToGson(Object args[]) {
		ArgumentMaker am = new ArgumentMaker();
		
		for(Object arg : args) {
			am.addArg(arg);
		}
		
		return am.toString();
	}
	
	/**
	 * 远程短连接调用方法
	 * @param interfaces 接口类型
	 * @param method 方法名称
	 * @param args 数组参数
	 * @return 返回执行结果,返回值类型为方法返回值类型
	 */
	private Object invoker(Class<?> interfaces, Method method, Object args[]) {
		Object result = null;
		try {
			Socket socket = new Socket(serverIp, serverPort);
			dis = new DataInputStream(socket.getInputStream());
			dos = new DataOutputStream(socket.getOutputStream());
			
			dos.writeUTF(String.valueOf(method.toString().hashCode()));
			dos.writeUTF(parasToGson(args));
			
			String resString =  dis.readUTF();
			result = gson.fromJson(resString, method.getReturnType());
			
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return result;
	}
	
	/**
	 * 获得指定接口的代理
	 * @param interfaces 指定接口
	 * @return 返回指定接口的代理
	 */
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> interfaces) {
		return (T)Proxy.newProxyInstance(interfaces.getClassLoader(), 
				new Class<?>[] {interfaces}, new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						// 进行方法拦截
						return invoker(interfaces, method, args);
					}
				});
	}
	
}



总结

整套RPC与RMI框架用户在使用的过程当中只需要在要使用的接口实现类上加上注解,就可以远程调用方法,无需注意內部的逻辑问题,就可以直接使用里面的服务,这就是面向对象的设计过程。短连接的优点在于使用时建立连接,不用时断开连接,不过多的占用服务器的通信信道,大大减少了服务器的服务时间;但他的弊端在于如果短时间内客户端需要大量的远程调用方法就需要不断地与服务器连接断开,这样重复的操作无疑浪费了大量的时间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值