Java本地&远程调用Matlab函数及脚本

6 篇文章 0 订阅

Java本地&远程调用Matlab函数及脚本

Matlab版本: 2020b

Matlab提供了丰富的工具箱,如果可以使用Java调用Matlab来实现一些特定的算法,确实是一个不错的想法,经过实验证明这个想法可行,但是性能差点事情,对于一些特殊场景还是可以尝试的。查阅了网上大量的资料,实现Java调用Matlab基本有两种实现思路:1. 将Matlab函数打成Jar类库 ,然后基于MCR运行 2. 使用Matlab提供的Java Matlab Engine API。 本篇文章也是围绕着两个思路,介绍具体实现方法,这其中也会分别介绍本地调用,以及远程调用两种方式。

1 准备工作

  1. 远程服务器一台,可以装有window server或者ubuntu系统,或者自己在开发机安装
  2. 安装版本为2020b的matlab,关于matlab的安装可以参考这篇文章:https://www.bilibili.com/video/BV1uK4y1W7qB。这里我安装的window版本的,安装完成后,启动的时候遇到了一个问题,是由于忘记将压缩包中的补丁里bin复制到安装目录中了,这里一定要复制过去,不然启动不了,注意!!!
  3. 开发机一台

注意:安装Matlab的时候,一定要安装如下组件:

image-20220318173824921

2 将Matlab函数打成Jar类库 ,然后基于MCR运行

实现原理很简单,就是利用Matlab生成Java的包,使用这个包编写的程序,就可以在MCR的环境运行了。MCR的全拼MATLAB Compiler Runtime,就是一个Matlab的编译运行环境。网上大多数的文章都是基于这种原理实现的,官网文档也有介绍:https://ww2.mathworks.cn/help/compiler_sdk/gs/create-a-java-application-with-matlab-code.html。打包成Java类库有两种方式,通过Matlab界面,或者通过命令行。这篇文章只简单介绍通过Matlab软件界面进行打包。

2.1 安装MCR

如果安装Matlab,并且勾选的文章一开头说的那些选项,就会将MCR一起安装好,当然也可以单独安装MCR,参考:https://ww2.mathworks.cn/help/compiler/install-the-matlab-runtime.html,这里就不单独安装了。

2.2 打包Matlab函数

  1. 打开Matlab,在「主页」菜单里点击「新建脚本」,在编辑器中创建如下函数,并保存为mfft.m

    function y = mfft(x,N)
    y=abs(fft(x,N));
    

image-20220319203316898

  1. 打包Java类库,在「APP」菜单点击「Library Compiler」打开类库编译器

    image-20220319203756376

    在打开的类库编译器窗口中,「TYPE」一栏选择「Jav aPakage」,在「EXPORTED FUNCTIONS」一栏选择我们已经创建好的函数文件(这里可以选择多个函数),然后修改Jar的名称为signalprocessor,接着修改类名为SignalProcessor,最后点击右上角的「Package」按钮打包即可。

  2. 打包完成之后,会弹出打包后的文件夹,其中「for_redistribution_files_only」文件夹下的Jar就是我们打包后的类库,在我们自己的Java应用中引用这个类库即可。

    image-20220319205120185

2.3 本地调用

上面通过Matlab的「Library Compiler」成功打包了一个包含FFT函数的Java类库,接下来要介绍如何在我们自己的Java项目中去调用这个类库。

2.3.1 创建Java项目

使用IDEA创建一个名为matlab-mcr-java的Maven项目

maven-1

2.3.2 使用Maven加载生成的类库

  1. 在项目中创建一个名字为「lib」的文件夹,并将matlab安装目录中的R2020b\toolbox\javabuilder\jar\javabuilder.jar,以及我们生成的类库signalprocessor.jar复制到这个文件夹下

  2. 修改pom.xml文件,加载lib中的两个类库

    <dependencies>
        <dependency>
            <groupId>matlab</groupId>
            <artifactId>matlab.javabuilder</artifactId>
            <version>2020R</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/javabuilder.jar</systemPath>
        </dependency>
    
    
        <dependency>
            <groupId>matlab</groupId>
            <artifactId>matlab.signalprocessor</artifactId>
            <version>1.0</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/signalprocessor.jar</systemPath>
        </dependency>
    </dependencies>
    
  3. maven重新加载

2.3.3 编写本地调用程序

  1. 创建matlab.mcr.example包,并创建SignalProcessorExample类

    public class SignalProcessorExamples {
        public static void main(String[] args) throws Exception {
            SignalProcessor processor = new SignalProcessor();
            double[] x = new double[]{1.0, 2.0, 3.0, 1.0};
            Object[] res = processor.mfft(1, x, (double) x.length);
            MWArray a = (MWNumericArray) res[0];
            System.out.println(Arrays.toString((double[]) a.getData()));
        }
    }
    
    
  2. 在装有MatlabMCR的机器上运行,注意,对象初始化会比较慢,如果要在项目中使用,可以提前初始化对象,后面直接进行方法调用即可
    在这里插入图片描述

2.4 远程调用

上面开发的程序,必须要在具有MCR的环境中运行。其实在打包Java类库的时候,有一个选项,询问用户是否将Matlab Runtime的类库一并打包,如果选择这个选项的话,就不需要单独的MCR环境,显然实际项目中这么做不太理想,因为包含MCR的一个Java类库就几个G大小。另外一个问题就是,不能每一个应用都体用一个MCR环境,这样也会造成资源的浪费,所以Matlab提供了基于RMI远程调用的方式,关于什么是RMI,参考这篇文章:https://www.cnblogs.com/wxisme/p/5296441.html。

Implementation of RMI with client-server applications on one JVM versus multiple JVMs

如上是Matlab官网提供的RMI远程调用的架构图,客户端不需要具有MCR环境,服务具有就可以了。下面将通过代码,分别实现客户端和服务端示例。

2.4.1 服务端示例

在官方的javabuilder.jar类库中,会包含RMI相关的封装,服务端只需要去创建监听对象即可。如下为服务端的代码

public class SignalProcessorServerExamples {
    public static void main(String[] args) throws Exception {
        BasicRemoteFactory factory = new BasicRemoteFactory(SignalProcessor.class, SignalProcessorRemote.class, true);
        // 这里调用,是初始化单播远程对象
        Remote stub = factory.getStub();
        Registry reg = LocateRegistry.createRegistry(10990);
        reg.rebind("FactoryInstance", factory);
        System.out.println("Server registered and running successfully!!\n");
        Thread.currentThread().join();
    }
}

注意:创建BasicRemoteFactory对象的时候,构造函数中的最后一个布尔参数一定要设置成fMarshalOutputs=true,设置为true的话,服务端会将matlab的返回对象包装成Java类,否则客户端需要依赖MCR的库进行转换,这样的话,客户端也需要具有MCR环境,所以这里直接设置为true,让其在服务端就转换成Java的对象。

// 这段代码在com.mathworks.toolbox.javabuilder.remoting.RemoteProxy.ProxyInvocationHandler的invoke方法中
if (this.iMarshalOutputs) {
var11 = marshalMWArrayToObject(var11);
}

2.4.2 客户端示例

客户端需要获取远程的桩,然后再进行调用

public class SignalProcessorClientExamples {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("192.168.32.61", 10990);
        RemoteFactory factory = (RemoteFactory) registry.lookup("FactoryInstance");
        SignalProcessorRemote remote = (SignalProcessorRemote) factory.newInstance();

        double[] x = new double[]{1.0, 2.0, 2.5, 1.0};
        Object[] res = remote.mfft(1, x, 4.);
        System.out.println(Arrays.toString(((double[][]) res[0])[0]));

    }
}

其中通过主机IP和端口号获取远程的Registry,然后从Registry中获取到名字为FactorInstance的RemoteFactory对象,通过RemoteFactory创建SignalProcessorRemote。

2.4.3 运行验证

  1. 在装有Matlab的机器上运行服务端
    在这里插入图片描述
  2. 在自己的开发机上运行客户端

image-20220320124246161

2.5 完整代码

完整的代码已经提交到Github上了:https://github.com/shirukai/matlab-mcr-java.git

3 使用Matlab提供的Java Matlab Engine API

上一节介绍了通过Matlab打包成Java类库然后进行调用的方法,这一节将介绍另一个中方法,通过Matlab提供的Matlab Engine API进行调用,可以参考官网文章: https://ww2.mathworks.cn/help/matlab/matlab-engine-api-for-java.html。这种方法官网只提供了本地调用,即必须在装有Matlab的机器上,关于远程调用,笔者自己实现了两个方案,一个是借鉴上一节远程调用的RMI实现方案,另一种是我们比较熟悉的RPC调用。

注意:使用Matlab Engine API,需要提前配置好相关环境变量例如将安装目录\Polyspace\R2020b\bin\win64添加的Path中,其它机器参考:https://ww2.mathworks.cn/help/matlab/matlab_external/setup-environment.html

3.1 本地调用

3.1.1 创建Java项目

使用IDEA创建一个名为matlab-engine-java的Maven项目
https://cdn.jsdelivr.net/gh/shirukai/images/20220320131117.gif_

3.1.2 引入相关依赖

这里需要engine api相关的依赖,在Matlab的安装目录\R2020b\extern\engines\java\jar\engine.jar,在项目中创建一个lib目录,将engine.jar复制到lib目录下。修改pom.xml,引入依赖

    <dependencies>
        <dependency>
            <groupId>matlab</groupId>
            <artifactId>matlab.engine</artifactId>
            <version>2020R</version>
            <scope>system</scope>
            <systemPath>${basedir}/lib/engine.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.36</version>
        </dependency>
    </dependencies>

3.1.3 编写本地调用程序

在项目中创建包matlab.engine.remote.example并创建类MatlabEngineExamples:

public class MatlabEngineExamples {
    public static void main(String[] args)throws Exception{
        MatlabEngine engine = MatlabEngine.startMatlab();
        engine.eval("y=0:1024-1;s=sin(2.0*pi*y*100.0/1000.0);fx=abs(fft(s));");
        // 获取变量
        double[] fx = engine.getVariable("fx");
        System.out.println(Arrays.toString(fx));
    }
}

在装有Matlab的机器上运行
https://cdn.jsdelivr.net/gh/shirukai/images/20220320134057.png
如果遇到如下错误,参考:https://ww2.mathworks.cn/matlabcentral/answers/91874-why-do-i-receive-license-manager-error-103,将安装目录\licenses\下的许可证中的"SIGN=" 全局替换成 “TS_OK SIGN=”

image-20220320133252898

3.2 远程调用

本篇文章提供两种远程调用的方式:RMI和RPC,设计模式都是相似的,都需要服务端、客户端。所以首先提取公共的部分,比如调用接口的定义,接口的实现、客户端及工厂定义、服务端及工程定义等。然后再分别实现具体的客户端以及服务端即可。设计UML类图如下:
https://cdn.jsdelivr.net/gh/shirukai/images/20220320223215.png

如下为公共部分的类及接口

  1. 定义接口MatlabEngineService

    matlab.engine.remote.api包下创建MatlabEngineService接口

    package matlab.engine.remote.api;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    /**
     * 定义Matlab引擎的接口,其中继承Remote用来兼容RMI方式调用
     *
     * @author shirukai
     */
    public interface MatlabEngineService extends Remote {
        /**
         * 执行Matlab函数
         *
         * <blockquote><pre>
         * double[] x = {2.0, 4.0, 6.0};
         * double[] res = service.feval("sqrt", (Object) x);
         * </pre></blockquote>
         *
         * @param funcName 函数名称
         * @param params   函数的参数列表
         * @param <T>      返回值类型
         * @return 执行结果
         * @throws RemoteException 兼容RMI
         */
        <T> T feval(String funcName, Object... params) throws RemoteException;
    
        /**
         * 执行Matlab脚本
         *
         * @param script 脚本
         * @throws RemoteException 兼容RMI
         */
        void eval(String script) throws RemoteException;
    
        /**
         * 获取变量
         * 执行eval()方法之后,可以调用此方法获取脚本中的变量
         *
         * @param varName 变量名称
         * @param <T>     返回值类型
         * @return 变量结果
         * @throws RemoteException 兼容RMI
         */
        <T> T getVariable(String varName) throws RemoteException;
    }
    
    
  2. 实现接口MatlabEngineServiceImpl

    matlab.engine.remote.service包下创建MatlabEngineServiceImpl类

    package matlab.engine.remote.service;
    
    import com.mathworks.engine.EngineException;
    import com.mathworks.engine.MatlabEngine;
    import matlab.engine.remote.api.MatlabEngineService;
    
    import java.io.Serializable;
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    
    /**
     * Matlab引擎服务接口的具体实现
     * 参考:{@link MatlabEngine}
     *
     * @author shirukai
     */
    public class MatlabEngineServiceImpl extends UnicastRemoteObject implements MatlabEngineService, Serializable {
        private final MatlabEngine engine;
    
        public MatlabEngineServiceImpl() throws RemoteException, EngineException, InterruptedException {
            super();
            // 启动Matlab服务
            this.engine = MatlabEngine.startMatlab();
        }
    
        @Override
        public <T> T feval(String funcName, Object... params) throws RemoteException {
            try {
                return engine.feval(funcName, params);
            } catch (Exception e) {
                throw new RemoteException(e.getMessage());
            }
        }
    
    
        @Override
        public void eval(String script) throws RemoteException {
            try {
                engine.eval(script);
            } catch (Exception e) {
                throw new RemoteException(e.getMessage());
            }
        }
    
        @Override
        public <T> T getVariable(String varName) throws RemoteException {
            try {
                return engine.getVariable(varName);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }
    
    
  3. 定义服务端抽象类

    matlab.engine.remote.common包下创建Client抽象类

    package matlab.engine.remote.common;
    
    /**
     * 定义客户端创建参数,及创建方法
     *
     * @author shirukai
     */
    public abstract class Client {
        protected final String host;
        protected final Integer port;
    
        public Client(String host, Integer port) {
            this.host = host;
            this.port = port;
        }
    
        abstract public <T> T get(Class<T> interfaceClass) throws Exception;
    }
    
    
  4. 定义客户端抽象类

    matlab.engine.remote.common包下创建Server抽象类

    package matlab.engine.remote.common;
    
    /**
     * 定义服务端创建参数,及启动方法
     *
     * @author shirukai
     */
    public abstract class Server<T> {
        protected final Integer port;
        protected final T instance;
    
        public Server(Integer port, T instance) {
            this.port = port;
            this.instance = instance;
        }
    
        public abstract void start() throws Exception;
    }
    
    
  5. 定义服务端工厂类

    matlab.engine.remote.common包下创建ServerFactory类

    package matlab.engine.remote.common;
    
    import java.rmi.Remote;
    
    /**
     * 用来创建指定类型的服务端
     *
     * @author shirukai
     */
    public class ServerFactory {
        /**
         * 创建RMI的服务端
         *
         * @param port     监听端口号
         * @param instance 代理实例
         * @param <T>      实例类型
         * @return RMI服务端
         */
        public static <T extends Remote> Server<T> createRMIServer(Integer port, T instance) {
            // TODO
        }
    
        /**
         * 创建RPC的服务端
         *
         * @param port     监听端口号
         * @param instance 代理实例
         * @param <T>      实例类型
         * @return RPC服务端
         */
        public static <T> Server<T> createRPCServer(Integer port, T instance) {
            //TODO
        }
    }
    
    
  6. 定义客户端工厂类

    matlab.engine.remote.common包下创建ClientFactory类

    package matlab.engine.remote.common;
    
    /**
     * 用来创建指定类型的客户端
     *
     * @author shirukai
     */
    public class ClientFactory {
        /**
         * 创建基于RMI方式的客户端
         *
         * @param host 远程服务端的地址
         * @param port 远程服务端的端口
         * @return RMI客户端
         */
        public static Client createRMIClient(String host, Integer port) {
          // TODO 
            return null;
        }
    
        /**
         * 创建基于RPC方式的服务端
         *
         * @param host 远程服务端的地址
         * @param port 远程服务端的端口
         * @return RPC客户端
         */
        public static Client createRPCClient(String host, Integer port) {
           // TODO 
            return null;
        }
    }
    
    

3.2.1 基于RMI的实现

3.2.1.1 服务端

RMI的服务端比较简单,继承Server抽象类,实现start方法,直接创建一个Registry监听指定端口,然后绑定一个实例对象即可

package matlab.engine.remote.rmi.server;

import matlab.engine.remote.common.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
 * RMI服务端
 *
 * @author shirukai
 */
public class RMIServer<T extends Remote> extends Server<T> {
    private static final Logger LOG = LoggerFactory.getLogger(Server.class);

    public RMIServer(Integer port, T instance) {
        super(port, instance);
    }

    /**
     * 启动监听服务
     *
     * @throws Exception e
     */
    @Override
    public void start() throws Exception {
        LOG.info("Starting the RMI-based Matlab engine...");
        LOG.info("Listening port: {}.", port);
        LOG.info("Service backend: {}.", instance.getClass());
        Registry registry = LocateRegistry.createRegistry(port);
        String name = instance.getClass().getInterfaces()[0].getSimpleName();
        registry.rebind(name, instance);
    }
}

服务端创建完成之后,在ServerFactory中添加创建RMI服务端的代码

    public static <T extends Remote> Server<T> createRMIServer(Integer port, T instance) {
        return new RMIServer<>(port, instance);
    }
3.2.1.2 客户端

客户单也是很简单,继承Client抽象类,实现get方法,get方法中,根据远程主机IP和端口号获取Registry,然后查找到对应的实例返回即可。

package matlab.engine.remote.rmi.client;

import matlab.engine.remote.common.Client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

/**
 * RMI客户端
 *
 * @author shirukai
 */
public class RMIClient extends Client {
    public RMIClient(String host, Integer port) {
        super(host, port);
    }

    /**
     * 获取客户端
     *
     * @param interfaceClass 代理接口类
     * @param <T>            代理接口类型
     * @return 代理实例
     * @throws Exception e
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T get(Class<T> interfaceClass) throws Exception {
        Registry registry = LocateRegistry.getRegistry(this.host, this.port);
        return (T) registry.lookup(interfaceClass.getSimpleName());
    }
}

客户端创建完成之后,在ClientFactory中添加创建RMI客户端的代码

    public static Client createRMIClient(String host, Integer port) {
        return new RMIClient(host, port);
    }
3.2.1.3 运行验证
  1. 编写服务端示例,在matlab.engine.remote.example包下

    package matlab.engine.remote.example;
    
    import matlab.engine.remote.common.Server;
    import matlab.engine.remote.common.ServerFactory;
    import matlab.engine.remote.service.MatlabEngineServiceImpl;
    
    /**
     * @author shirukai
     */
    public class RMIEngineServerExamples {
        public static void main(String[] args) throws Exception {
            MatlabEngineServiceImpl service = new MatlabEngineServiceImpl();
            Server<MatlabEngineServiceImpl> server = ServerFactory.createRMIServer(10991, service);
            server.start();
        }
    }
    
  2. 编写客户端示例,在matlab.engine.remote.example包下

    package matlab.engine.remote.example;
    
    import matlab.engine.remote.api.MatlabEngineService;
    import matlab.engine.remote.common.Client;
    import matlab.engine.remote.common.ClientFactory;
    
    import java.util.Arrays;
    
    /**
     * @author shirukai
     */
    public class RMIEngineClientExamples {
        public static void main(String[] args) throws Exception {
            Client client = ClientFactory.createRMIClient("192.168.66.212", 10991);
            MatlabEngineService service = client.get(MatlabEngineService.class);
    
            // 调用matlab函数
            double[] x = {2.0, 4.0, 6.0};
            double[] res = service.feval("sqrt", (Object) x);
            for (double e : res) {
                System.out.println(e);
            }
    
            // 调用matlab脚本
            String scripts = "y=0:1024-1;s=sin(2.0*pi*y*100.0/1000.0);fx=abs(fft(s));";
            service.eval(scripts);
    
            // 获取变量
            double[] fx = service.getVariable("fx");
            System.out.println(Arrays.toString(fx));
        }
    }
    
    
  3. 运行服务端示例

    image-20220321165730832

  4. 运行客户端示例

    runcode-3

3.2.2 基于RPC的实现

RPC的核心实现有很多种,我们可以基于Netty、Dubbo、Thrift的等等框架,这里为了测试,使用了自己开发的RPC框架,底层基于Socket通信,之前在RPC的文章里有介绍过,这里就不再赘述,贴一下代码,在matlab.engine.remote.rpc.core包下实现。

  1. RPCRequest

    package matlab.engine.remote.rpc.core;
    
    import java.io.Serializable;
    
    /**
     * RPC通信的请求类型
     *
     * @author shirukai
     */
    public class RPCRequest implements Serializable {
        private static final long serialVersionUID = 4932007273709224551L;
        /**
         * 方法名称
         */
        private String methodName;
    
        /**
         * 参数列表
         */
        private Object[] parameters;
    
        /**
         * 参数类型
         */
        private Class<?>[] parameterTypes;
    
        public String getMethodName() {
            return methodName;
        }
    
        public RPCRequest setMethodName(String methodName) {
            this.methodName = methodName;
            return this;
        }
    
        public Object[] getParameters() {
            return parameters;
        }
    
        public RPCRequest setParameters(Object[] parameters) {
            this.parameters = parameters;
            return this;
        }
    
        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }
    
        public RPCRequest setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
            return this;
        }
    }
    
  2. RPCResponse

    package matlab.engine.remote.rpc.core;
    
    import java.io.Serializable;
    
    /**
     * PRC响应类型
     *
     * @author shirukai
     */
    public class RPCResponse implements Serializable {
        public static String SUCCEED = "succeed";
        public static String FAILED = "failed";
        private static final long serialVersionUID = 6595683424889346485L;
    
        /**
         * 响应状态
         */
        private String status = "succeed";
        /**
         * 响应信息,如异常信息
         */
        private String message;
    
        /**
         * 响应数据,返回值
         */
        private Object data;
    
        public String getStatus() {
            return status;
        }
    
        public RPCResponse setStatus(String status) {
            this.status = status;
            return this;
        }
    
        public String getMessage() {
            return message;
        }
    
        public RPCResponse setMessage(String message) {
            this.message = message;
            return this;
        }
    
        public Object getData() {
            return data;
        }
    
        public RPCResponse setData(Object data) {
            this.data = data;
            return this;
        }
    }
    
  3. RpcProvider

    package matlab.engine.remote.rpc.core;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Method;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * 用来创建RPC服务
     *
     * @author shirukai
     */
    public class RpcProvider {
        private static final Logger LOG = LoggerFactory.getLogger(RpcProvider.class);
    
        public static <T> void start(int port, T ref) {
            try {
                LOG.info("The RPC Server is starting, address:{}, bind:{}", InetAddress.getLocalHost().getHostAddress(), port);
                ServerSocket listener = new ServerSocket(port);
                while (true) {
                    Socket socket = listener.accept();
                    // 接收数据并进行反序列化
                    ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
    
                    // 获取请求对象
                    Object object = objectInputStream.readObject();
    
                    if (object instanceof RPCRequest) {
                        RPCRequest request = (RPCRequest) object;
                        LOG.info("Received request:{}", request);
                        // 处理请求
                        RPCResponse response = handleRequest(ref, request);
                        // 将结果返回给客户端
                        LOG.info("Send response to client.{}", response);
                        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                        objectOutputStream.writeObject(response);
                    }
                    socket.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        private static <T> RPCResponse handleRequest(T ref, RPCRequest request) {
            RPCResponse response = new RPCResponse();
            try {
                LOG.info("The server is handling request.");
                Method method = ref.getClass().getMethod(request.getMethodName(), request.getParameterTypes());
                Object data = method.invoke(ref, request.getParameters());
                response.setData(data);
            } catch (Exception e) {
                response.setStatus(RPCResponse.FAILED).setMessage(e.getMessage());
            }
            return response;
        }
    }
    
  4. RPCConsumer

    package matlab.engine.remote.rpc.core;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.Socket;
    
    /**
     * 用来创建PRC客户端
     *
     * @author shirukai
     */
    public class RPCConsumer {
    
        @SuppressWarnings("unchecked")
        public static <T> T get(String host, Integer port, Class<?> interfaceClass) {
            // 实例化RPC代理处理器
            RPCInvocationHandler handler = new RPCInvocationHandler(host, port);
            return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, handler);
        }
    
        private static class RPCInvocationHandler implements InvocationHandler {
            private final String host;
            private final Integer port;
    
            public RPCInvocationHandler(String host, Integer port) {
                this.host = host;
                this.port = port;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 构建请求对象
                RPCRequest rpcRequest = new RPCRequest();
                rpcRequest.setMethodName(method.getName()).setParameterTypes(method.getParameterTypes()).setParameters(args);
                // 使用客户端发送请求
                RPCResponse response = this.send(rpcRequest);
    
                // 响应成功返回结果
                if (RPCResponse.SUCCEED.equals(response.getStatus())) {
                    return response.getData();
                }
                throw new RuntimeException(response.getMessage());
            }
    
            public RPCResponse send(RPCRequest rpcRequest) throws Exception {
    
                Socket socket = new Socket(host, port);
    
                //请求序列化
                ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
    
                //将请求发给服务提供方
                objectOutputStream.writeObject(rpcRequest);
    
                // 将响应体反序列化
                ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
    
                Object response = objectInputStream.readObject();
                if (response instanceof RPCResponse) {
                    return (RPCResponse) response;
                }
                throw new RuntimeException("Return error");
            }
        }
    }
    
    
3.2.1.1 服务端

matlab.engine.remote.rpc.server下创建RPCServer

package matlab.engine.remote.rpc.server;

import matlab.engine.remote.common.Server;
import matlab.engine.remote.rpc.core.RpcProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * RPC服务端
 *
 * @author shirukai
 */
public class RPCServer<T> extends Server<T> {
    private static final Logger LOG = LoggerFactory.getLogger(Server.class);

    public RPCServer(Integer port, T instance) {
        super(port, instance);
    }

    @Override
    public void start() throws Exception {
        LOG.info("Starting the RPC-based Matlab engine...");
        LOG.info("Listening port: {}.", port);
        LOG.info("Service backend: {}.", instance.getClass());
        RpcProvider.start(port, instance);
    }
}

服务端创建完成之后,在ServerFactory中添加创建RPC服务端的代码

    public static <T> Server<T> createRPCServer(Integer port, T instance) {
        return new RPCServer<>(port, instance);
    }
3.2.1.2 客户端

matlab.engine.remote.rpc.client下创建RPCClient

package matlab.engine.remote.rpc.client;

import matlab.engine.remote.common.Client;
import matlab.engine.remote.rpc.core.RPCConsumer;

/**
 * PRC客户端
 *
 * @author shirukai
 */
public class RPCClient extends Client {
    public RPCClient(String host, Integer port) {
        super(host, port);
    }

    @Override
    public <T> T get(Class<T> interfaceClass) throws Exception {
        return RPCConsumer.get(host, port, interfaceClass);
    }
}

客户端创建完成之后,在ClientFactory中添加创建RPC客户端的代码

    public static Client createRPCClient(String host, Integer port) {
        return new RPCClient(host, port);
    }
3.2.1.3 运行验证
  1. 编写服务端示例

    package matlab.engine.remote.example;
    
    import matlab.engine.remote.common.Server;
    import matlab.engine.remote.common.ServerFactory;
    import matlab.engine.remote.service.MatlabEngineServiceImpl;
    
    
    /**
     * @author shirukai
     */
    public class RPCEngineServerExamples {
        public static void main(String[] args) throws Exception {
            MatlabEngineServiceImpl service = new MatlabEngineServiceImpl();
            Server<MatlabEngineServiceImpl> server = ServerFactory.createRPCServer(10992, service);
            server.start();
        }
    }
    
    
  2. 编写客户端示例

    package matlab.engine.remote.example;
    
    import matlab.engine.remote.api.MatlabEngineService;
    import matlab.engine.remote.common.Client;
    import matlab.engine.remote.common.ClientFactory;
    
    /**
     * @author shirukai
     */
    public class RPCEngineClientExamples {
        public static void main(String[] args)throws Exception {
            Client client = ClientFactory.createRPCClient("192.168.66.212", 10992);
            MatlabEngineService service = client.get(MatlabEngineService.class);
    
            double[] x = {2.0, 4.0, 6.0};
            double[] res = service.feval("sqrt", x);
            for (double e : res) {
                System.out.println(e);
            }
        }
    }
    
    
  3. 运行服务端示例
    https://cdn.jsdelivr.net/gh/shirukai/images/20220321170523.png

  4. 运行客户端示例
    https://cdn.jsdelivr.net/gh/shirukai/images/20220321170623.gif

3.3 完整代码

Matlab Engine调用的完整代码也提交到github上了:https://github.com/shirukai/matlab-engine-java.git

4 总结

Java 本地及远程调用Matlab的两种实现方案都已经介绍完了,简单总结一下优缺点。

基于MCR的Jar类库

优点:运行环境不依赖Matlab,Runtime不需要Lincese授权,依赖环境安装包较小,可以在容器运行,生成的类库中自带远程调用的实现。

缺点:只能运行函数,而且需要对函数进行单独打包

Matlab Engine API

优点:调用灵活,可以运行函数以及脚本,不需要单独打包

缺点:需要自己进行远程调用的实现,运行环境需要安装matlab,docker支持不友好。

  • 17
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Akka 远程调用的实现基于 Akka Remoting 模块。以下是一个简单的示例: 首先,需要在项目中添加 Akka Remoting 依赖: ```xml &lt;dependency&gt; &lt;groupId&gt;com.typesafe.akka&lt;/groupId&gt; &lt;artifactId&gt;akka-remote_2.11&lt;/artifactId&gt; &lt;version&gt;2.5.9&lt;/version&gt; &lt;/dependency&gt; ``` 然后,定义一个远程 Actor,它可以通过网络接收和处理消息: ```java import akka.actor.AbstractActor; import akka.event.Logging; import akka.event.LoggingAdapter; public class RemoteActor extends AbstractActor { private LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); @Override public Receive createReceive() { return receiveBuilder() .match(String.class, message -&gt; { log.info(&quot;Received message: {}&quot;, message); getSender().tell(&quot;Hello from remote actor&quot;, getSelf()); }) .build(); } } ``` 接下来,在本地 Actor 中创建一个远程 Actor 的引用,并向其发送消息: ```java import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import akka.pattern.Patterns; import scala.concurrent.Await; import scala.concurrent.duration.Duration; import java.util.concurrent.TimeUnit; public class LocalActor { public static void main(String[] args) throws Exception { ActorSystem system = ActorSystem.create(&quot;MySystem&quot;); // 创建远程 Actor 的引用 ActorRef remoteActor = system.actorOf(Props.create(RemoteActor.class), &quot;remoteActor&quot;); // 向远程 Actor 发送消息,并等待其回复 String message = &quot;Hello from local actor&quot;; Object response = Patterns.ask(remoteActor, message, 5000).toCompletableFuture().get(5000, TimeUnit.MILLISECONDS); System.out.println(&quot;Received response: &quot; + response); system.terminate(); } } ``` 在上面的代码中,我们使用 `Patterns.ask` 方法向远程 Actor 发送一条消息,并等待其回复。该方法返回一个 `Future` 对象,我们可以使用 `toCompletableFuture()` 方法将其转换为 Java 8 的 `CompletableFuture` 对象,从而方便地使用 `get` 方法等待结果。 以上就是一个简单的 Akka 远程调用的示例。在实际应用中,还需要配置 Akka Remoting 的一些参数,例如远程 Actor 的地址、端口等。具体配置方法可以参考 Akka 官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值