RMI

RMI 介绍

RMI 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。因此,RMI 意味着需要一个 Server 端和一个 Client 端。

Server 端通常会创建一个对象,并使之可以被远程访问。

这个对象被称为远程对象。Server 端需要注册这个对象可以被 Client 远程访问。

Client 端调用可以被远程访问的对象上的方法,Client 端就可以和 Server 端进行通信并相互传递信息。

RMI 工作原理

图片

Client 端有一个被称 Stub 的东西,有时也会被成为存根,它是 RMI Client 的代理对象,Stub 的主要功能是请求远程方法时构造一个信息块,RMI 协议会把这个信息块发送给 Server 端

这个信息块由几个部分组成:

  • 远程对象标识符。
  • 调用的方法描述。
  • 编组后的参数值(RMI协议中使用的是对象序列化)。
    既然 Client 端有一个 Stub 可以构造信息块发送给 Server 端,那么 Server 端必定会有一个接收这个信息快的对象,称为 Skeleton 。

它主要的工作是:

  • 解析信息快中的调用对象标识符和方法描述,在 Server 端调用具体的对象方法。
  • 取得调用的返回值或者异常值。
  • 把返回值进行编组,返回给客户端 Stub.
    到这里,一次从 Client 端对 Server 端的调用结果就可以获取到了。

RMI 开发

RMI Server

Server 端主要是构建一个可以被传输的类 User,一个可以被远程访问的类 UserService,同时这个对象要注册到 RMI 开放给客户端使用。

1.定义服务器接口(需要继承 Remote 类,方法需要抛出 RemoteException)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.wdbyte.rmi.server;

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

public interface UserService extends Remote {

/**
* 查找用户
*
* @param userId
* @return
* @throws RemoteException
*/
User findUser(String userId) throws RemoteException;
}

User 对象在步骤 3 中定义
2.实现服务器接口(需要继承 UnicastRemoteObject 类,实现定义的接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.wdbyte.rmi.server;

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

public class UserServiceImpl extends UnicastRemoteObject implements UserService {

protected UserServiceImpl() throws RemoteException {
}

@Override
public User findUser(String userId) throws RemoteException {
// 加载在查询
if ("00001".equals(userId)) {
User user = new User();
user.setName("金庸");
user.setAge(100);
user.setSkill("写作");
return user;
}
throw new RemoteException("查无此人");
}
}

3.定义传输的对象,传输的对象需要实现序列化(Serializable)接口。
需要传输的类一定要实现序列化接口,不然传输时会报错

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.wdbyte.rmi.server;

import java.io.Serializable;

public class User implements Serializable {

private static final long serialVersionUID = 6490921832856589236L;

private String name;
private Integer age;
private String skill;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getSkill() {
return skill;
}

public void setSkill(String skill) {
this.skill = skill;
}

@Override
public String toString() {
return "User{" +
"name='" + name + ''' +
", age=" + age +
", skill='" + skill + ''' +
'}';
}
}

4.注册( rmiregistry)远程对象,并启动服务端程序。
服务端绑定了 UserService 对象作为远程访问的对象,启动时端口设置为 1900

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.wdbyte.rmi.server;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RmiServer {

public static void main(String[] args) {
try {
UserService userService = new UserServiceImpl();
LocateRegistry.createRegistry(1900);
Naming.rebind("rmi://localhost:1900/user", userService);
System.out.println("start server,port is 1900");
} catch (Exception e) {
e.printStackTrace();
}
}
}

RMI Client

相比 Server 端,Client 端就简单的多。直接引入可远程访问和需要传输的类,通过端口和 Server 端绑定的地址,就可以发起一次调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.wdbyte.rmi.client;

import java.rmi.Naming;

import com.wdbyte.rmi.server.User;
import com.wdbyte.rmi.server.UserService;

public class RmiClient {
public static void main(String args[]) {
User answer;
String userId = "00001";
try {
// lookup method to find reference of remote object
UserService access = (UserService)Naming.lookup("rmi://localhost:1900/user");
answer = access.findUser(userId);
System.out.println("query:" + userId);
System.out.println("result:" + answer);
} catch (Exception ae) {
System.out.println(ae);
}
}
}

捋⼀捋这整个过程,⾸先客户端连接Registry,并在其中寻找Name是finduesr的对象,这个对应数据流中的Call消息;然后Registry返回⼀个序列化的数据,这个就是找到的Name=finduser的对象,这个对应 数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是⼀个远程对象,地址 在 ip:port ,于是再与这个地址建⽴TCP连接;在这个新的连接中,才执⾏真正远程⽅法调⽤,也就是 finduser()

图片

RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server可以在上⾯注册⼀个Name到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server;最后,远程⽅法实际上在RMI Server上调⽤。