自己简单写了个Java RMI(远程方法调用)的实现案例。

为了更好理解RMI(远程方法调用)、序列化的意义等等,花费三天多的时间肝了一个Java RMI的实现案例。

!!!高能预警!!!

代码量有点大,先附上了简图用于理解

整个过程分为两大步

  • 第一步--注册过程:客户端通过指定路由获取注册中心指定的远程客户端对象
  • 第二部--服务调用过程:客户端通过远程客户端对象访问远程服务端(代理服务)从而访问到真实服务的实现

调整为舒适的姿势,慢慢看…… 废话少说,上代码!!!

1.定义远程标记接口

面向接口编程,具体作用看后面的代码怎么使用

// 标记接口:直接或间接实现MyRMI接口将获得远程调用的能力
public interface MyRMI{
}

2.编写RMI 服务注册中心

注册中心类:用于注册服务和获取服务,核心是hashMap路由表对象

/**
 * 注册中心:维护服务发布的注册表
 */
public class MyRMIRegistry {
    // 默认端口
    public final int REGISTRY_PORT = 10099;
    private String host;
    private int port;
    private Map<String, MyRMI> bindings;     public MyRMIRegistry(int port){
        this.port = port;
    }
    public MyRMIRegistry(String host, int port){
        this.host=host;
        this.port=port;
    }     public void createRegistry(String serverName,MyRMI myRMI){
        // 注册服务,并开启服务
        this.bindings = new HashMap<>();
        String host = null;
        try {
            host = Inet4Address.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        // 路由规则可自行定义,只要能确保Key唯一即可
        String binding = "myrmi://"+host+":"+port+"/"+serverName;
        this.bindings.put("myrmi://"+host+":"+port+"/"+serverName,myRMI);
        System.out.println("注册的服务有:"+bindings.keySet().toString());
        MyRMIRegistryServer myRMIRegistryServer = new MyRMIRegistryServer(this.port, this.bindings);
        Executors.newCachedThreadPool().submit(myRMIRegistryServer); // 线程池启动服务     }     public MyRMI getRegistry(String serverName){
        Socket socket = null;
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        MyRMI myRMI = null;
        // 通过
        try {
            socket = new Socket(host, port);
            out = new ObjectOutputStream(socket.getOutputStream());
            out.writeObject("myrmi://"+host+":"+port+"/"+serverName);
            in = new ObjectInputStream(socket.getInputStream());
            myRMI = (MyRMI)in.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return myRMI;
    }
}

RMI 注册中心获取服务的线程:启动注册中心服务,等待客户端来获取路由表中的远程客户端

/**
 * RMI注册中心获取服务线程
 */
public class MyRMIRegistryServer implements Runnable {
    private int port;
    private Map<String, MyRMI> bindings;
    public MyRMIRegistryServer(Integer port,Map<String, MyRMI> bindings){
        this.port = port;
        this.bindings = bindings;
    }     @Override
    public void run() {
        ServerSocket serverSocket = null;
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
            serverSocket = new ServerSocket(this.port);
            while(true){
                Socket socket = serverSocket.accept();
                in = new ObjectInputStream(socket.getInputStream());
                out = new ObjectOutputStream(socket.getOutputStream());
                // 看看客户端想要什么服务
                String serverName = (String)in.readObject();
                Iterator iterator = bindings.keySet().iterator();
                while (iterator.hasNext()){
                    String key = (String) iterator.next();
                    if(serverName.equals(key)){
                        // 给客户端响应服务对象
                        MyRMI myRMI = bindings.get(key);
                        out.writeObject(myRMI);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            // 异常后进入
            try {
                if (out!=null)  out.close();
                if (in!=null)   in.close();
                if (serverSocket!=null) serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }     } }

3.定义要发布的服务接口

需要提供RMI服务的接口,必须继承自定义的MyRMI标记接口

/**
 * 服务接口
 */
public interface Hello extends MyRMI {
    public String sayHello(String name);
}

4.服务用到的实体类

/**
 * 对象数据类:Person
 */
public class Person implements Serializable {
    // 序列化版本UID
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private String sex;     public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Person{" + "name='" + name + ", age=" + age + ", sex='" + sex + '}';
    }
    public Person() {
    }
    public Person(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

5.实现要发布的服务接口

/**
 * 对外提供的服务实现
 */
public class HelloImpl implements Hello {
    private static File file = new File("D:/HelloRMI.txt");
    private static List<Person> list = new ArrayList<>();     @Override
    public String sayHello(String name) {
        String result = "没有获取到"+name+"的信息";
        try {
            List<Person> personList = readList();
            for(Person person:personList){
                if (person.getName().equals(name)){
                    result = "Hello , welcome to the RMI! "
                            + "姓名:"+name + " 年龄:"+person.getAge()+" 性别:"+person.getSex();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }         return result;
    }     /**
     * 生成数据,为测试做准备
     * @param args
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //数据准备:集合类都实现了序列化接口Serializable
        list.add(new Person("张三", 38, "男"));
        list.add(new Person("李四", 38, "男"));
        list.add(new Person("如花", 18, "女"));
        // 持久化对象数据
        writerList(list);
        // 查询持久化对象数据
        List<Person> personList = readList();
        System.out.println("遍历持久化对象数据>");
        for (Person person : personList) {
            System.out.println(person);
            if (person.getAge() == 38) {
                person.setAge(18);
            }
        }     }     public static void writerList(List<Person> list) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
        objectOutputStream.writeObject(list);
        objectOutputStream.close();
    }     public static List<Person> readList() throws IOException, ClassNotFoundException {
        // 读取普通文件反序列化
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        List<Person> personList = (List<Person>) objectInputStream.readObject();
        objectInputStream.close();
        return personList;
    }
}

6.远程客户端的线程类

用于自动生成服务接口(继承了MyRMI标记接口)的远程客户端类:这个类原本是通用类实现,为了方便实现,就直接实现Hello接口了

/**
 * 远程客户端的线程类的生成:
 *      为了方便实现,这边直接实现服务接口编写
 */
public class HelloClientThread implements Hello,Serializable {
    // 序列化版本UID
    private static final long serialVersionUID = 1L;
    private Map<String,Object> map = new HashMap<>(); // 报文对象:方法名和参数对象
    private String ip;
    private int port;     public HelloClientThread(String ip, int port){
        this.ip = ip;
        this.port = port;
    }     @Override
    public String sayHello(String name) {
        map.put("sayHello",name);
        String result = (String)send();
        return result;
    }     private Object send(){
        Object o =null;
        Socket socket = null;
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
            socket = new Socket(ip, port);
            out = new ObjectOutputStream(socket.getOutputStream());
            in = new ObjectInputStream(socket.getInputStream());
            // 告诉服务端我要调用什么服务
            out.writeObject(map);
            // 获取服务实现对象
            o = in.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                if (out!=null)  out.close();
                if (in!=null)   in.close();
                if (socket!=null) socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return o;
    }
}

7.远程服务端的线程类

用于自动生成服务接口(继承了MyRMI标记接口)的远程服务端类:这个类原本也是通用类实现,为了方便实现,部分代码尚未做到解耦通用

/**
 * 远程服务端的线程类的生成:
 *      为了方便实现,这边直接实现服务线程类
 */
public class HelloServerThread implements Runnable {
    private Integer port;
    private MyRMI myRMI;
    public HelloServerThread(Integer port, MyRMI myRMI){
        this.port = port;
        this.myRMI = myRMI;
    }     @Override
    public void run() {
        ServerSocket serverSocket = null;
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        try {
            serverSocket = new ServerSocket(this.port);
            while(true){
                Socket socket = serverSocket.accept();
                in = new ObjectInputStream(socket.getInputStream());
                out = new ObjectOutputStream(socket.getOutputStream());
                // 看看客户端想要什么服务
                Map map = (Map)in.readObject();
                Iterator iterator = map.keySet().iterator();
                while (iterator.hasNext()){
                    String key = (String) iterator.next();
                    if("sayHello".equals(key)){
                        // 给客户端响应服务对象
                        Hello hello = (Hello)myRMI;
                        String result = hello.sayHello((String) map.get(key));
                        out.writeObject(result);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            // 异常后进入
            try {
                if (out!=null)  out.close();
                if (in!=null)   in.close();
                if (serverSocket!=null) serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }     }
    
}

8.远程客户端生成和远程服务端生成和启动的类

/**
 * 远程客户端生成和远程服务端生成和启动的类
 */
public class RemoteSocketObject{
    // 默认端口
    private int port=18999;     // 指定远程通讯端口和代理服务
    public MyRMI createRemoteClient(MyRMI myRMI,int port){
        if (port > 0)
            this.port=port;         MyRMI myRMIClient = null;
        try {
            // 生成底层通讯服务端,并启动
            HelloServerThread helloServerThread = new HelloServerThread(this.port, myRMI);
            Executors.newCachedThreadPool().submit(helloServerThread); // 线程池启动服务
            // 生成底层通讯客户端
            String localHost = Inet4Address.getLocalHost().getHostAddress();
            System.out.println("host="+localHost+",port="+this.port);
            myRMIClient= new HelloClientThread(localHost, this.port);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return myRMIClient;
    }
    
}

9.服务发布类

/**
 * RMI 服务发布类
 */
public class HelloServer {
    public static void main(String[] args) {         System.out.println("Create Hello Remote Method Invocation...");
        // 实例化一个Hello
        Hello hello = new HelloImpl();
        // 转换成远程服务,并提供远程客户端
        Hello remoteClient = (Hello)new RemoteSocketObject().createRemoteClient(hello, 0);
        // 将服务实现托管到Socket服务
        MyRMIRegistry myRMIRegistry = new MyRMIRegistry(16000);
        // 开启线程服务
        myRMIRegistry.createRegistry("Hello",remoteClient);     }
}

10.客户端测试类

/**
 * 客户端测试类
 *      客户端只知道服务接口、服务发布的地址和服务发布的名称
 */
public class TestHello {
    public static void main(String[] args) {
        // 注意不是127.0.0.1,不知道host的看server端启动后打印的信息
        // 端口16000是注册中心的端口,底层代理服务的端口客户端无需知道
        MyRMIRegistry client = new MyRMIRegistry("192.168.233.1", 16000);
        Hello hello = (Hello) client.getRegistry("Hello");
        System.out.println(hello.sayHello("张三"));
    }
}

11.总结

所有代码整下来,在真正的场景中:

客户端只知道:TestHello类、Hello接口定义、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中只知道Key,不知道具体值);

服务端只知道:Hello接口、HelloImpl服务实现类、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中知道Key和具体值);

关于其他的代码实现都是无感的,为了简单实现远程客户端和远程服务端,将服务接口耦合到两者上了,未做到解耦通用。

Java往期文章

Java全栈学习路线、学习资源和面试题一条龙

我心里优秀架构师是怎样的?

免费下载经典编程书籍

更多优质文章和资源

原创不易、三联支持:分享,点赞,在看

自己写了个Java RMI(远程方法调用)的实现案例的更多相关文章

  1. JAVA RMI远程方法调用简单实例[转]

    RMI的概念 RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制.使用这种机制,某一台计算机上的对象可以调用另外 一台 ...

  2. JAVA RMI远程方法调用简单实例(转载)

    来源:http://www.cnblogs.com/leslies2/archive/2011/05/20/2051844.html RMI的概念 RMI(Remote Method Invocati ...

  3. Java RMI(远程方法调用) 实例与分析 (转)

    目的: 通过本文,可以加深对Java RMI的理解,知道它的工作原理,怎么使用等. 也为了加深我自己的理解,故整理成文.不足之处,还望指出. 概念解释: RMI(RemoteMethodInvocat ...

  4. Java RMI(远程方法调用) 实例与分析

    目的: 通过本文,可以加深对Java RMI的理解,知道它的工作原理,怎么使用等. 也为了加深我自己的理解,故整理成文.不足之处,还望指出. 概念解释: RMI(RemoteMethodInvocat ...

  5. java rmi远程方法调用实例

    RMI的概念 RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制.使用这种机制,某一台计算机上的对象可以调用另外一台计 ...

  6. Java RMI之HelloWorld经典入门案例

    Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方 ...

  7. Java RMI 框架_远程方法调用(2016-08-16)

    概念: Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可 ...

  8. Java的RMI远程方法调用实现和应用

    最近在学习Dubbo,RMI是很重要的底层机制,RMI(Remote Method Invocation)远程方法调用是一种计算机之间利用远程对象互相调用实现双方通讯的一种通讯机制.使用这种机制,某一 ...

  9. Java RMI(远程方法调用)开发

    参考 https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-arch2.html http://www.cnblogs.com/wxi ...

随机推荐

  1. Python与Javascript相互调用超详细讲解(四)使用PyNode进行Python与Node.js相互调用项(cai)目(keng)实(jing)践(yan)

    目录 前提 安装 使用 const pynode = require('@fridgerator/pynode')的时候动态链接错误 ImportError: math.cpython-39-x86_ ...

  2. Java语法专题1: 类的构造顺序

    合集目录 Java语法专题1: 类的构造顺序 问题 下面的第二个问题来源于Oracle的笔试题, 非常经典的一个问题, 我从07年开始用了十几年. 看似简单, 做对的比例不到2/10. 描述一下多级继 ...

  3. contos 6.9 和 centos7 配置docker?

    一.contos 6.9 配置docker? 1.检查centos的内核,因为目前docker的版本所支持的centos最低内核版本为2.4 // uname -r // 2.6.32-696.el6 ...

  4. X000011

    P1890 gcd区间 \(\gcd\) 是满足结合律的,所以考虑用 ST 表解决 时间复杂度 \(O((n\log n+m)\log a_i)\) 考虑到 \(n\) 很小,你也可以直接算出所有的区 ...

  5. Maven警告解决:Using platform encoding (UTF-8 actually)

    感谢原文作者:Scorpip_cc 原文链接:https://www.jianshu.com/p/9c8c01f6bebc 执行Maven Install打包的时候,提示以下警告信息: [WARNIN ...

  6. java基础之抽象类的介绍

    抽象类的特点: 1.当方法只有声明没有具体实现的时候,需要用abstract修饰符修饰.抽象方法必须定义在抽象类当中,所以抽象类也需要用abstract修饰 2.抽象类不可以被实例化,为什么呢?   ...

  7. memcached 测试代码

    转载请注明来源:https://www.cnblogs.com/hookjc/ #include<stdio.h> #include <iostream> #include & ...

  8. Java开发环境及工具安装配置

    Java开发环境及工具安装配置 Windows JDK 下载地址 https://www.oracle.com/java/technologies/javase-downloads.html 安装配置 ...

  9. 自定义的类实现copy操作

    1.自定义类实现copy操作 让类遵守NSCopying协议 实现 copyWithZone:方法,在该方法中返回一个对象的副本即可. 在copyWithZone方法中,创建一个新的对象,并设置该对象 ...

  10. HTTPStatus(状态码返回)详情

    1xx(临时响应) 表示临时响应并需要请求者继续执行操作的状态代码. 代码 说明 100 (继续) 请求者应当继续提出请求. 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分. 101 (切 ...