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

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

    免费下载经典编程书籍

    更多优质文章和资源

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

  • 相关阅读:
    程序员的进阶课-架构师之路(2)-数组
    程序员的进阶课-架构师之路(1)-数据结构与算法简介
    ASP.NET开发实战——(三)第一个ASP.NET应用《MyBlog》
    ASP.NET开发实战——(二)为什么使用ASP.NET
    ASP.NET开发实战——(一)开篇-用VS创建一个ASP.NET Web程序
    asp.net core 系列 12 选项 TOptions
    Linux编程 1 (文件系统路径说明, 目录结构说明)
    asp.net core 系列 9 三种运行环境和IIS发布
    asp.net core 系列 6 MVC框架路由(下)
    asp.net core 系列 4 注入服务的生存期
  • 原文地址:https://www.cnblogs.com/dennyLee2025/p/15969994.html
Copyright © 2020-2023  润新知