RMI
是Java编程语言里一种用于实现远程过程调用的应用程序编程接口。它使客户机上的运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能地简化远程接口对象的使用,简而言之就是为了调用远程方法。https://blog.csdn.net/u012291108/article/details/52863915
这个过程中涉及到对象的传输,所以会用到序列化和反序列化,以及JNDI(远程方法调用协议)
参考文章:
rmi详解:https://blog.csdn.net/weixin_44627989/article/details/93055791
stub(存根)和skeleton(框架):https://www.cnblogs.com/yin-jingyu/archive/2012/06/14/2549361.html
现在我们的需求是这样的:client想执行一个在远程机器上server的一个方法,如果我们手动去编写这些过程,socket网络编程或许是我们必须要面对的,由此便引入了stub和skeleton模型。
所有和网络相关的代码全部都由这两个部分来实现,这样一来,客户端和服务端就都不需要去处理网络相关的代码,,这样的逻辑结构
简单来说就是这样的一个逻辑
Client<->Stub<->socket<->skeleton<->server
在远程服务开启的时候,stub也没有办法知道server的域名和端口,这个时候就要用到RMIRegistry,server上会创建一个stub对象,然后把他注册到RMIRegistry,这样Client就能从RMIRegistry获取到对象,RMIRegistry会在1099端口导出自己,不设置的话默认是1099端口
调试代码
server端代码
server端接口
package test;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloService extends Remote {
public String sayHello() throws RemoteException;
//要发布的服务类的方法必须都throws RemoteException
//在Util中,创建代理对象时会checkMethod,存在没有throws RemoteException的则抛出IllegalArgumentException
}
server端实现代码
package test;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
//需要继承UnicastRemoteObject,因为Remote只是接口,UnicastRemoteObject是Remote的实现类
//还要实现HelloService这个接口
protected HelloServiceImpl() throws RemoteException {
super();
}
@Override
public String sayHello() throws RemoteException{
return "hello";
}
}
server端将端口开放出去
package test;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
//服务器端
public class Server {
final static String host = "127.0.0.1";
final static int port = 8080;
public static void main(String[] args) throws RemoteException, MalformedURLException {
HelloService helloService = new HelloServiceImpl();
Registry reg=LocateRegistry.createRegistry(port);
reg.rebind("rmi://"+host+":"+port+"/hello", helloService);//不写port默认是1099
System.out.println("服务启动...");
}
}
客户端代码
package test;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client {
public static void main(String[] args) throws MalformedURLException, RemoteException, NotBoundException {
HelloService helloService = (HelloService) Naming.lookup("rmi://127.0.0.1:8080/hello");
//默认端口1099
System.out.println(helloService.sayHello());
}
}
跟踪服务端启动执行流程
在HelloServoceImpl里面实现父类无参构造
进入UnicastRemoteObject方法
步入
步入UnicastServerRef()函数,其中会new一个UnicastServerRef对象,进入UnicastServerRef类,会发现其父类中包含一个LiveRef类型的属性
步入这个liveRef函数
TCPEndpoint对象就获得了Ip以及端口的属性
紧接着步入
这时会判断obj是否为UnicastRemoteObject类型,由于obj为自定义的HelloServiceImpl,继承了UnicastRemoteObject,因此if条件成立,接着步入sref对象的exportObject函数
可以看到会先获得对象的类属性,然后创建一个代理对象,继续查看源码,可以知道该代理对象类型为HelloServiceImpl,handler为RemoteObjectInvocationHandler,其中handler会包括上面创建的LiveRef对象(前面也知道了该对象中包含Endpoint等通信所需信息),因此可以判断在远程调用该对象时,客户端获取到的其实是该代理对象,再往下看,会生成一个Target对象,可以看到该Target对象包含了许多数据(导出输入的原始对象,创建的代理对象等)
后面这个target对象又被this.ref的exportObject方法导出
步入ref.exportObject方法,此时就是在TCPEndpoint这个类里面,在transport这个里面是有这些属性的,
跟进这个exportObject()函数,可以看到这个Listen函数,就是建立监听
接着跟进,在listen函数里面首先获得TCPEndpoint对象
然后获取到端口
后面就会开启一个socket,而且这个端口是随机的
继承这个父类构造函数的作用
UnicastRemoteObject基类的构造方法将远程对象发布到一个随机端口上,当然端口也可以指定
每一个对象都会有一个单独的socket,端口值随机
可以看到在执行完构造HelloServiceImpl的构造的时候,这个对象属性里面就有了ip和端口的信息
执行下一行代码
步入
步入setup函数,这里会执行exportObject()方法
跟进这个方法,这个方法就会返回stub存根,生成skeleton对象
总结
Registry reg=LocateRegistry.createRegistry(port);
reg.rebind("rmi://"+host+":"+port+"/hello", helloService);
上面这两行代码就是建立Registry对象,然后将对象和请求放进这个hashTable中去,当我们请求
rmi://127.0.0.1:8080/hello
就会对应的访问到这个对象
总结:
1、首先接口要继承Remote这个类,然后实现类要继承UnicastRemoteObject这个类,通过这个类的构造方法将远程对象发布到一个随机端口上
2、然后新建Registry对象,这个对象的作用有两个
①在指定端口设置外部访问
②相当于建立了一个注册表,通过键和值来进行访问指定对象的方法
这就是整体建立客户端的一个逻辑
客户端执行流程跟踪
客户端的这行代码返回的是RegistryImpl_Stub,也就是一个存根
传入域名和端口获取到服务端的RegistryImpl的代理
下周仔细研究一下Java RMI Registry各个版本安全问题