自己简单写了个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. Go 获取键盘输入,进制转换

    #### Go 获取键盘输入,进制转换 最近爱上<<珂矣的心灵独语>> 连续听一下礼拜也不觉得厌: 喜欢她的宁静与安然,喜欢她的坦荡与欢喜,喜欢她的禅意与智慧; ***撑着一苇 ...

  2. 沁恒CH32F103C8T6(二): Linux PlatformIO环境配置, 示例运行和烧录

    目录 沁恒CH32F103C8T6(一): Keil5环境配置,示例运行和烧录 沁恒CH32F103C8T6(二): Linux PlatformIO环境配置, 示例运行和烧录 StdPeriphLi ...

  3. py笔记第一篇

    #!/usr/bin/python #coding=utf-8 #@rename file #@2019/11/27 import os ls = os.rename('/root/tigergao. ...

  4. Java 后台线程介绍

    一  是啥? package com.aaa.threaddemo; /* * 一 Java后台线程? * 守护线程--也称"服务线程",他是后台线程, * 它有一个特性,即为用户 ...

  5. X000101

    P3879 [TJOI2010]阅读理解 考虑用 Trie 解决 #include<stdio.h> #include<bitset> #include<string.h ...

  6. Linux之shell入门

    一.编写规范 代码规范: #!/bin/bash [指定告知系统当前这个脚本要使用的shell解释器] shell相关指令 文件命名规范: 文件名.sh .sh是linux下bash shell 的默 ...

  7. chmod 权限-rw-r--r--表示什么含义

    感谢原文作者:bugcoder321 原文链接:https://blog.csdn.net/li_canhui/article/details/89452134 在linux中,有时候可以看到一个文件 ...

  8. htc 简单的移动效果

    转载请注明来源:https://www.cnblogs.com/hookjc/ 1.创建 HTC 文件的架构.一个标准的 HTC 文件含有一个 SCRIPT 块和一对可选的 COMPONENT 标记. ...

  9. Java线程--CompletionService使用

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871911.html Java线程--CompletionService使用 public ...

  10. 一站式超全JavaScript数组方法大全

    一站式JavaScript数组方法大全(建议收藏) 方法一览表 详细操作 本人总结了JavaScript中有关数组的几乎所有方法(包含ES6之后新增的),并逐一用代码进行演示使用,希望可以帮助大家! ...