1)什么是 rmi
2)简单的实现 rmi
3)rmi 原理
4)手写 rmi 框架
进群: 697699179 可以获取 Java 各类入门学习资料!
这是我的微信公众号[编程 study] 各位大佬有空可以关注下, 每天更新 Java 学习方法, 感谢!
学习中遇到问题有不明白的地方, 推荐加小编 Java 学习群: 697699179 内有视频教程 , 直播课程 , 等学习资料, 期待你的加入
首先谈下什么 RPC?
Remote procedure call protocal 远程过程调用协议
不用知道具体细节, 调用远程系统中类的方法, 就跟调用本地方法一样.
RPC 协议其实是一种规范.
包括 Dubbo,Thrift,rmi,webservice,hessain
网络协议和网络 IO 对于调用端和服务端来说是透明的.
一个 RPC 框架应该包含的要素:
RMI 概述
rmi(remote method invocation) 远程方法调用
可以认为是 RPC 的 java 版本
RMI 使用的是 JRMP(JAVA Remote Messageing Protocol), 可以说 JRMP 是专门为 java 定制的通信协议, 所以它是纯 java 的分布式解决方案.
怎么实现一个 RMI 程序
1)创建远程接口并且继承 java.rmi.Remote 接口
packagecom.llf.rmidemo;importjava.rmi.Remote;importjava.rmi.RemoteException;publicinterfaceSayHelloextendsRemote{publicStringsayHello(String name)throwsRemoteException;}
2)实现我们这个远程接口并且继承 UnicastRemoteObject
packagecom.llf.rmidemo;importjava.rmi.RemoteException;importjava.rmi.server.UnicastRemoteObject;publicclassSayHelloImplextendsUnicastRemoteObjectimplementsSayHello{protectedSayHelloImpl()throwsRemoteException{}@OverridepublicStringsayHello(Stringname)throwsRemoteException{return"Hello LLF -->"+name;}}
3)创建服务器程序 调用 createRegistry 方法注册远程对象
package com.llf.rmidemo;importjava.NET.MalformedURLException;importjava.rmi.AlreadyBoundException;importjava.rmi.Naming;importjava.rmi.RemoteException;importjava.rmi.registry.LocateRegistry;publicclassHelloServer{publicstaticvoid main(String[] args) {try{SayHellohello=newSayHelloImpl();LocateRegistry.createRegistry(8888);try{Naming.bind("rmi://localhost:8888/sayhello", hello);System.out.println("Server start success!");}catch(MalformedURLExceptione) {e.printStackTrace();}catch(AlreadyBoundExceptione) {e.printStackTrace();}}catch(RemoteExceptione) {e.printStackTrace();}}}
4)创建客户端程序
package com.llf.rmidemo;importjava.NET.MalformedURLException;importjava.rmi.Naming;importjava.rmi.NotBoundException;importjava.rmi.RemoteException;publicclassHelloClient{publicstaticvoid main(String[] args) {try{SayHellohello=(SayHello)Naming.lookup("rmi://localhost:8888/sayhello");System.out.println(hello.sayHello("FXP"));}catch(MalformedURLExceptione) {e.printStackTrace();}catch(RemoteExceptione) {e.printStackTrace();}catch(NotBoundExceptione) {e.printStackTrace();}}}
结果:
自己去实现一个 RMI
1)编写服务器程序, 暴露监听, 可以使用 socket 2)编写客户端程序, 通过 IP 和端口连接到指定的服务, 并且把我们的数据做封装 (序列化) 3) 服务器端收到请求先反序列化在进行业务逻辑处理, 把返回结果序列化返回
rmi 框架调用时序图
rmi 源码分析
我们近乎的可以以如下的图来理解: a) stub 和 skeleton 这俩个身份都是作为代理存在, 客户端的称为 stub, 服务端的称为 skeleton, 通过这俩个对象屏蔽了远程方法调用的具体细节, 这俩个是必不可少的. b)Registry: 注册所, 提供了服务名到服务的映射.
结合这上面的图, 再以上面的 demo 代码, 我们来扒一扒 rmi 的底层源码 首先我们看提供服务的 server 方 createRegistry 方法
服务端先创建了一个 RegistryImpl 的对象, 然后做了一个安全校验, 这边我们不用关注, 重点是看 setup 方法.
进入到 RegistryImpl 类
然后进入到 UnicastServerRef 的 exportObject 方法 1)首先为传入的 RegistryImpl 创建一个代理对象 stub 2)把 UnicastServerRef 的 skeleton 对象设置为当前 RegistryImpl 对象 3)skeleton,stub,unicastserverRef 对象, id 和一个 boolean 构造了一个 target 对象
再往下追就是 export 的 exportObject 方法
主要是调用 listen 方法创建一个 serversocket, 启动一条线程等待客户端请求. 至此为止我们的服务端已经起了服务等待客户端连接了.
客户端
这边其实就是创建一个 stub 的代理对象
用代码来模拟 RMI 底层过程如下: 新建一个 User 的对象
packagecom.llf.rmi;publicclassUser{privateintage;publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age = age;}}
编写一个 Skeleton 类供客户端调用[这块是 rmi 定义出来屏蔽底层序列化及流连接的, 这边模拟写了下底层的序列化及流]
packagecom.llf.rmi;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.NET.ServerSocket;importjava.NET.Socket;//server 程序 publicclassUser_SkeletonextendsThread{privateUserServeruserServer;publicUser_Skeleton(UserServeruserServer) {this.userServer = userServer;}public void run() {ServerSocketserverSocket =null;ObjectInputStreamread =null;ObjectOutputStreamoos =null;Socketsocket=null;try{serverSocket =newServerSocket(8888); socket = serverSocket.accept();while(socket !=null) {read =newObjectInputStream(socket.getInputStream());Stringmethod = (String) read.readObject();if(method.equals("age")) {int age = userServer.getAge();oos =newObjectOutputStream(socket.getOutputStream());oos.writeInt(age);oos.flush();}}}catch(Exceptione) {e.printStackTrace();}finally{if(serverSocket !=null) {try{oos.close();read.close();socket.close();serverSocket.close();}catch(IOExceptione) {e.printStackTrace();}}}}}
写一个 stub
packagecom.llf.rmi;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.NET.Socket;importjava.NET.UnknownHostException;publicclassUser_StubextendsUser{privateSocketsocket;publicUser_Stub() {try{socket=newSocket("localhost",8888);}catch(UnknownHostExceptione) {e.printStackTrace();}catch(IOExceptione) {e.printStackTrace();}}public int getAge(){ObjectOutputStreamoos=null;ObjectInputStreamois=null;try{oos=newObjectOutputStream(socket.getOutputStream());oos.writeObject("age");oos.flush();ois=newObjectInputStream(socket.getInputStream());returnois.readInt();}catch(IOExceptione) {e.printStackTrace();}finally{try{ois.close();oos.close();}catch(IOExceptione) {// TODO Auto-generated catch blocke.printStackTrace();}}return0;}}
编写服务器代码
packagecom.llf.rmi;publicclassUserServerextendsUser{public static void main(String[] args) {UserServerserver=newUserServer();server.setAge(18);// 模拟 rmi 生成的 skeleton 代理对象 User_Skeletonuser_Skeleton=newUser_Skeleton(server);user_Skeleton.start();}}
编写客户端代码
packagecom.llf.rmi;publicclassUserClient{publicstaticvoidmain(String[] args){User user=newUser_Stub();intage=user.getAge();System.out.println(age);}}
来源: http://www.jianshu.com/p/5e49b582d19d