说实话我每次看到RMI的流程我都觉得脑袋疼。而且分析完了都不记得分析了个什么鸟。
或许这次会好一点。
RMI远程类调用
RMI的作用就是客户端调用服务端的远程类。
我们开两个项目,一个做客户端,一个做服务端。用jdk8u65
RMIServer
定义一个继承了Remote的接口RemoteInterface
importjava.rmi.Remote;
importjava.rmi.RemoteException;
publicinterfaceRemoteInterfaceextendsRemote{
publicStringsayHello(Stringname) throwsRemoteException;
}
一个实现该接口的类RemoteImpl,需要继承UnicastRemoteObject
importjava.rmi.RemoteException;
importjava.rmi.server.UnicastRemoteObject;
publicclassRemoteImplextendsUnicastRemoteObjectimplementsRemoteInterface{
protectedRemoteImpl() throwsRemoteException{
}
publicStringsayHello(Stringname) {
System.out.println("Hello "+name);
return"Hello "+name;
}
}
RemoteImpl必须加入一个调用了父类构造函数的构造函数。
这里的无参构造函数实际上会自动向第一行插入一条隐式的
super()
,如下。而不写构造函数是自动生成无参构造函数,不满足调用super(args...);
的要求,所以报错。protectedRemoteImpl() throwsRemoteException{
super();
}
向外开放服务的RMIServer。需要LocateRegistry.createRegistry(1099),开放注册中心到1099端口。并把类绑定到注册中心。
importjava.rmi.AlreadyBoundException;
importjava.rmi.RemoteException;
importjava.rmi.registry.LocateRegistry;
importjava.rmi.registry.Registry;
publicclassRMIServer{
publicstaticvoidmain(String[] args) throwsRemoteException, AlreadyBoundException{
RemoteInterfaceremoteImpl=newRemoteImpl();
Registryregistry=LocateRegistry.createRegistry(1099);
registry.bind("remoteImpl", remoteImpl);
}
}
RMIClient
需要一个相同的接口RemoteInterface
importjava.rmi.Remote;
importjava.rmi.RemoteException;
publicinterfaceRemoteInterfaceextendsRemote{
publicStringsayHello(Stringname) throwsRemoteException;
}
使用服务端远程类的,客户端RMIClient
importjava.rmi.NotBoundException;
importjava.rmi.RemoteException;
importjava.rmi.registry.LocateRegistry;
importjava.rmi.registry.Registry;
publicclassRMIClient{
publicstaticvoidmain(String[] args) throwsRemoteException, NotBoundException{
Registryregistry=LocateRegistry.getRegistry("127.0.0.1", 1099);
RemoteInterfaceremoteImpl=(RemoteInterface) registry.lookup("remoteImpl");
System.out.println(remoteImpl.sayHello("RMI"));
}
}
先开服务端,再开客户端。客户端就能调用到服务端上的方法sayHello
源码分析
移步视频:
https://www.bilibili.com/video/BV1L3411a7ax?p=1&vd_source=732f44595cd3e361ab78ff559f3c5ab5
此处概述+只给利用点和利用方式,因为分析了也记不住。我直接偷偷分析,挂上来我自己都不看。
偷个包浆RMI通信老图
先从服务端开始
服务端
RemoteInterface remoteImpl = new RemoteImpl();
从UnicastServerRef一路跟到了TCPEndpoint.getLocalEndpoint,获取了IP
在UnicastServerRef.exportObject完成了创建远程对象的主要逻辑,前面都是层层封装,没什么好看的。
生成了一个stub并赋值,这是服务端stub
实际上,赋值调用的Util.creatProxy()内部,就是生成了一个代理类。等于说stub就是个代理类,如下
handler来自RemoteObjectInvocationHandler,看到该类满足代理handler的要求,继承了InvocationHandler
还记得动态代理吗,调用代理类的方法会自动走进handler的invoke方法(虽然这里没调用,但是为后面你使用sayHello的时候做了铺垫)
这里RemoteObjectInvocationHandler.invoke就是加了个判断的普通invoke执行方法
下面一堆调用,从ref.exportObject跟到TCPTransport.exportObject。listen()方法开始监听。跟进去看看
从ep中提取了Endpoint(翻译为终端)和端口。ep就是之前TCPEndpoint.getLocalEndpoint生成的。然后调用了newServerSocket
在newServerSocket中,createServerSocket新建了一个Websocket。并且监听端口为0,会调用setDefaultPort获取端口。从前面的调试信息也可以看到,一路过来端口都是默认的0
setDefaultPort内部,获取了所有的本地未开放端口,并异步循环取最后一个端口。就是随机取一个端口啦
从这里开始,port就有值了。
继续运行到UnicastServerRef.exportObject。在get不到值的时候会向map里put键值。
虽然是put进去了,但是只有个类名。方法名还需要跳转到UnicastServerRef$HashToMethod_Maps.computeValue中获取
从接口中循环获取方法名,设为可访问,put进map中
最后我们生成的remoteImpl对象,有LiveRef包含的host和一个随机的端口号,还有装填了方法的hashToMethod_Map。这些都封装在UnicastServerRef的一个对象中。创建了一个代理类stub,但是return时并没有存储,只是写了个逻辑在这。
但是服务端开随机的端口号,客户端怎么知道这个端口号来获取类呢?下一步就是解决这个问题
Registry registry = LocateRegistry.createRegistry(1099);
新建了一个指定端口为1099空的LiveRef对象
在随后的setup函数中,同样的来到UnicastServerRef.exportObject完成代理对象stub的创建。这是注册端stub
注意,这里创建stub时,跟进Util.creatProxy->stubClassExists()。
如果存在remoteClass_Stub类(在这里就是RegistryImpl_Stub类),返回true
可以看到该类是存在的
在creatStub创建了RegistryImpl_Stub类