Java RMI 的使用及原理
1、示例
三个角色:RMIService、RMIServer、RMIClient。(RMIServer向RMIService注册Stub、RMIService在RMIClient lookup时向其提供Stub)
服务端编写完后,把服务端的功能接口类给客户端,客户端编写自己的代码即可。(客户端通过向RMI Service查找指定的服务得到Stub,不用手动生成任何Stub)
代码:
server:
接口定义及实现:
/**
* <br>
* 在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象, 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上 调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口” (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。
*/
public interface Hello extends Remote {
/*
* extends了Remote接口的类或者其他接口中的方法若是声明抛出了RemoteException异常, 则表明该方法可被客户端远程访问调用。
*/
public String sayHello(String name) throws RemoteException;
} /**
* 远程对象必须实现java.rmi.server.UniCastRemoteObject类,这样才能保证客户端访问获得远程对象时, 该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为“存根”, 而服务器端本身已存在的远程对象则称之为“骨架”。其实此时的存根是客户端的一个代理,用于与服务器端的通信,
* 而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
*/
/* java.rmi.server.UnicastRemoteObject构造函数中将生成stub和skeleton */
public class HelloImpl extends UnicastRemoteObject implements Hello {
private static final long serialVersionUID = -271947229644133464L; // 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
public HelloImpl() throws RemoteException {
super();
} @Override
public String sayHello(String name) throws RemoteException {
// TODO Auto-generated method stub
return "hello " + name;
}
}
服务注册及服务端:
/**
* 注册远程对象,向客户端提供远程对象服务.远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求 到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象
*/
public class HelloServer { public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Hello h = new HelloImpl(); /* 生成stub和skeleton,并返回stub代理引用 */
String serverIp = "localhost";
int listenPort = 12345;
String serverURL = serverIp + ":" + listenPort; /*
* 本地创建并启动RMI Service注册表,被创建的Registry将在指定的端口上侦听到来的请求
*/
Registry registry = LocateRegistry.createRegistry(listenPort);
// Registry registry = LocateRegistry.getRegistry("localhost", 12345);// 也可以获取远程RMI Service注册表,该RMI Service通过 rmiregistry -p 1099 启动 /* 将stub代理绑定到Registry服务的URL上 */
registry.bind("MyHello", h);// 通过RMI注册表绑定服务,不用指定完整RMI URL
// Naming.bind("rmi://" + serverURL + "/MyHello", h);// 或者通过命名服务绑定服务,由于命名服务不止为RMI提供查询服务,故需指定完整RMI URL,java.lang.String://host:port/name System.out.println("HelloServer启动成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
client:(把Hello接口给客户端并编写客户端代码)
查找服务并调用:
public class HelloClient { public static void main(String[] args) throws RemoteException, MalformedURLException, NotBoundException {
// String serverIp = "192.168.7.39";
String serverIp = "localhost";
int serverPort = 12345;
String serverURL = serverIp + ":" + serverPort;
Hello h = null; /* 从RMI Registry中请求stub */
// h = (Hello) Naming.lookup("rmi://" + serverURL + "/MyHello"); Registry registry = LocateRegistry.getRegistry(serverIp, serverPort);
h = (Hello) registry.lookup("MyHello"); /* 通过stub调用远程接口实现 */
System.out.println(h.sayHello("hello"));
}
}
RMI可以实现远程通讯,缺点之一:客户端只能是Java的,不能跨语言。
2、原理
本质是利用客户端的Stub(静态代理)和服务端的Skeleton(骨架)来为上层屏蔽底层通信。
RMI远程调用步骤:
1,客户对象调用客户端辅助对象上的方法
2,客户端辅助对象打包调用信息(变量,方法名),通过网络发送给服务端辅助对象
3,服务端辅助对象将客户端辅助对象发送来的信息解包,找出真正被调用的方法以及该方法所在对象
4,调用真正服务对象上的真正方法,并将结果返回给服务端辅助对象
5,服务端辅助对象将结果打包,发送给客户端辅助对象
6,客户端辅助对象将返回值解包,返回给客户对象
7,客户对象获得返回值
对于客户对象来说,步骤2-6是完全透明的
A.
B.
Java RMI由3个部分构成:
- RMIService即JDK提供的一个可以独立运行的程序(bin目录下的rmiregistry)。
- RMIServer即我们自己编写的一个java项目,这个项目对外提供服务。
- RMIClient即我们自己编写的另外一个java项目,这个项目远程使用RMIServer提供的服务。
首先,RMIService必须先启动并开始监听对应的端口。
其次,RMIServer将自己提供的服务的实现类注册到RMIService上,并指定一个访问的路径(或者说名称)供RMIClient使用。
最后,RMIClient使用事先知道(或和RMIServer约定好)的路径(或名称)到RMIService上去寻找这个服务,并使用这个服务在本地的接口调用服务的具体方法。
RMIService只负责接受RMIServer注册Stub和RMIClient查询Stub,不参与RMIServer、RMIClient间的后续交互过程。
RMIService没和RMIServer一起
通常RMIService是在RMIServer里被创建的,此时执行顺序是RMIServer—RMIService—RMIClient;
但RMIService、RMIServer、RMIClient也可以部署到3个不同的JVM中,即此时RMI Service不在RMI Server里被创建,这时执行顺序是RMIService---RMIServer—RMIClient。这种情况下在执行RMIService前,需要通过 rmic 类名 命令产生stub类并连同功能接口类放到RMIService下,然后通过 rmiregistry -p 端口 命令或代码 LocateRegistry.createRegistry(listenPort) 启动RMIService(默认端口号为1099)。
实际应用中很少有单独提供一个RMIService服务器,开发的时候可以使用Registry类在RMIServer中启动RMIService。
RMI并发
在JDK1.5及以前版本中,RMI每接收一个远程方法调用就生成一个单独的线程来处理这个请求,请求处理完成后,这个线程就会释放;在JDK1.6之后,RMI使用线程池来处理新接收的远程方法调用请求-ThreadPoolExecutor,RMIService亦然。
在JDK1.6中,RMI提供了可配置的线程池参数属性(启动参数 java -jar -Dxxx=xx xxx):
sun.rmi.transport.tcp.maxConnectionThread - 线程池中的最大线程数量,默认无限,但Linux单进程可打开最大文件数有限,此时可能出问题。
sun.rmi.transport.tcp.threadKeepAliveTime - 线程池中空闲的线程存活时间(ms),默认1分钟。
3、RMI相关资料
1、http://blog.csdn.net/a19881029/article/details/9465663——示例
2、http://blog.csdn.net/sinat_34596644/article/details/52599688——底层原理简述
3、http://blog.csdn.net/sureyonder/article/details/5653609——Java RMI线程模型及内部实现原理
4、http://blog.csdn.net/yinwenjie/article/details/49120813——详细介绍了RMI不同运行方式及底层原理
4、缺点与改进
RMI的缺点:
跨平台能力差,服务端和客户端只能是Java
客户端对服务端依赖严重,客户端和服务端分别有自动生成的Stub和Skeleton,若服务端接口变化则需要重新生成Stub和Skekleton
针对跨平台能力差的缺点,可以通过自动生成不同语言的Stub和Skeleton(如Google的 Protobuf 即如此)
针对客户端对服务端依赖严重的缺点,一种解决方法是:去掉Stub并让服务端与客户端通过JSON或XML等数据格式进行交互,实现解耦。这其实就是现在很流行的HTTP Restfull API
更多关于客户端服务端通信方法的演进可参阅:咖啡馆的故事:FTP, RMI , XML-RPC, SOAP, REST一网打尽
Java RMI 的使用及原理的更多相关文章
- Java rmi漏洞利用及原理记录
CVE-2011-3556 该模块利用了RMI的默认配置.注册表和RMI激活服务,允许加载类来自任何远程(HTTP)URL.当它在RMI中调用一个方法时分布式垃圾收集器,可通过每个RMI使用endpo ...
- java RMI原理详解
java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以 ...
- JAVA RMI分布式原理和应用
RMI(Remote Method Invocation)是JAVA早期版本(JDK 1.1)提供的分布式应用解决方案,它作为重要的API被广泛的应用在EJB中.随着互联网应用的发展,分布式处理任务也 ...
- Java RMI之HelloWorld篇
Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方 ...
- Java RMI 介绍和例子以及Spring对RMI支持的实际应用实例
RMI 相关知识 RMI全称是Remote Method Invocation-远程方法调用,Java RMI在JDK1.1中实现的,其威力就体现在它强大的开发分布式网络应用的能力上,是纯Java的网 ...
- Java学习笔记(十六)——Java RMI
[前面的话] 最近过的好舒服,每天过的感觉很充实,一些生活和工作的技巧注意了就会发现,其实生活也是可以过的如此的有滋有味,满足现在的状况,并且感觉很幸福. 学习java RMI的原因是最近在使用dub ...
- JAVA RMI helloworld入门
Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方 ...
- Java RMI 远程方法调用
Java RMI 指的是远程方法调用 (Remote Method Invocation).它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法.可以用此方 ...
- Java RMI(远程方法调用)开发
参考 https://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-arch2.html http://www.cnblogs.com/wxi ...
随机推荐
- Linux 密码过期(WARNING:Your password has expired )
最近遇到两次这个问题,我们公司用的是开源的堡垒机Jumpserver但是最近有两个同学遇到了 WARNING:Your password has expired 第一次遇到这个问题也没有往深了去查,当 ...
- SaaS多租户模式数据存储方案
云计算多租户几乎用于所有软件即服务 (Software as a Service, SaaS) 应用程序,因为计算资源是可伸缩的,而且这些资源的分配由实际使用决定.话虽如此,用户可以通过 Intern ...
- ASP.NET MVC中全局异常处理
以前不知道从哪里找到的处理全局异常的,觉得蛮好用就记下来了. 1, 建立MyExecptionAttribute.cs类,写入如下代码: using System; using System.Coll ...
- Atitit 路径规划法attilax总结 扫描线路法
Atitit 路径规划法attilax总结 扫描线路法 2017/2/8 20:43:37[吐槽]深圳-小 2017/2/8 20:43:37 群主做什么的2017/2/10 10:03:15系统消 ...
- analysis-what-blockchain-technology-means-for-artificial-intelligence-cm888540
http://m.nasdaq.com/article/analysis-what-blockchain-technology-means-for-artificial-intelligence-cm ...
- 用NPOI创建Excel、合并单元格、设置单元格样式、边框的方法
本篇文章小编为大家介绍,用NPOI创建Excel.合并单元格.设置单元格样式.边框的方法.需要的朋友参考下 今天在做项目中,遇到使用代码生成具有一定样式的Excel,找了很多资料,最后终于解决了,Ex ...
- 移动WEB开发基础入门
什么是移动WEB开发,我个人理解就是,将网页更好的显示在移动端的一些设置,简单来说就两点如下: 1.流式布局,即百分比自适应布局 将body下的div容器的样式设置如下: div{ width:100 ...
- Android 视觉规范
各种分辨率下图片尺寸 工程师通常开发三套UI布局文件,所以设计师通常也要不同的需求处理2-3套切图. con Type Standard Asset Sizes (in Pixels), for Ge ...
- 【Android】Handler详解
Handler的定义 主要接受子线程发送的数据, 并用此数据配合主线程更新UI. 解释: 当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进 ...
- js 将数字转换成人民币大写的方法
//将数字转换成人民币大写的方法 var digitUppercase = function (n) { var fraction = ['角', '分']; var digit = [ '零', ' ...