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

自己简单写了个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全栈学习路线、学习资源和面试题一条龙

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

免费下载经典编程书籍

更多优质文章和资源👇

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

posted @ 2022-03-05 23:59  渊渟岳  阅读(533)  评论(1编辑  收藏  举报