RMI菜鸟游记

最近想学习一下Web底层机制,也不知道从哪里开始看起。找了一本差不多的书《Java网络高级编程》,看到RMI这一块,发现自己看不明白,就网络上各处寻找资料,鼓捣了一晚上,终于明白了怎么回事。

RMI简介

Java Remote Method Invocation(RMI–Java远程方法调用)允许您使用Java编写分布式对象。

RMI的组成

一个正常工作的RMI 系统由下面几个部分组成:

  • 远程服务的接口定义
  • 远程服务接口的具体实现
  • 桩(Stub )和框架( Skeleton )文件
  • 一个运行远程服务的服务器
  • 一个RMI 命名服务,它允许客户端去发现这个远程服务
  • 类文件的提供者(一个HTTP 或者 FTP 服务器)
  • 一个需要这个远程服务的客户端程序

RMI工作原理

RMI系统结构,在客户端和服务器端都有几层结构。方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向0下,传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。

占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。

要完成以上步骤需要有以下几个步骤:

  • 生成一个远程接口
  • 实现远程对象( 服务器端程序 )
  • 编写服务器程序 、注册远程对象、启动远程对象
  • 编写客户程序

RMI的实现原理

  • 调用者(Client )以通常方法 obj.invoke() 调用一个本地接口的远程实现类的实例
  • 顾客Stub的功能把有关的远程调用参数封装为一个消息包(数据包)或者一组消息包,第一步调用访问的远程服务的地址(rmi://localhost:1099/)、对象(obj)、调用方法(invoke)及其参数都包含在这条消息中
  • 调用者(Client)本地RMI Runtime将这条消息发送给对应的服务器地址(rmi://localhost:1099/)RMIRuntime(rmi协议基于tcp/ip协议),服务器端RMI Runtime寻找绑定远程对象的进程(某个javaw.exe),然后调用这个进程中绑定的远程对象的指定方法,执行之后,将执行结果返回给调用者(Client )(其中的运行涉及到序列化和反序列化,因此为了平台兼容性每个有关java类都最好实现序列化。经常看到有人压制序列号警告而不是添加一个序列化 ID)
  • 调用者(Client)RMI Runtime接受到返回的结果,然后将此结果反序列化并且返回到调用方法。

在以上回避了一个重要问题,即Client怎么知道并调用Server上某个远程对象呢?这里就用到了命名服务。命名服务(Name Service)是分布式系统基本的公共服务。Java远程方法调用利用RMI机制提供命名服务。命名是给一个对象起一个名字,也就是就是说把名字绑定(binding)到该对象上,也就是以Key|Value的mapping存储在RMI上的,它的目的就是用人们容易理解的名字代替计算机中人们不易理解的存储表示对象。Client通过Server上的某个key(String类型)获取指定的value(Object类型),这个value是一种实现类,这个实现类所实现的规范或者说是接口本地一定要有,然后本地调用的就是这个接口的远程实现了。由此可见,RMI的命名服务在RMI服务器端为RMI服务器提供者提供发布RMI服务对象的功能,在RMI客户端为RMI客户提供搜索远程RMI服务对象的功能。

实例

远程接口

该接口定义了一个方法,用于提供远程服务

1
2
3
4
5
6
7
8
9
10
package net.sulin.test.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Upper extends Remote {

public String UpperString(String param) throws RemoteException ;

}

实现远程对象(服务器端程序)

用来创建一个 Upper对象,然后由远程客户端获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package net.sulin.test.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class UpperImpl extends UnicastRemoteObject implements Upper {

private static final long serialVersionUID = 806571139035814115L;

protected UpperImpl() throws RemoteException {
super();
}

@Override
public String UpperString(String param) throws RemoteException {
System.out.println("remote invoking successfully");
return param.toUpperCase();
}
}

编写服务器程序、注册远程对象、启动远程对象

远程对象的注册类,该类应该在服务器端执行。执行之后,该机器将变为RMI服务器,客户端可以通过正确的url 来访问服务器上的远程对象对外暴露的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
package net.sulin.test.rmi;

import java.rmi.Naming;

public class ServerBad {

public static void main(String[] args) throws Exception {
UpperImpl upp = new UpperImpl();
Naming.rebind("upper", upp);
System.out.println("Bind successfully");
}

}

运行该服务端代码后,将会注册一个远程服务对象

编写客户程序

代码首先获取一个远程对象,然后如同本地调用一样,调用远程对象中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
package net.sulin.test.rmi;

import java.rmi.Naming;

public class Client {

public static void main(String[] args) throws Exception {
Upper upper = (Upper) Naming.lookup("upper");
System.out.println(upper.UpperString("aaaaaa"));
}

}

还需要做下列的一系列事情

  • 使用rmic编译一个能被RMI服务使用的clsss文件。这个clsss就是需要远程调用的类,在这里就是 UpperImpl.class了。记住,先javac UpperImpl.java,再rmic UpperImpl。
  • 然后就是启动RMI服务了。打开控制台,输入start rmiregistry,再按回车就OK了。
  • 最后一步就是启动服务类ServerBad了。

最后一步是最容易出错的,参见下面注意事项:

注意:如果提示找不到Stub 类,这个需要用下面的命令来运行

1
java.exe  -Djava.rmi.server.codebase =file:/ *_stub.class /   ServerBad

_stub.clss就是第一步生成的_stub.class的文件绝对路径

启动你的客户端程序吧

你难道就不好奇我的服务类为什么是Server Bad,因为我感觉这种方法太麻烦了。。。

还有一种更简单的方法,使用java.rmi.registry.Registry动态注册一个绑定指定端口(可选,默认1099 )的RMI服务。

服务端的类可以换为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package net.sulin.test.rmi;  

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {

public static void main(String[] args) throws Exception {
bind();
}

public static Registry createRegistry() throws Exception {
Registry registry = null;
final int port = 2222;
registry = LocateRegistry.createRegistry(port);
return registry;
}

public static void bind() throws Exception{
Registry registry = null;
registry = createRegistry();
Upper upp = new UpperImpl();
registry.bind("upper", upp);
System.out.println("MyRMIBind Successfully");
}

}

以上部分是单一的Client-Server的远程方法调用,还有一对多的Client(one)-Server(many)。的远程方法调用,即服务器端有多个,不但可以分散请求压力,还可以防止服务器出现故障导致的一系列问题。

还有一种Client/Server的远程方法调用,即一个网络点即可以是Client也可以是Server。

还有服务器链的远程方法调用,即Client-Server-Server…… 。 Client请求的第一个Server不能完成全部工作,就可以设置服务器链,利用多个Server同步响应一个调用请求
后面这几种都是基于上面的单一远程方法调用,很容易理解的。

下来看到第三章CORBA了、本来是看网络通信底层的,怎么书上讲到分布式开发上了。。。