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

java

自己简单写了个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(远程方法调用)的实现案例 的全部内容, 来源链接: utcz.com/z/393712.html

回到顶部