其中的核心思想或步骤是:通过网络通信,一个网络端调用另一个网络端的方法(过程),实现相对应的功能,得到相应的结果。RMI即JAVA中RPC的实现。“invocation”意为“反射”,也就是说明利用了java中的反射机制实现的。
从应用角度来分析,客户端通过一定方式调用代理对象的方法,得到了方法执行的结果。而内部是在经过代理机制拦截到相关方法之后,其参数和相关方法信息被发送至RMI服务器,服务器端执行了这个方法。然后再将执行结果发送至客户端。客户端再将返回值返回。即为同步调用。
——客户端
——服务器端
——方法工厂
——注册过程
本文是针对于想法进行简单实现,如有意见或争议可在评论区向作者提出。
原创文章,转载复制请注明出处。
——针对RPC客户端,设计一个代理模式。表面上用户是通过得到的代理对象直接执行了相关方法,实际上通过代理对象拦截到相关方法时,将执行方法所需要的相关信息(方法及其参数信息)通过网络通信发送给RPC服务器端(连接上服务器),而在RPC服务器端根据客户端所发来的相关信息真正执行这个方法,再将返回值再发送给客户端,RPC服务完成,断开与服务器之间的连接(面向服务的短链接)。
以实现功能为主,本次客户端设计的代理模式使用了JDK代理模式。所以支持RPC模式的方法,将是定义在接口中定义的方法。
//RpcClient类
public T getProxy(Class> klass) throws Exception;
而在代理对象拦截到方法时,我们在新产生的InvocationHandler对象覆盖的invoke()方法中,连接Rpc服务器,将方法、以及参数发送至Rpc服务器端并等待他返回的结果。考虑到代码的解耦,这个过程我们封装在类RpcClientExecutor中。
public class RpcClientExecutor {
private String rpcServerIp;//rpc服务器的信息
private int rpcServerPort;
private Gson gson;//网络信息均使用Gson格式的字符串进行发送
private Class> returnType;//方法的返回值,用于得到结果的Gson转换
//相关set方法与get方法
T rpcExecutor(String rpcBeanId, Object[] para) throws Exception();
//在此方法中实现与Rpc服务器的通讯过程
//para为代理对象拦截到的参数数组
//rpcBeanId
//方法返回值为Gson转换回来的从服务器得到的结果
至于rpcBeanId代表什么意思,在叙述完大框架再详细说明。
——针对RPC服务器端,在启动服务器后,实时侦听客户端的链接请求。每次接收到一个请求时,为此客户端派发一个RPC服务线程与其交互。也就是说,服务器端的侦听线程是持续运行的,客户端的连接请求是并发的。
//服务器派发的服务线程
public class RpcServerExecutor implements Runnable {
private Socket socket;//对端
private DataInputStream dis;//通信信道
private DataOutputStream dos;
private RpcServer rpcServer;//Rpc服务器对象
private Gson gson;
//相关带参构造等
public void run() {
//接收RPC客户端传递的rpcBeanId和参数;
// 定位相关类、对象和方法;
// 执行RPC客户端要求执行的方法;
// 向RPC客户端返回执行结果。
// 服务完成,关闭相关通信信道
}
//RpcServer即Rpc服务器
public class RpcServer implements Runnable {
private ServerSocket server;
private int port;
private volatile boolean goon;
private final RpcBeanFactory rpcBeanFactory;//Rpc方法工厂
private static long executorId;//区分派发服务线程的ID
//相关方法
@Override
public void run() {
while (goon) {
try {
Socket rpcClient = server.accept();
new RpcServerExecutor(this, rpcClient, ++executorId);
} catch (Exception e) {
goon = false;
}
}
stopRpcServer();
}
——既然在服务器端是通过反射机制去调用的,那么最最容易想到的方法就是:
客户端将方法名、所存在的接口、参数数组发送给服务器端;
服务器端先从接口定位到其实现类,再使用实现类对象和相关参数执行相关方法;
再将方法执行的结果回送给客户端。
这样确实能实现,但实属笨重,并且不太容易实现。仔细考虑,既然我们Rpc服务器端能执行代理拦截到的方法,那就一定拥有这个方法所在的接口的实现类。我们的RPC是基于网络通信方式进行运作的。对于每次信息的发送,都是要消耗“奢侈的”网络资源的。
所以我们要使用珍贵的网络资源,尽量发送尽可能少的数据,完成相对应的功能。
所以我们给出RpcBeanDefnation与RpcBeanFactory。
public class RpcBeanDefination {
private Class> klass;//实现接口
private Method method;//
private Object object;//实现类对象
RpcBeanDefination() {
}
//相关get与set方法
}
public class RpcBeanFactory {
private final Map rpcBeanMap;
//其中Map中的键String存放了接口中每个方法的唯一识别码
//识别码是每个相对应Method对象HashCode的字符串,即method.toString().hashCode();
RpcBeanFactory() {
rpcBeanMap = new HashMap<>();
}
void rpcBeanRegistry(String rpcBeanId, RpcBeanDefination rpcBeanDefination) {
RpcBeanDefination rbd = rpcBeanMap.get(rpcBeanId);
if (rbd != null) {
return;
}
rpcBeanMap.put(rpcBeanId, rpcBeanDefination);
}
RpcBeanDefination getRpcBean(String rpcBeanId) {
return rpcBeanMap.get(rpcBeanId);
}
}
我们在RpcServer启动时,现在其RpcBeanFactory中对要进行Rpc服务的接口及其实现类进行注册(rpcBeanRegistry方法);
其中注册过程:
得到接口中的每个方法,计算出他的识别码;
再通过实现类得到执行对象,构建出其RpcBeanDefination 对象;
以识别码为键,以其RpcBeanDefination 对象为值放入RpcBeanFactory中。
method.toString()包含方法所存在的包、类、名字、参数个数。所以不会存在重复的可能。但这也要求服务器端与客户端的接口存放的包路径必须一致。
我们把这个注册过程封装在RpcBeanRegistry类中。
/**
* Rpc方法注册到RpcBeanFactory中的具体过程,以及多种注册方式的重载。
*/
public class RpcBeanRegistry {
RpcBeanRegistry() {
}
static void registInterface(RpcBeanFactory rpcBeanFactory, Class> interfaces) {
doRegist(rpcBeanFactory, interfaces, null);
}
//具体的注册过程
private static void doRegist(RpcBeanFactory rpcBeanFactory, Class> interfaces, Object object) {
Method[] methods = interfaces.getDeclaredMethods();
for (Method method : methods) {
String rpcBeanId = String.valueOf(method.toString().hashCode());
//识别码的计算
RpcBeanDefination rpcBeanDefination = new RpcBeanDefination();
rpcBeanDefination.setKlass(interfaces);
rpcBeanDefination.setMethod(method);
rpcBeanDefination.setObject(object);
rpcBeanFactory.rpcBeanRegistry(rpcBeanId, rpcBeanDefination);
}
}
static void registInterface(RpcBeanFactory rpcBeanFactory, Class> interfaces, Object object) {
if (!interfaces.isAssignableFrom(object.getClass())) {
return;
}
doRegist(rpcBeanFactory, interfaces, object);
}
static void registInterface(RpcBeanFactory rpcBeanFactory, Class> interfaces, Class> klass) {
if (!interfaces.isAssignableFrom(klass)) {
return;
}
try {
doRegist(rpcBeanFactory, interfaces, klass.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
也就是说,在代理对象拦截到方法及其参数时,内部将其方法的RpcBeanId计算出来,然后连接Rpc服务器,将rpcBeanId与参数发送过去,等待服务器端返回结果;
服务器端在接收到Rpc请求消息时,通过识别码从Factory中取出RpcBeanDefnation,从请求中得到参数信息并进行转换,反射执行,再将结果发送至客户端,服务结束。