RMI与RPC

Ora ·
更新时间:2024-11-11
· 713 次阅读

RMI(Remote Method Invocation)远程方法调用与RPC(Remote Procedure Call Protocol)远程过程调用协议 文章目录RMI(Remote Method Invocation)远程方法调用与RPC(Remote Procedure Call Protocol)远程过程调用协议概述实现原理RMI框架注册    RMIMethodDefinitionFactory服务端    RmiServer    RmiActioner    ArgumentMaker客户端    RmiClientProxy    ModelDialog (注解)    RMIModelDialog (模态框)    RMIClient总结一缺陷    writeUTF or writeObject总结二 概述

        RPC(Remote Procedure Call Protocol)远程过程调用协议,通过网络从远程计算机上请求调用某种服务。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 应用:Dubbo。
        RMI(Remote Method Invocation)远程方法调用。能够让在客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机中的对象上的方法。应用:EJB。
        它们之间的区别在于,RPC不支持传输对象,是网络服务协议,与操作系统和语言无关。RMI只用于Java,支持传输对象。 RMI是面向对象的,Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型。而RPC的结果统一由外部数据表示(External Data Representation,XDR)语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。只有由XDR定义的数据类型才能被传递,可以说RMI是面向对象方式的Java RPC。
        更多详细区别可以参见RMI与RPC的区别

实现原理

        一种用于实现远程过程调用(RPC)(Remote procedure call)的Java API,这是一种基于网络的技术。本地机执行一个方法,而这个方法实质上是在服务器端的。表面上是客户端在调用一个方法,但,本质上是服务器在执行这个方法,并通过网络传输返回方法执行结果。
        从简介中我们可以确定几个基本问题:
        1、建立服务器;
        2、客户端连接服务器;
        3、在客户端执行一个方法,而该方法应该在服务器上执行;
        4、为了保证其工具性质,可以通过接口完成方法的确定。
        进一步分析:
        客户端在执行一个方法的时候,需要在执行过程中,连接服务器,并在连接成功后,将这个方法的方法名以及参数列表发送给给服务器;然后等待服务器返回调用方法的返回值。
        服务器与客户端连接建立好后,接收客户端传送过来的方法名以及参数列表,并反射调用该方法;最后,将这个方法的执行结果返回给客户端。

RMI框架

        本篇通过代理机制来实现RMI框架;只提供给客户端调用方法的接口,而不保存接口实现类,通过代理机制得到接口的实现类对象,在方法拦截中连接远程服务器、发送方法及参数列表、接收执行结果(接收完后关闭与服务器的连接)。在服务器端保存接口和接口实现类,并通过注解将实现类对象和方法注册到容器里,当客户端调用方法时,从容器中取出对应方法反射执行,并且将结果发送回客户端。

注册

         要想实现实现RMI,服务端就必须得通过客户端发送的调用的接口里面定义的方法找到对应服务端该接口实现类的方法执行。而服务端通过客户端发送来的方法的方法名、参数个数、参数类型等来找到保存在服务端的接口实现类的方法,这个过程是繁琐且耗时的;所以我先通过包扫描将接口中的方法和接口实现类中的方法相互映射,并注册到一个容器里面,这样客户端只用将接口方法发送过来,就可以该方法直接找到对应实现的方法,为远程调用节省时间。

         为了准确地描述接口中的方法和接口实现类中的方法的映射关系,注册的方式可以是注解或通过文件配置如(XML文件等),这里我使用注解的方式。
        如下是RmiAction注解类。

package com.rmi.annotation; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * 1.该类是一个注解类,里面有一个数组类型的成员RmiInterfaces * 2.使用方法是,对实现了接口的实现类加上该注解,保证准确地找到实现类 * 3.为后续的包扫描以及注册接口方法与实现类方法之间的对应关系提供了前提条件。 * @author dingxiang * */ @Retention(RUNTIME) @Target(TYPE) public @interface RmiAction { Class[] RmiInterfaces(); }

        服务端开启前要先通过包扫描找到该注解标记的类,注解中是接口类型数组,将数组中每个接口含有的方法依次进行扫描并找到实现类对应的方法,形成一个MethodDefinition,再以接口完整方法名的hashCode作为键,RMIMethodDefinition作为值形成键值对保存在Map容器里面中,以供服务端反射执行调用。
在这里插入图片描述

    RMIMethodDefinitionFactory

        如下是RMIMethodDefinitionFactory类,专门处理接口方法与实现类对应方法的映射关系的注册。

package com.rmi.core; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import com.mec.util.PackageScanner; import com.rmi.annotation.RmiAction; /**rmi方法扫描注册类
* 1.该类失RMI框架核心类,扫描传入的路径,找到带有RmiAction注解的类 * 2.对类中的注解理的接口数组进行遍历 * 3.以接口方法的hashCode为键,实现类中对应的RMIMethodDefinition为值 * 4.提供注册这种键值对应关系的方法 * 5.提供根据接口方法的hascode * * @author dingxiang * */ public class RMIMethodDefinitionFactory { private static final Map methodpool; static { methodpool=new HashMap(); } public RMIMethodDefinitionFactory() { } public static Map getMethodPool() { return methodpool; } public static void scanActionPackage(String pakageNmae) { new PackageScanner() { @Override public void dealClass(Class klass) { if (klass.isPrimitive()//八大基本类型 || klass.isInterface()//接口 || klass.isAnnotation()//注解 || klass.isEnum()//枚举 || klass.isArray()//数组 || !klass.isAnnotationPresent(RmiAction.class))//不包含RmiAction注解的类 { return; } RmiAction action=klass.getAnnotation(RmiAction.class); Class[] interfaces=action.RmiInterfaces(); for(Class klazz:interfaces) { RegistryMethod(klazz,klass); } } }.scannerPackage(pakageNmae);; } /** * 注册接口方法与实现类中的方法的映射关系 * @param Interface 接口类 * @param klass 实现类 * */ private static void RegistryMethod(Class Interface,Class klass) { Method[] methods =Interface.getDeclaredMethods();//接口方法 Object object=null; for(Method method:methods) { try { String methodid=String.valueOf(method.toGenericString().hashCode()); Class[] paraTypes=method.getParameterTypes(); //通过接口方法名以及参数类型找到实现该接口的实现类里面对应的方法 Method classMethod =klass.getDeclaredMethod(method.getName(), paraTypes); object = klass.newInstance(); RMIMethodDefinition md=new RMIMethodDefinition(object, classMethod); //以接口完整方法名的hashCode作为键,RMIMethodDefinition作为值形成键值对保存在Map容器里面中,以供服务端反射执行调用 methodpool.put(methodid, md); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } } } /** * @param methodid 接口方法的hashCode * @return 实现该接口的实现类对应的RMIMethodDefinition * */ public RMIMethodDefinition GetByMethodId(String methodid) { return methodpool.get(methodid); } }

        如下是RMIMethodDefinition类,用来描述实现类中实现注解里面接口数组里面的接口的方法。

package com.rmi.core; import java.lang.reflect.Method; /** * 保存实现类实现的接口方法信息的类
* 将扫描到的实现类方法和对应的实现类对象保存; * * @author dingxiang * */ public class RMIMethodDefinition { private Object object; private Method method; public RMIMethodDefinition() { } public RMIMethodDefinition(Object object, Method method) { this.object = object; this.method = method; } public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } }
服务端     RmiServer

        服务端的端口可以通过配置文件(如properties)文件来配置,可以参见properties文件解析。

package com.rmi.core; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.mec.util.PropertiesParser; /** * 1.该类是服务端类 * 2.提供了开启和关闭的方法 * 3.可以设置服务端端口(rmiPort) * 4.对于侦听到的每一个客户端的方法调用请求
* 这里采用了线程池开启一个RmiAction来处理 * * @author dingxiang * */ public class RMIServer implements Runnable{ private static final int DEFAULT_PORT=54199; private int rmiPort; private ServerSocket server; private ThreadPoolExecutor threadpool; private volatile boolean goon; public RMIServer() { this.rmiPort=DEFAULT_PORT; } public void loadRMIServerConfig(String RMIServerConfigPath) { PropertiesParser.loadProperties(RMIServerConfigPath); String configRMIServerPortStr=PropertiesParser.value("configRMIServerPort"); if (configRMIServerPortStr!=null&&configRMIServerPortStr.length()>0) { int rmiServerPort=Integer.valueOf(configRMIServerPortStr); if (rmiServerPort>0&&rmiServerPort<65536) { this.rmiPort=rmiServerPort; } } } public ThreadPoolExecutor getThreadpool() { return threadpool; } public RMIServer(int rmiPort) { this.rmiPort=rmiPort; } public void setRmiPort(int rmiPort) { this.rmiPort=rmiPort; } public void startRmiServer() { if (goon==true) { return; } threadpool = new ThreadPoolExecutor(10, 50, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque()); try { server=new ServerSocket(rmiPort); } catch (IOException e) { e.printStackTrace(); } goon=true; new Thread(this, "RMIServer").start(); } @Override public void run() { while (goon) { try { Socket socket=server.accept(); threadpool.execute(new RMIActioner(socket)); } catch (IOException e) { } } } public void stopRmiServer() { if (goon==false) { return; } if (threadpool.isShutdown()) { threadpool.shutdownNow(); }else { threadpool.shutdown(); } if (server!=null&&!server.isClosed()) { try { server.close(); } catch (IOException e) { }finally { server=null; } } } }
    RmiActioner package com.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; /**1.该类专门处理客户端的请求 * @author dingxiang * */ public class RMIActioner implements Runnable { private Socket socket; private DataInputStream dis; private DataOutputStream dos; //与客户端建立通信信道,传输方法名及参数列表和执行结果 public RMIActioner(Socket socket) { this.socket = socket; try { dis=new DataInputStream(socket.getInputStream()); dos=new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } /**客户端发两次,服务端接收两次 *服务端第一次接收的是调用的方法id,第二次接收的是参数列表 *通过方法id,找到对应的RMIMethodDefinition,并执行 *将执行结果发送给客户端 */ @Override public void run() { try { String methodid=dis.readUTF(); String argString=dis.readUTF(); RMIMethodDefinitionFactory methodfactory=new RMIMethodDefinitionFactory(); RMIMethodDefinition methoddefinition=methodfactory.GetByMethodId(methodid); Object res=null; if (methoddefinition==null) { res=new RMIErrorMethod(methodid); } else { Method method=methoddefinition.getMethod(); Object excuter=methoddefinition.getObject(); Object[] paras=getParas(method, argString); res=method.invoke(excuter, paras); } dos.writeUTF(ArgumentMaker.gson.toJson(res));//将执行结果返回给请求的客户端 Close(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * 1、通过method得到方法的参数类型列表
* 2、通过gson将传送过来的字符串转换为指定的对象; * * @param method * @param argString * @return Object[] */ public Object[] getParas(Method method,String argString) { ArgumentMaker maker=new ArgumentMaker(argString); Object[] paras=null; Type[] paratypes=method.getParameterTypes(); int paraCount=method.getParameterCount(); if (paraCount<=0) { paras=new Object[] {}; } paras=new Object[paraCount]; for (int i = 0; i < paraCount; i++) { paras[i]=maker.getValue("arg"+i, paratypes[i]); } return paras; } /* * 关闭通信信道 * */ public void Close() { if (dis!=null) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); }finally { dis=null; } } if (dos!=null) { try { dos.close(); } catch (IOException e) { e.printStackTrace(); }finally { dos=null; } } try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } finally { socket = null; } } }
    ArgumentMaker

        最开始是通过json来传输参数列表的,我把参数加入到一个Map里面,这样虽然可以保存参数名与参数对象的映射关系,但会打乱参数的顺序,而反射机制得不到准确参数顺序,会导致反射执行方法失败;所以需要通过协议来规范各个参数与之对应对象的关系。
        一开始,客户端将参数对象转化为String类型,服务端再把String类型的参数按照其具体类型转化为具体类型的值。
        具体的应用详见RmiActioner的getParas(Method method,String argString)与RMIClient的invokeMethod()方法。

package com.mec.util; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; public class ArgumentMaker { public static final Gson gson = new GsonBuilder().create(); private static final Type type = new TypeToken<Map>() {}.getType(); private Map paraMap; public ArgumentMaker() { paraMap = new HashMap(); } //构造方法可以将Json化的参数Map转化为Map public ArgumentMaker(String str) { paraMap = gson.fromJson(str, type); } //按照参数顺序将参数加入到参数Map里面 public ArgumentMaker addArg(String name, Object value) { paraMap.put(name, gson.toJson(value)); return this; } @SuppressWarnings("unchecked") public T getValue(String name, Type type) { String valueString = paraMap.get(name); if (valueString == null) { return null; } return (T) gson.fromJson(valueString, type); } //根据参数顺序及其类型,取出String类型的参数值,并将其按照其实际类型还原成该类型的具体值 @SuppressWarnings("unchecked") public T getValue(String name, Class type) { String valueString = paraMap.get(name); if (valueString == null) { return null; } return (T) gson.fromJson(valueString, type); } //将参数Map转化为json @Override public String toString() { return gson.toJson(paraMap); } } 客户端

        RMI框架提供给调用方调用的是接口,必须得为接口构造一个假的实现。显然,要使用动态代理。这样,调用方的调用就被动态代理接收到了,同时,为了避免重复多次RMI调用(如用户多次点击等),这里引入了模态框。

    RmiClientProxy package com.rmi.core; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import javax.swing.JFrame; import com.rmi.annotation.MecDialog; import com.rmi.dialog.core.MecModelDialog; /** * 代理类
* 1、通过接口得到代理对象来调用方法;
* 2、在方法中进行拦截,拦截时连接服务器进行远程方法调用;
* 3、支持模态框的开启; * @author dingxiang * */ public class RMIClientProxy { private RMIClient rmiClient; public RMIClientProxy(RMIClient rmiClient) { this.rmiClient = rmiClient; } public RMIClientProxy() { } public void setRmiClient(RMIClient rmiClient) { this.rmiClient = rmiClient; } public RMIClient getRmiClient() { return rmiClient; } /**根据调用的方法检查返回结果 * @param method * @param result */ public void checkMethodResult(Method method,Object result) { if (result instanceof RMIErrorMethod) { throw new RuntimeException("没有找到"+method.getName()+"方法"); } } /**通过JDK代理的方式来调用RMI服务器的方法 * @param interfaces 被代理的目标接口 * @return */ public Object getProxy(Class interfaces) { return Proxy.newProxyInstance(interfaces.getClassLoader(), new Class[] {interfaces}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { rmiClient.connectToServer(); rmiClient.setArgs(args); rmiClient.setMethod(method); Object result=rmiClient.invokeMethod(); checkMethodResult(method,result); return result; } }); } /** * 模态框式RMI连接(防止用户多次点击)
* 1、对于通过JDK代理的方式传递进来的接口,在invoke()中检查调用的方法是否有模态框注解,有则开启模态框;
* 2.在得到RMI服务器返回的结果后,自动关闭模态框 * @param interfaces 被代理的目标接口 * @param parent 模态框依赖显示的父窗口 * @return */ @SuppressWarnings("unchecked") public T getProxy(Class interfaces, JFrame parent) { return (T) Proxy.newProxyInstance( interfaces.getClassLoader(), new Class[] {interfaces}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; rmiClient.connectToServer(); rmiClient.setMethod(method); rmiClient.setArgs(args); if (method.isAnnotationPresent(MecDialog.class)) { MecDialog modelDialog = method.getAnnotation(MecDialog.class); String caption = modelDialog.caption(); MecModelDialog dialog = new MecModelDialog(parent, true); dialog.setRmiClient(rmiClient); dialog.setCaption(caption); dialog.showDialog(); result = dialog.getResult(); } else { result = rmiClient.invokeMethod(); } checkMethodResult(method,result); return result; } }); } }
    ModelDialog (注解) package com.rmi.annotation; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target(METHOD) public @interface ModelDialog { String caption(); }

        用法示例(要调用的接口方法前加上注解,caption表示要显示的内容)。

package com.rmi.action; import java.util.List; import com.rmi.annotation.ModelDialog; import com.rmi.model.NetNode; import com.rmi.model.Student; public interface IRmiAction { @ModelDialog(caption = "****Time is coming....****") String addHello(String message); @ModelDialog(caption = "<<<>>>") Student GetStudent(); @ModelDialog(caption="获取服务节点信息") List GetServerNodes(); }     RMIModelDialog (模态框) package com.rmi.dialog.core; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dialog; import java.awt.Font; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.Window; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import javax.swing.BorderFactory; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import com.rmi.core.RMIClient; public class RMIModelDialog extends JDialog { private static final long serialVersionUID = -6816161311513319945L; private Container container; private JLabel jlblMessage; private JPanel jpnlMessage; private RMIClient rmiClient; private Object result; public RMIModelDialog setCaption(String message) { Font font = new Font("宋体", Font.BOLD, 16); Container parent = getParent(); int parentLeft = parent.getX(); int parentTop = parent.getY(); int parentWidth = parent.getWidth(); int parentHeight = parent.getHeight(); int width = (message.length() + 4) * font.getSize(); int height = 5 * font.getSize(); setSize(width, height); setLocation(parentLeft + (parentWidth - width) / 2, parentTop + (parentHeight - height) / 2); container = getContentPane(); container.setLayout(null); setUndecorated(true); jpnlMessage = new JPanel(); jpnlMessage.setSize(width, height); jpnlMessage.setLayout(new BorderLayout()); jpnlMessage.setBackground(Color.lightGray); jpnlMessage.setBorder(BorderFactory.createLineBorder(Color.gray, 2)); container.add(jpnlMessage); jlblMessage = new JLabel(message, JLabel.CENTER); jlblMessage.setFont(font); jlblMessage.setSize(width, height); jlblMessage.setForeground(Color.blue); jlblMessage.setHorizontalTextPosition(JLabel.CENTER); jpnlMessage.add(jlblMessage, BorderLayout.CENTER); dealAction(); return this; } public void setRmiClient(RMIClient rmiClient) { this.rmiClient = rmiClient; } private void dealAction() { addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { result = rmiClient.invokeMethod(); dispose(); } }); } public Object getResult() { return result; } public void showDialog() { this.setVisible(true); } public void exitDialog() { this.dispose(); } public RMIModelDialog() { super(); } public RMIModelDialog(Dialog owner, boolean modal) { super(owner, modal); } public RMIModelDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc) { super(owner, title, modal, gc); } public RMIModelDialog(Dialog owner, String title, boolean modal) { super(owner, title, modal); } public RMIModelDialog(Dialog owner, String title) { super(owner, title); } public RMIModelDialog(Dialog owner) { super(owner); } public RMIModelDialog(Frame owner, boolean modal) { super(owner, modal); } public RMIModelDialog(Frame owner, String title, boolean modal, GraphicsConfiguration gc) { super(owner, title, modal, gc); } public RMIModelDialog(Frame owner, String title, boolean modal) { super(owner, title, modal); } public RMIModelDialog(Frame owner, String title) { super(owner, title); } public RMIModelDialog(Frame owner) { super(owner); } public RMIModelDialog(Window owner, ModalityType modalityType) { super(owner, modalityType); } public RMIModelDialog(Window owner, String title, ModalityType modalityType, GraphicsConfiguration gc) { super(owner, title, modalityType, gc); } public RMIModelDialog(Window owner, String title, ModalityType modalityType) { super(owner, title, modalityType); } public RMIModelDialog(Window owner, String title) { super(owner, title); } public RMIModelDialog(Window owner) { super(owner); } }     RMIClient package com.rmi.core; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Method; import java.net.Socket; import java.net.UnknownHostException; import com.mec.util.ArgumentMaker; import com.mec.util.PropertiesParser; /**RMI客户端 * 1.支持RMI服务器Ip和Port的配置 * 2.提供调用方法的方法id以及参数列表和方法执行结果的收发 * @author dingxiang */ public class RMIClient { private static int DEFAULT_PORT=54199; private static String DEFAULT_IP="192.168.137.1"; private int rmiPort; private String rmiIp; private Socket socket; private DataInputStream dis; private DataOutputStream dos; private Method method; private Object[] args; public RMIClient() { this(DEFAULT_PORT, DEFAULT_IP); } public RMIClient(int rmiPort,String rmiIp) { this.rmiIp=rmiIp; this.rmiPort=rmiPort; } public void loadRMIClientConfig(String RMIClientConfigPath) { PropertiesParser.loadProperties(RMIClientConfigPath); String RMIIp=PropertiesParser.value("configRMIIp"); if (rmiIp!=null&&rmiIp.length()>0) { this.rmiIp=RMIIp; } String configRMIPortStr=PropertiesParser.value("configRMIPort"); if (configRMIPortStr!=null&&configRMIPortStr.length()>0) { int rmiServerPort=Integer.valueOf(configRMIPortStr); if (rmiServerPort>0&&rmiServerPort<65536) { this.rmiPort=rmiServerPort; } } } public void setRmiPort(int rmiPort) { this.rmiPort=rmiPort; } public void setRmiIP(String rmiIp) { this.rmiIp=rmiIp; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } void connectToServer() { try { socket=new Socket(rmiIp, rmiPort); dis=new DataInputStream(socket.getInputStream()); dos=new DataOutputStream(socket.getOutputStream()); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public Object invokeMethod() { ArgumentMaker maker=new ArgumentMaker(); if (args==null) { args=new Object[] {}; } int argsCount=args.length; for(int i=0;i<argsCount;i++) { maker.addArg("arg"+i, args[i]); } String argString=maker.toString(); Object result=null; try { dos.writeUTF(String.valueOf(method.toGenericString().hashCode())); dos.writeUTF(argString); String res=dis.readUTF(); close(); if (res.contains("RMIErrorMethod")) { result=ArgumentMaker.gson.fromJson(res, RMIErrorMethod.class); }else { if (method.getReturnType().equals(void.class)) { result= null; } else { result=ArgumentMaker.gson.fromJson(res, method.getGenericReturnType()); } } } catch (IOException e) { e.printStackTrace(); } return result; } void close() { if (dis!=null) { try { dis.close(); } catch (IOException e) { } finally { dis=null; } } if (dos!=null) { try { dos.close(); } catch (IOException e) { }finally { dos=null; } } if (socket!=null&&!socket.isClosed()) { try { socket.close(); } catch (IOException e) { }finally { socket=null; } } } }

测试代码以及结果截图如下
         ServerDemo

package com.rmi.test; import com.rmi.core.RMIMethodDefinitionFactory; import com.rmi.core.RMIServer; public class ServerDemo { public static void main(String[] args) { RMIMethodDefinitionFactory.scanActionPackage("com.rmi"); RMIServer server=new RMIServer(54199); server.startRmiServer(); } }

         ClientDemo4

package com.rmi.test; import com.rmi.core.RMIClient; import com.rmi.core.RMIClientProxy; import com.rmi.view.ParentView; public class ClientDemo4 { public static void main(String[] args) { RMIClientProxy proxy=new RMIClientProxy(); RMIClient client=new RMIClient(); proxy.setRmiClient(client); ParentView parentView=new ParentView(); parentView.setProxy(proxy); parentView.showView(); // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // parentView.closeView(); } }

在这里插入图片描述

总结一

        RMI框架使用者只需根据提供的服务的接口来调用服务,而不必知道服务实现的详细细节;这里调用的服务包括(无参,单参,多参以及无返回值的服务)并且采用短连接的方式,减小了服务器的压力,服务器也不必维护与客户端的长时间连接而耗费资源与时间。

代码以上传至https://github.com/dx99606707/depository/blob/master/20200116@RMI1.rar

缺陷     writeUTF or writeObject

        在ArgumentMaker中使用了json来序列化数据(包括参数以及返回结果),用writeUTF()来进行传输,然而writeUTF(value:String) :将 UTF-8 字符串写入字节流。先写入以两个字节表示的 UTF-8 字符串长度(以字节为单位),然后写入表示字符串字符的字节。因为先把字符长度写入二进制,16位能保存的字节长度为65535。对于保存参数列表的Map,在某些情况下转化为json以后太长,会报出String Too Long Exception。因此为了解决这个问题,要么把传输的数据大小限制在65535,但这样的解决方式,着实落了下乘;这里使用writeObject()和readObject()来进行进行参数列表以及调用方法执行结果的传输,而writeObject和readObject()的辅助信息较大,因此对于数据长度不是很大的方法的hashCode,仍然用writeUTF()和readUTF()来进行传输。
        通过这样的方式,则不必在需要ArgumentMaker使用json来序列化以及反序列化传输的数据,传输的参数列表就是有序的参数列表,服务器根据传过来的方法的hashCode找到对应RMIMethodDefinition以后,直接反射执行即可。修改过后的RMIClient以及RMiActioner如下

package com.rmi.core; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.net.Socket; import java.net.UnknownHostException; import com.mec.util.PropertiesParser; /**RMI客户端 * 1.支持RMI服务器Ip和Port的配置 * 2.提供调用方法的方法id以及参数列表和方法执行结果的收发 * @author dingxiang */ public class RMIClient { private static int DEFAULT_PORT=54199; private static String DEFAULT_IP="192.168.137.1"; private int rmiPort; private String rmiIp; private Socket socket; private DataInputStream dis; private DataOutputStream dos; private Method method; private Object[] args; public RMIClient() { this(DEFAULT_PORT, DEFAULT_IP); } public RMIClient(int rmiPort,String rmiIp) { this.rmiIp=rmiIp; this.rmiPort=rmiPort; } public void loadRMIClientConfig(String RMIClientConfigPath) { PropertiesParser.loadProperties(RMIClientConfigPath); String RMIIp=PropertiesParser.value("configRMIIp"); if (rmiIp!=null&&rmiIp.length()>0) { this.rmiIp=RMIIp; } String configRMIPortStr=PropertiesParser.value("configRMIPort"); if (configRMIPortStr!=null&&configRMIPortStr.length()>0) { int rmiServerPort=Integer.valueOf(configRMIPortStr); if (rmiServerPort>0&&rmiServerPort<65536) { this.rmiPort=rmiServerPort; } } } public void setRmiPort(int rmiPort) { this.rmiPort=rmiPort; } public void setRmiIP(String rmiIp) { this.rmiIp=rmiIp; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public Object[] getArgs() { return args; } public void setArgs(Object[] args) { this.args = args; } void connectToServer() { try { socket=new Socket(rmiIp, rmiPort); dis=new DataInputStream(socket.getInputStream()); dos=new DataOutputStream(socket.getOutputStream()); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public Object invokeMethod() { if (args==null) { args=new Object[] {}; } Object result=null; try { ObjectOutputStream out=new ObjectOutputStream(dos); dos.writeUTF(String.valueOf(method.toGenericString().hashCode())); out.writeObject(args); ObjectInputStream ois=new ObjectInputStream(dis); result=ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return result; } void close() { if (dis!=null) { try { dis.close(); } catch (IOException e) { } finally { dis=null; } } if (dos!=null) { try { dos.close(); } catch (IOException e) { }finally { dos=null; } } if (socket!=null&&!socket.isClosed()) { try { socket.close(); } catch (IOException e) { }finally { socket=null; } } } } package com.rmi.core; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; /**1.该类专门处理客户端的请求 * @author dingxiang */ public class RMIActioner implements Runnable { private Socket socket; private DataInputStream dis; private DataOutputStream dos; //与客户端建立通信信道,传输方法名及参数列表和执行结果 public RMIActioner(Socket socket) { this.socket = socket; try { dis=new DataInputStream(socket.getInputStream()); dos=new DataOutputStream(socket.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } /**客户端发两次,服务端接收两次 *服务端第一次接收的是调用的方法id,第二次接收的是参数列表 *通过方法id,找到对应的RMIMethodDefinition,并执行 *将执行结果发送给客户端 */ @Override public void run() { try { ObjectInputStream ois=new ObjectInputStream(dis); String methodid= dis.readUTF(); Object[] args=(Object[]) ois.readObject(); RMIMethodDefinitionFactory methodfactory=new RMIMethodDefinitionFactory(); RMIMethodDefinition methoddefinition=methodfactory.GetByMethodId(methodid); Object res=null; if (methoddefinition==null) { res=new RMIErrorMethod(methodid); } else { Method method=methoddefinition.getMethod(); Object excuter=methoddefinition.getObject(); res=method.invoke(excuter, args); } ObjectOutputStream out=new ObjectOutputStream(dos); out.writeObject(res); Close(); } catch (IOException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /* * 关闭通信信道 * */ public void Close() { if (dis!=null) { try { dis.close(); } catch (IOException e) { e.printStackTrace(); }finally { dis=null; } } if (dos!=null) { try { dos.close(); } catch (IOException e) { e.printStackTrace(); }finally { dos=null; } } try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } finally { socket = null; } } } 总结二

        至此,RMI框架完成。也许你会问既然有HTTP请求,为什么还要用RPC调用?(前面我们说过可以说RMI是面向对象方式的Java RPC。)对于HTTP,以其中的Restful规范为代表,它可读性好,且可以得到防火墙的支持、甚至跨语言的支持;但是HTTP也有其缺点,这是与其优点相对应的。首先是有用信息占比少,毕竟HTTP工作在第七层,包含了大量的HTTP头等信息。其次是效率低,还是因为第七层的缘故。还有,其可读性似乎没有必要,因为我们可以引入网关增加可读性,此外,使用HTTP协议调用远程方法比较复杂,要封装各种参数名和参数值。。
        在分布式系统中,因为每个服务的边界都很小,很有可能调用别的服务提供的方法。这就出现了服务A调用服务B中方法的需求,但如前所述,基于Restful的远程过程调用有着明显的缺点,主要是效率低、封装调用复杂。当存在大量的服务间调用时,这些缺点变得更为突出。
        服务A调用服务B的过程是应用间的内部过程。而牺牲可读性提升效率、易用性是可取的。由此产生了RPC。通常,RPC要求在调用方中放置被调用的方法的接口。调用方只要调用了这些接口,就相当于调用了被调用方的实际方法,十分易用。于是,调用方可以像调用内部接口一样调用远程的方法,而不用封装参数名和参数值等操作。调用方调用内部的一个方法,但是被RPC框架偷梁换柱为远程的一个方法。之间的通信数据可读性不需要好,只需要RPC框架能读懂即可,因此效率可以更高。
        代码已上传至:https://github.com/dx99606707/depository/blob/master/20200120@RMI2.rar


作者:黑珍珠苦荞香茶



rmi rpc

需要 登录 后方可回复, 如果你还没有账号请 注册新账号