当前位置: 首页 > 工具软件 > RMI for C++ > 使用案例 >

制作简易rmi

戴建义
2023-12-01

制作简易rmi

一、rmi简介

rmi即:远程方法调用(Remote Method Invocation)

在c/s模式下,客户端和服务端所侧重的东西是不一样的,客户端只需要发出请求和得到结果,而不需要知道这个请求是如何完成的。同时,为了保护软件的封装性和安全性,不能把一个事务的具体执行都放在客户端执行,只需要给一个接口,用户调用接口所给的方法,等待结果返回回来,这才是一个软件安全性的一个保障。这时候,rmi的出现就给了我们这个想法提供了强有力的支撑。

二、rmi需求分析

  • rmi,从中文译名上可以看出,明显分为了rmi服务器和rmi客户端,客户端向服务器发出请求,得到服务器的响应信息
  • 分析rmi服务器:
    • 首先rmi服务器必须含有所有的真正执行的方法,只有这样。客户端调用接口方法后,服务器才能找到真正执行的方法。如何保存这种信息呢: > > 键值对,以接口方法的==toString().hashcode()==作为键,用一个definition作为值,放入map中。这个definition中需要存有真正执行的方法method和执行的对象object。
    • 服务器端,当接收到一个新的请求,需要为这个请求单独创建一个线程去完成,而不是处理完后再去接收其他请求
    • 服务器端需要拥有解析对端发过来的方法名称和参数,并将其转换为真正的可执行方法,执行后返回
  • 分析rmi客户端:
    • 客户端只拥有所有的接口方法,并且所有的接头都必须使用JDKProxy进行代理。调用真正方法时,Proxy会真正的将信息传输过去
    • rmi服务器不止一个,在连接rmi服务器的过程中,通过set()可以动态的控制想要连接的rmi服务器
    • rmi客户端必须拥有将参数json化的功能,以便rmi服务器进行解析
  • 关于代理:代理模式
  • 若存在无返回值的方法,那么服务器和客户端进行判断后都无需进行等待

三、代码展示

1.基础工具类

/**
 * RMI服务器结点信息,存有RMI服务器的IP地址和端口号port
 * @Author :漠殇
 * @Data :Create in 11:20 2021/7/16
 */
public class NodeAddress {
    private String ip;
    private int port;

    public NodeAddress() {
    }

    public NodeAddress(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}
public class ArgumentMaker {
	public static final Gson gson = new GsonBuilder().create();
	private static Type mapType = new TypeToken<Map<String, String>>() {}.getType();
	private Map<String, String> argMap;
	
	public ArgumentMaker() {
		this.argMap = new HashMap<String, String>();
	}
	
	public ArgumentMaker(String parameterString) {
		this.argMap = gson.fromJson(parameterString, mapType);
	}
	
	public Object getParameter(String name, Class<?> type) {
		String strValue = this.argMap.get(name);
		return gson.fromJson(strValue, type);
	}
	
	public Object getParameter(String name, Type type) {
		String strValue = this.argMap.get(name);
		return gson.fromJson(strValue, type);
	}
	
	public ArgumentMaker add(String parameterName, Object parameterValue) {
		this.argMap.put(parameterName, gson.toJson(parameterValue));
		return this;
	}
	
	@Override
	public String toString() {
		return gson.toJson(this.argMap);
	}
	
}

2.服务器端

/**
 * 一个远程调用方法的信息,执行类对象和方法
 * @Author :漠殇
 * @Data :Create in 9:45 2021/7/16
 */
public class RmiInterfaceDefinition {
    private Class<?> klass;
    private Object object;
    private Method method;

    RmiInterfaceDefinition() {
    }

    RmiInterfaceDefinition(Class<?> klass, Object object, Method method) {
        this.klass = klass;
        this.object = object;
        this.method = method;
    }

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

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

    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;
    }
}

/**
 * 所有需要远程调用的方法都存在与这个类的interfacePool中
 * @Author :漠殇
 * @Data :Create in 9:43 2021/7/16
 *
 */
public class RmiInterfaceFactory {
    private static final Map<String, RmiInterfaceDefinition> interfacePool;

    static {
        interfacePool = new HashMap<>();
    }
    public RmiInterfaceFactory() {
    }

    /**
     * 初始化interfacepool,完成接口类与实现类的对应
     * xml格式
     * <interfaces>
     *     <interface interfaceName="" className=""></interface>
     * </interfaces>
     * @param mappingPath
     */
    public void scanMethodMap(String mappingPath) {
        try {
            new XMLParser() {
                @Override
                public void dealElement(Element element, int index) throws Throwable {
                    String interfaceName = element.getAttribute("interfaceName");
                    String className = element.getAttribute("className");
                    byClassNamedealClass(interfaceName, className);
                }
            }.parseTag(XMLParser.newDocument(mappingPath), "interface");
        } catch (XMLNotFoundException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }

    /**
     * 外面可以设置新的接口和他的实现类
     * @param interfaceName
     * @param className
     * @throws Throwable
     */
    public void addInterface(String interfaceName, String className) throws Throwable {
        byClassNamedealClass(interfaceName, className);
    }

    /**
     * 判断接口interfaceName是否符合规定和类className是否是接口的实现类
     * @param interfaceName
     * @param className
     * @throws Throwable
     */
    private void byClassNamedealClass(String interfaceName, String className) throws Throwable{
        Class<?> interfaze = Class.forName(interfaceName);
        Class<?> klass = Class.forName(className);

        if (!interfaze.isInterface()) {
            new Exception("[ " + interfaze.getName() + "] : NOT is interface");
        }

        //判断klass是否实现了这个interfaze接口
        if (interfaze.isAssignableFrom(klass)) {
            new Exception("[" + interfaze.getName() + "] : NOT is " + "[" + klass.getName() + "] Assignable");
        }

        Object object = klass.newInstance();
        creatRealInterfaceMethod(interfaze, klass, object);
    }

    /**
     * 完成真正的一个接口对应一个实现类方法的填充。
     * @param interfaze
     * @param klass
     * @param object
     * @throws NoSuchMethodException
     */
    private void creatRealInterfaceMethod(Class<?> interfaze, Class<?> klass, Object object) throws NoSuchMethodException {
        Method[] methods = interfaze.getDeclaredMethods();
        for (Method interfaceMethod : methods) {
            //creat key
            String key = String.valueOf(interfaceMethod.toString().hashCode());

            //找到接口方法对应类的真正方法
            String methodName = interfaceMethod.getName();
            Class<?>[] parameterTypes = interfaceMethod.getParameterTypes();
            Method method = klass.getDeclaredMethod(methodName, parameterTypes);

            //生成这个Method的Definition,放入方法池
            RmiInterfaceDefinition rmiInterfaceDefinition = new RmiInterfaceDefinition(klass, object, method);
            RmiInterfaceFactory.interfacePool.put(key, rmiInterfaceDefinition);
        }
    }

    public static RmiInterfaceDefinition getMethod(String key) {
        return RmiInterfaceFactory.interfacePool.get(key);
    }
}

/**
 * RMI服务器,处理每一个连接客户端的请求
 * @Author :漠殇
 * @Data :Create in 11:24 2021/7/16
 */
public class RmiServer implements Runnable, ISpeaker {
    private ServerSocket serverSocket;
    private int serverPort;
    private volatile boolean goon;
    private List<IListener> listeners;

    public RmiServer() {
        this.listeners = new ArrayList<>();
        this.goon = false;
    }

    public int getServerPort() {
        return serverPort;
    }

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

    public boolean isStartUp() {
        return this.goon == true;
    }

    public boolean isShutDown() {
        return this.goon == false;
    }

    public void startUp() throws IOException {
        if (this.goon == true) {
            speakOut("RmiServer started");
        }

        this.goon = true;
        this.serverSocket = new ServerSocket(this.serverPort);
        new Thread(this).start();
        speakOut("RmiServer start");
    }

    public void shutDown() {
        if (this.goon == false) {
            speakOut("RmiServer not start");
        }
        close();
    }

    @Override
    public void run() {
        while (this.goon) {
            try {
                Socket client = serverSocket.accept();
                String clientIp = client.getInetAddress().getHostAddress();
                speakOut("用户[" + clientIp + "]上线");
                //TODO 单独处理每个客户请求
                new RmiServerCommunication(client);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        close();
    }

    void close() {
        this.goon = false;
        if (this.serverSocket!= null && !this.serverSocket.isClosed()) {
            try {
                this.serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                this.serverSocket = null;
            }
        }
    }

    @Override
    public void addListener(IListener listener) {
        this.listeners.add(listener);
    }

    @Override
    public void removeListener(IListener listener) {
        this.listeners.remove(listener);
    }

    @Override
    public void speakOut(String message) {
        System.out.println(message);
    }
}

/**
 * 真正完成方法的调用和执行的类
 * 客户端将参数和方法名传输过来,这里真正执行,并返回执行结果
 * @Author :漠殇
 * @Data :Create in 12:04 2021/7/16
 */
public class RmiServerCommunication implements Runnable{
    private Socket socket;
    private DataInputStream dis;
    private DataOutputStream dos;

    public RmiServerCommunication(Socket socket) throws IOException {
        this.socket = socket;
        this.dis = new DataInputStream(this.socket.getInputStream());
        this.dos = new DataOutputStream(this.socket.getOutputStream());
        new Thread(this, "RMI-SERVER-COMMUNICATION").start();
    }

    @Override
    public void run() {
        try {
            String methodId = this.dis.readUTF();
            String strParameter = this.dis.readUTF();

            RmiInterfaceDefinition rid = RmiInterfaceFactory.getMethod(methodId);
            if (rid == null) {
                new Exception("[ " + methodId + " ]不存在");
            }

            Object object = rid.getObject();
            Method method = rid.getMethod();

            Object[] objects = getParameterValues(method, strParameter);
            Object res = method.invoke(object, objects);

            //只有存在返回值,才执行下面的语句
            if (!method.getReturnType().equals(void.class)) {
                this.dos.writeUTF(ArgumentMaker.gson.toJson(res));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } finally {
            close();
        }
    }

    /**
     * 根据对端传输过来的parameter字符串,解析出真正的的参数数组
     * @param method
     * @param strParameter
     * @return
     */
    private Object[] getParameterValues(Method method, String strParameter) {
        ArgumentMaker argumentMaker = new ArgumentMaker(strParameter);
        int parameterCounts = method.getParameterCount();
        if (parameterCounts <= 0) {
            return new Object[]{};
        }

        Parameter[] parameters = method.getParameters();
        Object[] values = new Object[parameterCounts];

        for (int i = 0; i < parameterCounts; i++) {
            String key = "arg" + i;
            values[i] = argumentMaker.getParameter(key, parameters[i].getParameterizedType());
        }

        return values;
    }


    private void close() {
        try {
            if (this.dis != null) {
                this.dis.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            this.dis = null;
        }

        try {
            if (this.dos != null) {
                this.dos.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            this.dos = null;
        }

        try {
            if (this.socket != null) {
                this.socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            this.socket = null;
        }
    }
}

3.客户端

/**
 * 代理使用的入口,通过调用getProxy()方法,完成代理的使用
 * @Author :漠殇
 * @Data :Create in 9:24 2021/7/16
 */
public class JDkProxy {
    private RmiClient rmiClient;
    public JDkProxy() {
    }

    public void setRmiClient(RmiClient rmiClient) {
        this.rmiClient = rmiClient;
    }

    public <T> T getProxy(Class<?> interfaze) {
        if(!interfaze.isInterface()) {
            return null;
        }

        ClassLoader classLoader = interfaze.getClassLoader();
        Class<T>[] classes = new Class[] {interfaze};


        return (T) Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //这里启动rmi客户端连接程序,真正发送请求
                RmiClientCommunication communication = new RmiClientCommunication(rmiClient.getNodeAddress());
                communication.setMethod(method);
                communication.setArgs(args);
                Object res = communication.doRmiAccept();
                return res;
            }
        });
    }
}

/**
 * 用户层面使用,需要将服务器地址和端口号通过构造方法进行设定,通过set方法,可以使用不同的RMI服务器
 * @Author :漠殇
 * @Data :Create in 11:52 2021/7/16
 */
public class RmiClient {
    private NodeAddress nodeAddress; //旧地址
    private String ip;
    private int port;

    public RmiClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public void setNodeAddress(NodeAddress nodeAddress) {
        this.nodeAddress = nodeAddress;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public NodeAddress getNodeAddress() {
        return new NodeAddress(this.ip, this.port);
    }
}

/**
 * 客户端真正处理方法调用的类
 * 通过set方法,拥有需要执行的接口方法和参数,再将真正的参数传递到RMI服务器,等待RMI服务器的回传信息,将结果再返回给调用端
 * 注意:返回值若为void,则不需要等待回传信息
 * @Author :漠殇
 * @Data :Create in 15:02 2021/7/16
 */
public class RmiClientCommunication {
    private NodeAddress nodeAddress;
    private Method method;
    private Object[] args;

    private Socket socket;
    private DataInputStream dis;
    private DataOutputStream dos;

    public void setNodeAddress(NodeAddress nodeAddress) {
        this.nodeAddress = nodeAddress;
    }

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

    public void setArgs(Object[] args) {
        this.args = args;
    }

    RmiClientCommunication(NodeAddress nodeAddress) {
        this.nodeAddress = nodeAddress;
    }

    Object doRmiAccept() {
        try {
            this.socket = new Socket(nodeAddress.getIp(), nodeAddress.getPort());
            this.dis = new DataInputStream(this.socket.getInputStream());
            this.dos = new DataOutputStream(this.socket.getOutputStream());

            this.dos.writeUTF(String.valueOf(this.method.toString().hashCode()));
            this.dos.writeUTF(argToString(this.args));

            //只有存在返回值,才执行下面的语句
            if (!method.getReturnType().equals(void.class)) {
                String resStr = this.dis.readUTF();
                Object result = ArgumentMaker.gson.fromJson(resStr, this.method.getGenericReturnType());
                return result;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    private String argToString(Object[] args) {
        ArgumentMaker argumentMaker = new ArgumentMaker();
        if (args == null) {
            return null;
        }
        for (int i = 0; i < args.length; i++) {
            argumentMaker.add("arg" + i, args[i]);
        }
        return argumentMaker.toString();
    }
}

四、存在的问题

  • 该程序无法解决无参的方法,但是一个方法的远程调用,如果没有参数,也及其不合理。笔者处理过关于无参的方法,无论是json字符串和writeUTf()都无法发送null,从网络通信的角度,无参方法使用rmi远程调用也极不合理
  • 该程序没有异常的处理,比如:服务器掉线,服务器忙等,客户机掉线的异常处理。
 类似资料: