一、开篇语
上一篇已经详细介绍了什么是RMI,RMI的作用,RMI框架的通信的过程是如何实现的。
现在我们带着疑问去解析RMI源码底层实现:
1、客户端和服务器端是如何创建的?
2、客户端和服务器端的信息是如何传输的?
3、RMI 有没有什么优点,有什么缺点?
源码阅读到什么地步?阅读到能解决自己的疑问,能放过自己的地步就足以。
二、源码解析
1、服务器端本地创建远程注册中心Registry,并把实例化的远程对象注册进去;
(1)远程对象的服务接口要继承Remote,而服务的具体实现类要继承UnicastRemoteObject;
(2)通过端口信息,在本地服务器创建或者导出一个注册服务实例。
LocateRegistry.createRegistry(port);
Naming.bind("rmi://localhost:"+port+"/sayHello",iSayHello);
我们先来了解LocateRegistry的作用:
通过源码可以看出它所提供的的接口的作用是:去获得/创建一个绑定指定服务器(包括本机)和特定端口的远程对象注册服务的的实例;
提供2种方式创建:
(1)、只提供port,默认方式创建
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
(2)、通过指定的端口,和指定套接字创建工厂创建。(分析到这里我们会发现RMI的通信肯定是基于TCP的,和Socket脱不了干系)
public static Registry createRegistry(int port,
RMIClientSocketFactory csf,
RMIServerSocketFactory ssf)
throws RemoteException
{
return new RegistryImpl(port, csf, ssf);
}
我们走进 new RegistryImpl(port);
public RegistryImpl(final int var1) throws RemoteException {
对于跨主机的访问,RMI加入了安全管理器(SecurityManager),那么也需要对应的安全策略文件;
if (var1 == 1099 && System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
最终实现
RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
return RegistryImpl.registryFilter(var0);
}));
return null;
}
}, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
} catch (PrivilegedActionException var3) {
throw (RemoteException)var3.getException();
}
} else {
最终实现
LiveRef var2 = new LiveRef(id, var1);
this.setup(new UnicastServerRef(var2, RegistryImpl::registryFilter));
}
}
最终实现在setup()里,点进去看;
LiveRef:是一个可clone的live对象引用,创建一个TCPEndpoint,这个为后面创建socket服务做准备;
new UnicastServerRef(var2, RegistryImpl::registryFilter)
构建一个远程服务引用对象参数;传入setup();其中registryFilter是过滤输入的对象是否支持java的序列化的作用;
private void setup(UnicastServerRef var1) throws RemoteException {
this.ref = var1;
var1.exportObject(this, (Object)null, true);
}
var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
创建远程代理对象,getClientRef()实现了InvocaltionHandler,动态代理内部方法回调方式,提供了TCP通信创建;
最终注册服务创建完成,把服务相关信息封装在Target上,并且暴露他的TCP端口,提供给客户端远程访问;
同时执行代码
his.ref.exportObject(var6);
创建了客户端的远程访问的注册中心的代理对象RemoteSub对象实例;
return createStub(var3, var1);
到这里服务器的启动已经完成,但是我们别忘记了,服务器端的socketserver是如何启动的:
他就是我上面说的动态代理 截图中的步骤2,invoke()方法里,创tcpconnection
通过源码发现通信是BIO而不是NIO。是阻塞的;
后面就是把远程访问代理对象绑定到请求的url上,其实服务器用的就是一个hashTable map集合来维护的;
客户端创建;
ISayHello iSayHello = (ISayHello) Naming.lookup("rmi://localhost:" + port + "/sayHello");
从注册服务中心获取远程访问的对象sub,服务器端会对应生成一个RomteSkeleton代理对象,负责和client的remoteSutub对象进行tcp通信;
到这里我们大概就了解RMI框架底层实现原理了;
个人总结:
RMI出来的非常的早,对现在分布式通信奠定了思想基础,虽然已经被淘汰了,但是掌握他的思想还是很重要的,只是基于java,不支持跨语言,服务注册只会注册到RMI Restory注册中心,如果注册中心挂掉了以后,因为不支持负载, 导致所有的客户端都不可以用了,序列化是java原生的序列化,效率不高;服务器底层采用 的是BIO,综合性能不是很高;不支持重试机制,如果网络异常,不会重试,而是直接抛异常;
三、手写RMI框架
TransMessage
TransMessage_Skeleton
TransMessage_Stub
TransMessageClient
TransMessageServer
package com.gupao.vip.RMI.sourceparse;
import java.io.IOException;
public class TransMessage {
private String content;
public String getContent() throws IOException, ClassNotFoundException {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
package com.gupao.vip.RMI.sourceparse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* serversocket
*
*/
public class TransMessage_Skeleton extends Thread{
private TransMessageServer transMessageServer;
public TransMessage_Skeleton(TransMessageServer transMessageServer) {
this.transMessageServer = transMessageServer;
}
@Override
public void run() {
ServerSocket serverSocket=null;
try {
serverSocket=new ServerSocket(8888);
Socket socket=serverSocket.accept();
while(socket!=null){
ObjectInputStream read=new ObjectInputStream(socket.getInputStream());
String method=(String)read.readObject();
if(method.equals("content")){
String content = transMessageServer.getContent();
ObjectOutputStream outputStream=new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(content);
outputStream.flush();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.gupao.vip.RMI.sourceparse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
*/
public class TransMessage_Stub extends TransMessage {
private Socket socket;
public TransMessage_Stub() throws IOException {
socket=new Socket("localhost",8888);
}
public String getContent() throws IOException, ClassNotFoundException {
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject("content");
outputStream.flush();
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
String content = (String)objectInputStream.readObject();
return content;
}
}
package com.gupao.vip.RMI.sourceparse;
import java.io.IOException;
/**
*/
public class TransMessageClient {
public static void main(String[] args) throws IOException {
TransMessage transMessage =new TransMessage_Stub();
String content= null;
try {
content = transMessage.getContent();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(content);
}
}
package com.gupao.vip.RMI.sourceparse;
/**
*/
public class TransMessageServer extends TransMessage {
public static void main(String[] args) {
TransMessageServer userServer=new TransMessageServer();
userServer.setContent("hello world");
TransMessage_Skeleton skel=new TransMessage_Skeleton(userServer);
skel.start();
}
}