RMI的入门
RMI是分布式对象软件包,它简化了在多台计算机上的JAVA应用之间的通信。必须在jdk1.1以上
java.rmi.server.UnicastRemoteObject 所有可以被远程调用的对象都必须扩展该类
使用这种机制,某一台计算机上的对象在调用另外一台计算机上的方法时,使用的程
序语法规则和在本地机上对象间的方法调用的语法规则一样。
优点
只要按照RMI规则设计程序,可以不必再过问在RMI之下的网络细节了,如:TCP和Socket等等。
任意两台计算机之间的通讯完全由RMI负责。调用远程计算机上的对象就像本地对象一样方便。
也就是说,可以将类似Java哈西表这样的复杂类型作为一个参数进行传递。
如果用户能够传递属性,那么就可以在自己的解决方案中使用面向对象的设计方式。
所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象——包括实现和类型
——就会失去设计方式上所提供的优点。
RMI使用专门为保护系统免遭恶意小程序侵害而设计的安全管理程序。
远程接口实际上就是Java接口。
为了实现RMI的功能必须创建远程对象任何可以被远程调用的对象必须实现远程接口。但远程
接口本身并不包含任何方法。因而需要创建一个新的接口来扩展远程接口。
新接口将包含所有可以远程调用的方法。远程对象必须实现这个新接口,由于新的接口扩展了
远程接口,实现了新接口,就满足了远程对象对实现远程接口的要求,所实现的每个对象都将
作为远程对象引用。
1、定义一个远程接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常。
2、定义一个实现该接口的类。
3、使用RMIC程序生成远程实现所需的残根和框架。
4、创建一个服务器,用于发布2中写好的类。
6、启动rmiRegistry并运行自己的远程服务器和客户程序。
import java.rmi.RemoteException;
{
public String SayHello() throws RemoteException; //需要抛出remote异常
}
抛出这个异常的原因
由于任何远程方法调用实际上要进行许多低级网络操作,因此网络错误可能在调用过程中随时发生。
因此,所有的RMI操作都应放到try-catch块中。
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
implements I_Hello
{
{
}
{
return "Hello world !!";
}
扩展java.rmi.server.UnicastRemoteObject
UnicastRemoteObject顾名思义,是让客户机与服务器对象实例建立一对一的连接。
服务器上生成的反调动调用参数和进行实际方法调用调动返回值的代码称为框架。
生成残根和框架的工具
Rmic命令行工具(RMI Compiler)
格式:
Rmic classname
public class RMI_Server
{
{
try
{
Hello hello = new Hello(); //实例化要发布的类
Naming.rebind("RMI_Hello", hello); //绑定RMI名称 进行发布
System.out.println("=== Hello server Ready === ");
}
catch(Exception exception)
{
exception.printStackTrace();
}
}
}
public class RMI_Client {
{
I_Hello hello = (I_Hello) Naming.lookup("RMI_Hello"); //通过RMI名称查找远程对象
System.out.println(hello.SayHello()); //调用远程对象的方法
{
e.printStackTrace();
}
}
permission java.net.SocketPermission "*:1024-65535",
"connect,accept";
permission java.net.SocketPermission "*:80","connect";
};
RMI的使用介绍
远程方法调用正是基于三层架构设计的中间层。远程方法调用的原理是桩和骨架结构。主要有三层,分别是桩/骨架层,远程引用层,还有传输层。
要完成一个简单的RMI应用要分为以下几个简单的步骤:
1。定义远程接口
2。定义和实现RMI服务器类
3。定义和实现客户端类
4。编译以上的源码
5。生成桩/骨架
6。创建安全策略
7。启动RMI注册表
8。启动服务器
9。启动客户端
我们来做一个例子,在服务器上有一个连接数据库,插入值的方法,我们在客户端要使用这个方法,来真正的插入一个值!
第一步:先编写一个接口AccountServer.java,这个接口要继承Remote接口,并且提供一个远程方法insertDetails(),这个方法要抛出RemoteException异常,代码如下:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface AccountServer extends Remote
{
public String insertDetails(String firstName, String lastName,
String phone, String income, String accountType)
throws RemoteException;
}
第二步:实现远程对象类,就是RMI服务器,AccountServerImpl.java,这个类要继承UnicastRemoteObject类,并且要实现自己写的远程接口类AccountServer,首先要定义远程对象构造器,然后就是要实现远程接口中的方法,这个方法来连接mysql数据库,并且插入值.然后我们main方法中,设置安全管理程序,然后创建远程对象,接着注册远程对象.代码如下:
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class AccountServerImpl extends UnicastRemoteObject implements
AccountServer
{
private Connection con;
public AccountServerImpl() throws RemoteException
{
super();
}
public String insertDetails(String firstName, String lastName,
String phone, String income, String accountType)
throws RemoteException
{
int rowsAffected = 0;
String sReturn = "fail";
try
{
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1/jsptest?useUnicode=true&characterEncoding=GBK",
"root", "");
PreparedStatement ps = con
.prepareStatement("insert into User_info values(?,?,?,?,?)");
ps.setString(1, "firstName");
ps.setString(2, "lastName");
ps.setString(3, "phone");
ps.setString(4, "income");
ps.setString(5, "accountType");
rowsAffected = ps.executeUpdate();
if (rowsAffected > 0)
{
sReturn = "success";
}
}
catch (Exception e)
{
e.printStackTrace();
}
return sReturn;
}
public static void main(String[] args)
{
//设置安全管理程序
System.setSecurityManager(new RMISecurityManager());
try
{
//创建远程对象
AccountServerImpl as = new AccountServerImpl();
//注册远程对象
Naming.rebind("AccountServer", as);
System.out.println("RMI服务器启动成功");
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
}
第三步:编写客户端代码,Client.java 这个代码中有两个重要的任务,就是得到远程对象的应用,然后调用远程对象,其余的都是界面部分,就不再讲解,代码如下:
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.Naming;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Client
{
public static void main(String[] args)
{
ClientFrame frame = new ClientFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class ClientFrame extends JFrame
{
public ClientFrame()
{
setTitle("客户端");
setSize(200, 220);
Container con = getContentPane();
ClientPanel panel = new ClientPanel();
con.add(panel);
}
}
class ClientPanel extends JPanel
{
private JLabel lfirst, llast, lphone, lincome, ltype;
private JTextField ffirst, flast, fphone, fincome, ftype;
private JButton submit;
public ClientPanel()
{
lfirst = new JLabel(" 姓:");
llast = new JLabel(" 名:");
lphone = new JLabel("电话:");
lincome = new JLabel("来自:");
ltype = new JLabel("类型:");
ffirst = new JTextField(10);
flast = new JTextField(10);
fphone = new JTextField(10);
fincome = new JTextField(10);
ftype = new JTextField(10);
submit = new JButton(" 提 交 ");
submit.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
//得到远程对象
AccountServer as = (AccountServer) Naming
.lookup("rmi://localhost/AccountServer");
String firstName = ffirst.getText();
String lastName = flast.getText();
String phone = fphone.getText();
String income = fincome.getText();
String accountType = ftype.getText();
//调用远程方法
String str = as.insertDetails(firstName, lastName, phone,
income, accountType);
if ("fail".equals(str))
{
JOptionPane.showMessageDialog(null, "插入失败");
}
else
{
JOptionPane.showMessageDialog(null, "插入成功");
}
}
catch (Exception e2)
{
e2.printStackTrace();
}
}
});
add(lfirst);
add(ffirst);
add(llast);
add(flast);
add(lphone);
add(fphone);
add(lincome);
add(fincome);
add(ltype);
add(ftype);
add(submit);
}
}
第四步:在Dos下,进入代码所在目录,使用javac *.java 来编译所有代码
第五步:在class文件所在目录使用 rmic AccountServerImpl来生成桩和骨架(AccountServerImpl_Skel.class和AccountServerImpl.Stub.class)
第六步:用policytool工具来创建安全策略文件,放开所有的权限,保存.java.policy文件在C:/Documents and Settings/Administrator文件夹下
第七步:使用start rmiregistry命令来启动注册表,默认使用1099端口,我们不需要改变
第八步:java -Djava.rmi.server.codebase=file:/e:/aa/ -classpath .;%CLASSPATH% AccountServerImpl来启动服务器,注意由于服务器中要使用Mysql数据库,所有我们引入使用数据库的jar包,将mysql的jar包添加的CLASSPATH,就OK呢
第九步:使用java Client来启动客户端,和得到图形界面的客户端,填入数据,点击提交,数据就会添加进入数据库
附:
数据库名称:jsptest
表名:user_info
表结构:
CREATE TABLE user_info (
firstName varchar(50) NOT NULL default '',
lastName varchar(50) NOT NULL default '',
phone varchar(20) NOT NULL default '',
income varchar(30) NOT NULL default '',
accountType varchar(50) NOT NULL default '',
PRIMARY KEY (firstName)
)
RMI代码动态下载
我看网上讲RMI应用时都是些比较老的文章,从前部署一个RMI应用有6个步骤:
(1)定义和实现远端接口中的参数
(2)定义和实现远端接口
(3)编写服务端代码
(4)编写客户端代码
(5)生成stub和skeltion ,并将stub打包到客户端jar中,将skeltion打包到服务端jar中
(6)启动rmiregistry , 并将服务注册到rmiregistry中,然后运行代码。
上边的步骤涉及到了好几个长串复杂的命令。。。
但在JDK5中使用了动态代理技术,实现了动态生成stub和skeltion类,从而省却了相当多的繁琐工作。(我这还是后来知道,我是说我这么简单就实现了RMI,跟网上说的咋区别那大。。。)
如此一来RMI成了相当轻量级的技术了,但还是会遇到繁琐的事情,比如代码动态下载,动态下载允许RMI应用动态从http后者ftp下载类定义的字节码(运行时加入参数-Djava.rmi.server.codebase=http://***/*.jar),就像是把远程的类加入本地的classpath一样,相当方便的技术。问题就是如果我们只是学习或做个简单的应用,为了部署一个RMI还要启用一个http或ftp服务,多么麻烦,谁都想只用启动程序就可以了,尤其是在调试的时候。当然如果只是在本地调试的话,可以用file://代替http,可是如果是个简单的应用必须用到http服务,但又不想兴师动众的启动一个http服务器的话JDK6的新特性之一http轻量级服务就派上用场了。
RMI代码动态下载其实就用到了HTTP协议的下载功能,所以,可以把这部分工作给一个简单的通用类就行了。如下:
- //RmiHttpServer.java
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import java.net.InetSocketAddress;
- import java.util.concurrent.Executors;
- import com.sun.net.httpserver.HttpExchange;
- import com.sun.net.httpserver.HttpHandler;
- import com.sun.net.httpserver.HttpServer;
- import com.sun.net.httpserver.spi.HttpServerProvider;
- public class RmiHttpServer {
- private int port;
- private HttpServer httpServer;
- private boolean isRunning = false;
- private String folder = "";
- private String context = "/";
- public RmiHttpServer() {
- this.port = 80;
- }
- public RmiHttpServer(int port) {
- this.port = port;
- }
- public RmiHttpServer(String context, String folder, int port) {
- this.folder = folder;
- this.context = context;
- this.port = port;
- }
- public synchronized void service() {
- if (isRunning) {
- System.out.println("HTTP服务已启动...");
- } else {
- HttpServerProvider httpServerProvider = HttpServerProvider
- .provider();
- InetSocketAddress addr = new InetSocketAddress(port);
- try {
- httpServer = httpServerProvider.createHttpServer(addr, 1);
- httpServer.createContext(context, new RmiHttpHandler(folder));
- httpServer.setExecutor(Executors.newCachedThreadPool());
- httpServer.start();
- System.out.println("HTTP服务已启动...");
- isRunning = true;
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("HTTP服务启动失败");
- }
- }
- }
- public synchronized void close() {
- isRunning = false;
- httpServer.stop(0);
- System.out.println("HTTP服务关闭");
- }
- }
- class RmiHttpHandler implements HttpHandler {
- public static int BUFFER_LENGTH = 1024;
- private String folder;
- public RmiHttpHandler(String folder) {
- this.folder = folder;
- }
- public void handle(HttpExchange httpExchange) throws IOException {
- String response;
- OutputStream out = httpExchange.getResponseBody();
- String path;
- if (folder.equals(""))
- path = httpExchange.getRequestURI().getPath().substring(1);
- else
- path = folder + httpExchange.getRequestURI().getPath();
- //System.out.println(path);
- File file = new File(path);
- if (file.exists() && file.canRead()) {
- httpExchange.sendResponseHeaders(200, file.length());
- FileInputStream in = new FileInputStream(file);
- byte[] buffer = new byte[BUFFER_LENGTH];
- int count = 0;
- for (int i = 0; count != -1; i++) {
- count = in.read(buffer);
- if (count != -1)
- out.write(buffer, 0, count);
- }
- in.close();
- } else {
- response = "error";
- httpExchange.sendResponseHeaders(404, response.length());
- out.write(response.getBytes());
- }
- out.close();
- }
- }
下面是个简单的例子来说明代码的动态下载和上面类的使用方法。
服务端建立文件夹server。
首先是要动态下载的接口
- //User.java
- import java.io.Serializable;
- public interface User extends Serializable{
- public String getName();
- }
然后是服务接口
- //Hello.java
- import java.rmi.Remote;
- import java.rmi.RemoteException;
- public interface Hello extends Remote {
- public User hello(String name) throws RemoteException;
- }
两个接口的实现
- //UserImpl.java
- public class UserImpl implements User {
- private static final long serialVersionUID = -5401902546629154615L;
- private String name;
- public UserImpl(String n){
- name = n;
- }
- public String getName() {
- return name+" 你好!我是aa";
- }
- }
- //HelloImpl.java
- import java.rmi.RemoteException;
- import java.rmi.server.UnicastRemoteObject;
- public class HelloImpl extends UnicastRemoteObject implements Hello {
- private static final long serialVersionUID = -4981732846986548202L;
- public HelloImpl() throws RemoteException {
- super();
- }
- public User hello(String name) throws RemoteException {
- System.out.println("服务端负责接口的实例化");
- return new UserImpl(name);
- }
- }
启动服务
- //Server.java
- import java.rmi.Naming;
- import java.rmi.RMISecurityManager;
- import java.rmi.RemoteException;
- import java.rmi.registry.LocateRegistry;
- public class Server {
- public static void main(String argv[]) throws RemoteException {
- System.setProperty("java.security.policy", "rmi.policy");//加载安全配置
- LocateRegistry.createRegistry(1099);
- if (System.getSecurityManager() == null) {
- System.setSecurityManager(new RMISecurityManager());
- }
- try {
- Hello hello = new HelloImpl();
- Naming.rebind("//127.0.0.1/HelloServer", hello);//绑定
- } catch (Exception e) {
- e.printStackTrace();
- }
- new RmiHttpServer(80).service();//启动http服务,负责将当前目录的字节码动态传输给客户端
- }
- }
配置文件rmi.policy
grant {
permission java.security.AllPermission;
};
下面就是客户端,建立文件夹client
我们只需要把客户端需要的接口User.class,Hello.class拷贝到classpath(即client目录下)就可以了,由于用到了代码动态下载,所以得获得许可,我们还需把rmi.policy也拷贝到环境中。
- //Client.java
- import java.rmi.*;
- public class Client {
- public static void main(String[] args) throws Exception {
- System.setProperty("java.security.policy", "rmi.policy");
- System.setProperty("java.rmi.server.codebase","http://127.0.0.1/");//指名需要下载的类的路径
- //注意,如果没有上面一行,会出现java.lang.ClassNotFoundException: UserImpl错误
- //因为UserImpl并不在本地环境中
- System.setSecurityManager(new RMISecurityManager());
- Hello hello = (Hello) Naming.lookup("//127.0.0.1/HelloServer"); //通过RMI名称查找远程对象
- System.out.println(hello.hello("syc").getName());//执行下载下来的类的getName方法
- }
- }
接下来执行Server,和Client,就可以看到结果了,如果我们改变UserImpl的实现,再次执行Client时,就会发现变化。
JDK5新版RMI编程指南 --转载:http://blog.csdn.net/hqx2008/article/details/1725670
接口 Registry
public interface Registry
extends Remote
Registry
是简单远程对象注册表的一个远程接口,它提供存储和获取绑定了任意字符串名称的远程对象引用的方法。
bind
、
unbind
和
rebind
方法用于改变注册表中的名称绑定,
lookup
和
list
方法用于查询当前的名称绑定。
类 LocateRegistry
java.lang.Object
java.rmi.registry.LocateRegistry
public final class LocateRegistry
extends Object
LocateRegistry
用于获得对特定主机(包括本地主机)上引导远程对象注册表的引用,或用于创建一个接受对特定端口调用的远程对象注册表。
static Registry
|
void
|
void
|
类 UnicastRemoteObject
java.lang.Object
java.rmi.server.RemoteObject
java.rmi.server.RemoteServer
java.rmi.server.UnicastRemoteObject
public class UnicastRemoteObject
extends RemoteServer
exportObject
方法,正在导出的远程对象的 stub 按以下方式获得:
- 如果使用
UnicastRemoteObject.exportObject(Remote)方法导出该远程对象,则加载 stub 类(通常使用
rmic
工具从远程对象的类预生成)并按以下方式构造 stub 类的实例。- “根类”按以下情形确定:如果远程对象的类直接实现扩展
Remote的接口,则远程对象的类为根类;否则,根类为直接实现扩展
Remote
接口的远程对象类的最具派生能力的超类。 - 要加载的 stub 类的名称通过连接带有后缀
"_Stub"
的根类的二进制名称确定。 - 按使用根类的类加载器的名称加载 stub 类。该 stub 类必须扩展
RemoteStub并且必须有公共构造方法,该构造方法有一个属于类型
RemoteRef的参数。
- “根类”按以下情形确定:如果远程对象的类直接实现扩展
RemoteRef构造 stub 类的实例。
static Remote
|
static Remote
|
static RemoteStub
|
static Remote
|
RMI如何固定分配端口
转自:http://blog.csdn.net/ygzx2008/article/details/2593793
RMI之所以使用的范围受限制主要有两方面原因,其一:必须要是java,平台的异构性受到限
制;其二:穿越防火墙不方便。这里主要谈谈RMI如何通过固定分配端口来穿越防火墙。
RMI穿越防火墙不方便主要是因为除了RMI服务注册的端口(默认1099)外,与RMI的通讯还需
要另外的端口来传送数据,而另外的端口是随机分配的,所以要想RMI的客户能通过防火墙来
与RMI服务通讯,则需要能让随机分配的端口固定下来,具体做法如下:
1、增加一个文件SMRMISocket.java
import java.rmi.server.*;
import java.io.*;
import java.net.*;
public class SMRMISocket extends RMISocketFactory
{
public Socket createSocket(String host, int port) throws IOException
{
return new Socket(host,port);
}
public ServerSocket createServerSocket(int port) throws IOException
{
if (port == 0)
{
port = 10990; //不指定就随机分配了
return new ServerSocket(port);
}
}
}
2、在实例化UnicastRemoteObject的子类前(即在得到Registry对象和邦定服务之前)加入一下代码:
try
{
RMISocketFactory.setSocketFactory(new SMRMISocket());
} catch (Exception ex) {
}
这样的话RMI分配的端口就被固定了,防火墙只需要打开1099和10990端口即可。