远程过程调用知识点归纳

关于RPC框架,首先我们要了解什么叫RPC,为什么要用RPC。

RPC是只远程过程调用,也就是说两台服务器A,B, 一个应用部署在A服务器上,另一个应用部署在B服务器上,A服务器上的应用想要调用B服务器上的应用提供的方法/函数,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语意和传递调用的参数。

比如说,一个方法可能这样定义:

Student getStudentByName(String name)

那么:

  首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程调用过程中所有交换的数据都在这个连接里传输,连接可以是按需连接,调用结束后就关闭,也

可以是长连接,多个远程调用共享一个连接。

  第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口号,方法的名称是什么,这样才能完成调用,比如基于WEB服务协议的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。

  第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于 二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。

  第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。

  第五,返回值还要发送给A服务器上的应用,也要经过序列化的方式发送,服务器A接到后,在反序列化,恢复为内存中的表达方式,交给A服务器上的应用。

RMI与Web Service的同和异:

RMI与Web Service各有各的优势与特点。在RMI中,客户端可以传递一个包含方法的对象给服务端,服务端可以执行该对象的方法,Web Service是通过HTTP协议进行数据传输,只能传递值,而不能传递方法。由于 Web Service是通过HTTP协议进行数据传输,因此,Web Service可以实现跨平台的调用,而RMI的客户端和服务端都必须是Java平台

1 RMI简介

RMI是Remote Method Invocation的简称,即远程方法调用。通过RMI,Client可以调用Server的方法。

首先,Server生成Stub和Skeleton,用于描述方法和解析方法调用。

然后,Client获取Stub,即获取Server提供的方法的描述,从而知道Server提供了哪些方法及相应的参数(如图:步骤1)。

接着,Client通过Stub调用Server的方法(如图:步骤2),调用信息被传递到Server(如图:步骤3),由Server的Skeleton解析。

解析后,Server根据信息,调用相应的方法(如图:步骤4),最后,将方法返回的数据传递给Client(如图:步骤5、6、7)。

单看Client方面,在不考虑从Server获取Stub的步骤前提下,Client的操作就是调用一个方法,然后获得返回值,与调用本地方法一样。

同样,单看Server方面,就是某个方法被调用,Server运行该方法,然后将结果返回给调用者。

但是,对于步骤1来讲,Client如何知道在哪里下载Server的Stub?或者说,Client如何知道这个Server位置?

这里,要用到的就是Registry。首先,Server将方法绑定在Registry上。接着,Client可以通过Registry来获取相关信息。然后,Client就可以调用该Server的方法了。

类似于以前学校里的公告板(互联网还不发达的时候),学校各部门的通知会张贴(注册)在公告板上,学生通过公告板来获取相关的信息。

若对Web Service有了解,会发现RMI与Web Service有一些相似。在Web Service中,客户端是通过获取Service Description来知道服务端提供了哪些方法及相关信息,并根据获取的信息,传递相应的数据至服务端,服务端解析并调用相应方法后,将结果返回至客户端。

但是,RMI与Web Service各有各的优势与特点。在RMI中,客户端可以传递一个包含方法的对象给服务端,服务端可以执行该对象的方法,Web Service是通过HTTP协议进行数据传输,只能传递值,而不能传递方法。由于 Web Service是通过HTTP协议进行数据传输,因此,Web Service可以实现跨平台的调用,而RMI的客户端和服务端都必须是Java平台。
 

2 实现步骤:Server端

2.1 定义接口(Interface)

在Server定义一个接口,该接口描述了Server提供的方法。接口仅定义抽象方法(仅方法名、参数及返回值等信息),不需要给出具体的逻辑实现。这个接口被作为Stub来使用。

Client通过获取该接口(Stub),从而知道Server提供了哪些方法,包括方法名、参数及返回值等信息,但并不知道这些方法的具体实现。

注意:由于该接口将用于远程调用,因此,接口需要继承Remote类,且接口方法需要声明可能会抛出RemoteException。

  1. Public interface Compute extends Remote {

  2. public int multiply(int a, int b) throws RemoteException;

  3. }

若参数为某类的对象,该类需要继承Serializable(可序列化)。

  1. public interface Compute extends Remote {

  2. public Object executeTask(Task task) throws RemoteException;

  3. }

  4.  
  5. public interface Task extends Serializable {

  6. public Object execute();

  7. }

2.2 实现接口

当Client调用Server提供的方法时,Server需要执行该方法并返回结果至Client。因此,Server需要提供方法的具体逻辑实现,即Server需要实现上述定义的接口。

  1. public class ComputeEngine implements Compute {

  2.  
  3. public ComputeEngine() throws RemoteException {

  4. super();

  5. }

  6.  
  7. public int multiply(int a, int b) throws RemoteException {

  8. return a * b;

  9. }

  10. }

2.3 公开接口

当Server完成接口的定义和实现后,需要将该接口公开,以表示该接口是用于远程调用的接口。有两种方法来完成这一操作。

方法一:使实现该接口的类继承UnicastRemoteObject类

  1. public class ComputeEngine extends UnicastRemoteObject implements Compute {

  2.  
  3. public ComputeEngine() throws RemoteException {

  4. super();

  5. }

  6.  
  7. public int multiply(int a, int b) throws RemoteException {

  8. return a * b;

  9. }

  10. }

方法二:通过UnicastRemoteObject.exportObject()方法将某个对象设置为公开接口对象

  1. public static void main(String[] args) {

  2.  
  3. // 省略其它代码

  4.  
  5. ComputeEngine computeEngine = new ComputeEngine();

  6. UnicastRemoteObject.exportObject(computeEngine);

  7.  
  8. // 省略其它代码

  9. }

2.4 绑定接口

2.4.1 绑定对象

由于Server对外公开的是接口,或者说,向Client提供的是Stub,因此,在绑定接口时,应该将Stub对象绑定至Registry。做法是,创建实现接口的类的对象,赋值给接口对象。(多态技术)

  1. // 通过 2.3中方法一 公开接口

  2. Compute compute = new ComputeEngine();

  3. // 通过 2.3中方法二 公开接口

  4. ComputeEngine computeEngine = new ComputeEngine();

  5. Compute compute = (Compute) UnicastRemoteObject.exportObject(computeEngine);

2.4.2 获取Registry

LocateRegistry类相关方法:

  1. // 在本地创建一个Registry并指定端口

  2. Registry registry = LocateRegistry.createRegistry(int port);

  3.  
  4. // 获得一个本地Registry对象并使用默认端口(1099)

  5. Registry registry = LocateRegistry.getRegistry();

  6.  
  7. // 获得一个本地Registry对象并指定端口

  8. Registry registry = LocateRegistry.getRegistry(int port);

  9.  
  10. // 获得一个指定服务器的Registry对象并使用默认端口(1099)

  11. Registry registry = LocateRegistry.getRegistry(String host);

  12.  
  13. // 获得一个指定服务器的Registry对象并指定端口

  14. Registry registry = LocateRegistry.getRegistry(String host, int port);

2.4.3 绑定Registry

Registry相关方法:

  1. // 在registry上将name与obj绑定

  2. registry.bind(String name, Remote obj);

  3.  
  4. // 在registry上将name与obj重新绑定(替换原name的绑定)

  5. registry.rebind(String name, Remote obj);

  6.  
  7. // 在registry上将name解绑(删除name的绑定)

  8. registry.unbind(String name);

  9.  
  10. // 在registry上查找指定name并返回相应的obj对象

  11. registry.lookup(String name);

2.4.4 Naming

有的时候,直接使用Naming类,而不使用LocateRegistry和Registry类。但是,Naming类实际上就是对LocateRegistry和Registry的再一次封装,其内部还是通过这两个类的方法实现绑定操作的。

例如,我们通过LocateRegistry.getRegistry方法来确定Registry的位置,然后通过registry.bind方法绑定name和obj。上述两步骤,可以通过Naming.bind方法一步直接完成,代码如下:

Naming.bind(“rmi://localhost:1099/compute”, stub);

即,通过Naming类方法,在指定name时,需要加上host地址。

Naming类相关方法:

  1. // 将name与obj绑定

  2. Naming.bind(String name, Remote obj);

  3.  
  4. // 将name与obj重新绑定(替换原name的绑定)

  5. Naming.rebind(String name, Remote obj);

  6.  
  7. // 将name解绑(删除name的绑定)

  8. Naming.unbind(String name);

  9.  
  10. // 查找指定name并返回相应的obj对象

  11. Naming.lookup(String name);

3 实现步骤:Client端

3.1 添加接口

将Server的接口文件添加至Client项目。

  1. Public interface Compute extends Remote {

  2. public int multiply(int a, int b) throws RemoteException;

  3. }

3.2 调用接口

  1. public static void main(String args[]) {

  2. try {

  3. String name = "rmi://localhost:1099/compute";

  4. Compute compute = (Compute) Naming.lookup(name);

  5. int result = compute.multiply(3, 5);

  6. } catch (Exception e) {

  7. e.printStackTrace();

  8. }

  9. }

4 执行步骤

4.1 系统参数

java.rmi.server.codebase=”…”

指定类文件路径,远程调用时可能会从该路径加载类文件。

java.rmi.server.useCodebaseOnly=true/false

如果设置为true,将禁用自动加载类文件,仅从CLASSPATH和java.rmi.server.codebase指定路径加载类文件。

java.security.policy=”…”

指定附加或不同的安全策略文件。

4.2 启动Registry

指定系统参数:

-J-D(name=value)

Windows终端:

start rmiregistry –J-Djava.rmi.server.useCodebaseOnly=false

Linux/Mac终端:

rmiregistry –J-Djava.rmi.server.useCodebaseOnly=false

4.3 启动Server

指定系统参数:

-D(name=value)

启动Server:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy="policy.permission" Server

4.4 启动Client

指定系统参数:

-D(name=value)

启动Client:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy="policy.permission" Client

RML执行过程的逻辑归纳

1.server在远程机器上监听一个端口,这个端口是jvm或者os在运行时随机选择的一个端口。可以说server在远程机器上在这个端口上导出自己。
2.client并不知道server在哪,以及sever监听哪个端口,但是他有stub,stub知道所有这些东西,这样client可以调用stub上他想调用的任何方法。
3.client调用给你stub上的方法
4.stub链接server监听的端口并发送参数,详细过程如下:
a.client连接server监听的端口
b.server收到请求并创建一个socket来处理这个链接
c.server继续监听到来的请求
d.使用双方协定的歇息,传送参数和结果
e.协议可以是JRMP或者 iiop
5.方法在远程server上执行,并发执行结果返回给stub
6.stub返回结果给client,就好像是stub执行了这个方法一样

猜你喜欢

转载自blog.csdn.net/lhw_csd/article/details/81196480