远程过程调用(RPC)详解

什么是 RPC

RPC 是远程过程调用(Remote Procedure Call)的缩写形式,Birrell 和 Nelson 在 1984 发表于 ACM Transactions on Computer Systems 的论文《Implementing remote procedure calls》对 RPC 做了经典的诠释。RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行。调用方可以通过使用参数将信息传送给被调用方,而后可以通过传回的结果得到信息。而这一过程,对于开发人员来说是透明的。

标图1 描述了数据报在一个简单的RPC传递的过程题

 

注:上述论文,可以在线阅读 http://www.cs.virginia.edu/~zaher/classes/CS656/birrel.pdf

远程过程调用采用客户机/服务器(C/S)模式。请求程序就是一个客户机,而服务提供程序就是一台服务器。和常规或本地过程调用一样,远程过程调用是同步操作,在远程过程结果返回之前,需要暂时中止请求程序。使用相同地址空间的低权进程或低权线程允许同时运行多个远程过程调用。

RPC 的基本操作

让我们看看本地过程调用是如何实现的。考虑下面的 C 语言的调用:

count = read(fd, buf, nbytes);

其中,fd 为一个整型数,表示一个文件。buf 为一个字符数组,用于存储读入的数据。 nbytes 为另一个整型数,用于记录实际读入的字节数。如果该调用位于主程序中,那么在调用之前堆栈的状态如图2(a)所示。为了进行调用,调用方首先把参数反序压入堆栈,即为最后一个参数先压入,如图2(b)所示。在 read 操作运行完毕后,它将返回值放在某个寄存器中,移出返回地址,并将控制权交回给调用方。调用方随后将参数从堆栈中移出,使堆栈还原到最初的状态。

标图2 过程调用中的参数传递题

RPC 背后的思想是尽量使远程过程调用具有与本地调用相同的形式。假设程序需要从某个文件读取数据,程序员在代码中执行 read 调用来取得数据。在传统的系统中, read 例程由链接器从库中提取出来,然后链接器再将它插入目标程序中。 read 过程是一个短过程,一般通过执行一个等效的 read 系统调用来实现。即,read 过程是一个位于用户代码与本地操作系统之间的接口。

虽然 read 中执行了系统调用,但它本身依然是通过将参数压入堆栈的常规方式调用的。如图2(b)所示,程序员并不知道 read 干了啥。

RPC 是通过类似的途径来获得透明性。当 read 实际上是一个远程过程时(比如在文件服务器所在的机器上运行的过程),库中就放入 read 的另外一个版本,称为客户存根(client stub)。这种版本的 read 过程同样遵循图2(b)的调用次序,这点与原来的 read 过程相同。另一个相同点是其中也执行了本地操作系统调用。唯一不同点是它不要求操作系统提供数据,而是将参数打包成消息,而后请求此消息发送到服务器,如图3所示。在对 send 的调用后,客户存根调用 receive 过程,随即阻塞自己,直到收到响应消息。

图3 客户与服务器之间的RPC原理标题

 当消息到达服务器时,服务器上的操作系统将它传递给服务器存根(server stub)。服务器存根是客户存根在服务器端的等价物,也是一段代码,用来将通过网络输入的请求转换为本地过程调用。服务器存根一般先调用 receive ,然后被阻塞,等待消息输入。收到消息后,服务器将参数由消息中提取出来,然后以常规方式调用服务器上的相应过程(如图3所示)。从服务器角度看,过程好像是由客户直接调用的一样:参数和返回地址都位于堆栈中,一切都很正常。服务器执行所要求的操作,随后将得到的结果以常规的方式返回给调用方。以 read 为例,服务器将用数据填充 read 中第二个参数指向的缓冲区,该缓存区是属于服务器存根内部的。

调用完后,服务器存根要将控制权教会给客户发出调用的过程,它将结果(缓冲区)打包成消息,随后调用 send 将结果返回给客户。事后,服务器存根一般会再次调用 receive,等待下一个输入的请求。

客户机器接收到消息后,客户操作系统发现该消息属于某个客户进程(实际上该进程是客户存根,只是操作系统无法区分二者)。操作系统将消息复制到相应的缓存区中,随后解除对客户进程的阻塞。客户存根检查该消息,将结果提取出来并复制给调用者,而后以通常的方式返回。当调用者在 read 调用进行完毕后重新获得控制权时,它所知道的唯一事就是已经得到了所需的数据。它不指导操作是在本地操作系统进行,还是远程完成。

整个方法,客户方可以简单地忽略不关心的内容。客户所涉及的操作只是执行普通的(本地)过程调用来访问远程服务,它并不需要直接调用 send 和 receive 。消息传递的所有细节都隐藏在双方的库过程中,就像传统库隐藏了执行实际系统调用的细节一样。

概况来说,远程过程调用包含如下步骤:

  1. 客户过程以正常的方式调用客户存根;
  2. 客户存根生成一个消息,然后调用本地操作系统;
  3. 客户端操作系统将消息发送给远程操作系统;
  4. 远程操作系统将消息交给服务器存根;
  5. 服务器存根调将参数提取出来,而后调用服务器;
  6. 服务器执行要求的操作,操作完成后将结果返回给服务器存根;
  7. 服务器存根将结果打包成一个消息,而后调用本地操作系统;
  8. 服务器操作系统将含有结果的消息发送给客户端操作系统;
  9. 客户端操作系统将消息交给客户存根;
  10. 客户存根将结果从消息中提取出来,返回给调用它的客户存根。

以上步骤就是将客户过程对客户存根发出的本地调用转换成对服务器过程的本地调用,而客户端和服务器都不会意识到中间步骤的存在。

RPC 的主要好处是双重的。首先,程序员可以使用过程调用语义来调用远程函数并获取响应。其次,简化了编写分布式应用程序的难度,因为 RPC 隐藏了所有的网络代码存根函数。应用程序不必担心一些细节,比如 socket、端口号以及数据的转换和解析。在 OSI 参考模型,RPC 跨越了会话层和表示层。

实现远程过程调用

要实现远程过程调用,需考虑以下几个问题。

如何传递参数

传递值参数

传递值参数比较简单,下图图展示了一个简单 RPC 进行远程计算的例子。其中,远程过程 add(i,j) 有两个参数 i 和 j, 其结果是返回 i 和 j 的算术和。

标题图4 通过RPC进行远程计算的步骤

通过 RPC 进行远程计算的步骤有:

  1. 将参数放入消息中,并在消息中添加要调用的过程的名称或者编码。
  2. 消息到达服务器后,服务器存根堆该消息进行分析,以判明需要调用哪个过程,随后执行相应的调用。
  3. 服务器运行完毕后,服务器存根将服务器得到的结果打包成消息送回客户存根,客户存根将结果从消息中提取出来,把结果值返回给客户端。

当然,这里只是做了简单的演示,在实际分布式系统中,还需要考虑其他情况,因为不同的机器对于数字、字符和其他类型的数据项的表示方式常有差异。比如整数型,就有 Big Endian 和 Little Endian 之分。

传递引用参数

传递引用参数相对来说比较困难。单纯传递参数的引用(也包含指针)是完全没有意义的,因为引用地址传递给远程计算机,其指向的内存位置可能跟远程系统上完全不同。如果你想支持传递引用参数,你必须发送参数的副本,将它们放置在远程系统内存中,向他们传递一个指向服务器函数的指针,然后将对象发送回客户端,复制它的引用。如果远程过程调用必须支持引用复杂的结构,比如树和链表,他们需要将结构复制到一个无指针的表示里面(比如,一个扁平的树),并传输到在远程端来重建数据结构。

如何表示数据

在本地系统上不存在数据不相容的问题,因为数据格式总是相同的。而在分布式系统中,不同远程机器上可能有不同的字节顺序,不同大小的整数,以及不同的浮点表示。对于 RPC,如果想与异构系统通信,我们就需要想出一个“标准”来对所有数据类型进行编码,并可以作为参数传递。例如,ONC RPC 使用 XDR (eXternal Data Representation) 格式 。这些数据表示格式可以使用隐式或显式类型。隐式类型,是指只传递值,而不传递变量的名称或类型。常见的例子是 ONC RPC 的 XDR 和 DCE RPC 的 NDR。显式类型,指需要传递每个字段的类型以及值。常见的例子是 ISO 标准 ASN.1 (Abstract Syntax Notation)、JSON (JavaScript Object Notation)、Google Protocol Buffers、以及各种基于 XML 的数据表示格式。

如何选用传输协议

有些实现只允许使用一个协议(例如 TCP )。大多数 RPC 实现支持几个,并允许用户选择。

出错时,会发生什么

相比于本地过程调用,远程过程调用出错的机会将会更多。由于本地过程调用没有过程调用失败的概念,项目使用远程过程调用必须准备测试远程过程调用的失败或捕获异常。

远程调用的语义是什么

调用一个普通的过程语义很简单:当我们调用时,过程被执行。远程过程完全一次性调用成功是非常难以实现。执行远程过程可以有如下结果:

  • 如果服务器崩溃或进程在运行服务器代码之前就死了,那么远程过程会被执行0次;
  • 如果一切工作正常,远程过程会被执行1次;
  • 如果服务器返回服务器存根后在发送响应前就奔溃了,远程过程会被执行1次或者多次。客户端接收不到返回的响应,可以决定再试一次,因此出现多次执行函数。如果没有再试一次,函数执行一次;
  • 如果客户机超时和重新传输,那么远程过程会被执行多次。也有可能是原始请求延迟了。两者都可能会执行或不执行。

RPC 系统通常会提供至少一次或最多一次的语义,或者在两者之间选择。如果需要了解应用程序的性质和远程过程的功能是否安全,可以通过多次调用同一个函数来验证。如果一个函数可以运行任何次数而不影响结果,这是幂等(idempotent)函数的,如每天的时间、数学函数、读取静态数据等。否则,它是一个非幂等(nonidempotent)函数,如添加或修改一个文件)。

远程调用的性能怎么样

毫无疑问,一个远程过程调用将会比常规的本地过程调用慢得多,因为产生了额外的步骤以及网络传输本身存在延迟。然而,这并不应该阻止我们使用远程过程调用。

远程调用安全吗?

使用 RPC,我们必须关注各种安全问题:

  • 客户端发送消息到远程过程,那个过程是可信的吗?
  • 客户端发送消息到远程计算机,那个远程机器是可信的吗?
  • 服务器如何验证接收的消息是来自合法的客户端吗?服务器如何识别客户端?
  • 消息在网络中传播如何防止时被其他进程嗅探?
  • 可以由其他进程消息被拦截和修改时遍历网络从客户端到服务器或服务器端?
  • 协议能防止重播攻击吗?
  • 如何防止消息在网络传播中被意外损坏或截断?

远程过程调用的优点

远程过程调用有诸多的优点:

  • 你不必担心传输地址问题。服务器可以绑定到任何可用的端口,然后用 RPC 名称服务来注册端口。客户端将通过该名称服务来找到对应的端口号所需要的程序。而这一切对于程序员来说是透明的。
  • 系统可以独立于传输提供者。自动生成服务器存根使其可以在系统上的任何一个传输提供者上可用,包括 TCP 和 UDP,而这些,客户端可以动态选择的。当代码发送以后,接收消息是自动生成的,而不需要额外的编程代码。
  • 应用程序在客户端只需要知道一个传输地址——名称服务,负责告诉应用程序去哪里连接服务器函数集。
  • 使用函数调用模型来代替 socket 的发送/接收(读/写)接口。用户不需要处理参数的解析。

RPC API

任何 RPC 实现都需要提供一组支持库。这些包括:

  • 名称服务操作: 注册和查找绑定信息(端口、机器)。允许一个应用程序使用动态端口(操作系统分配的);
  • 绑定操作:使用适当的协议建立客户机/服务器通信(建立通信端点);
  • 终端操作:注册端点信息(协议、端口号、机器名)到名称服务并监听过程调用请求。这些函数通常被自动生成的主程序——服务器存根(骨架)所调用;
  • 安全操作:系统应该提供机制保证客户端和服务器之间能够相互验证,两者之间提供一个安全的通信通道;
  • 国际化操作(可能):这是很少的一部分 RPC 包可能包括了转换时间格式、货币格式和特定于语言的在字符串表的字符串的功能;
  • 封送处理/数据转换操作:函数将数据序列化为一个普通的的字节数组,通过网络进行传递,并能够重建;
  • 存根内存管理和垃圾收集:存根可能需要分配内存来存储参数,特别是模拟引用传递语义。RPC 包需要分配和清理任何这样的分配。他们也可能需要为创建网络缓冲区而分配内存。RPC 包支持对象,RPC 系统需要一种跟踪远程客户端是否仍有引用对象或一个对象是否可以删除。
  • 程序标识操作:允许应用程序访问(或处理) RPC 接口集的标识符,这样的服务器提供的接口集可以被用来交流和使用。
  • 对象和函数的标识操作: 允许将远程函数或远程对象的引用传递给其他进程。并不是所有的 RPC 系统都支持。

所以,判断一种通信方式是否是 RPC,就看它是否提供上述的 API。

 Java RMI

CORBA 旨在提供一组全面的服务来管理在异构环境中(不同语言、操作系统、网络)的对象。Java 在其最初只支持通过 socket 来实现分布式通信。1995年,作为 Java 的缔造者,Sun 公司开始创建一个 Java 的扩展,称为 Java RMI(Remote Method Invocation,远程方法调用)。Java RMI 允许程序员创建分布式应用程序时,可以从其他 Java 虚拟机(JVM)调用远程对象的方法。

一旦应用程序(客户端)引用了远程对象,就可以进行远程调用了。这是通过 RMI 提供的命名服务(RMI 注册中心)来查找远程对象,来接收作为返回值的引用。Java RMI 在概念上类似于 RPC,但能在不同地址空间支持对象调用的语义。

与大多数其他诸如 CORBA 的 RPC 系统不同,RMI 只支持基于 Java 来构建,但也正是这个原因, RMI 对于语言来说更加整洁,无需做额外的数据序列化工作。Java RMI 的设计目标应该是:

  • 能够适应语言、集成到语言、易于使用;
  • 支持无缝的远程调用对象;
  • 支持服务器到 applet 的回调;
  • 保障 Java 对象的安全环境;
  • 支持分布式垃圾回收;
  • 支持多种传输。

分布式对象模型与本地 Java 对象模型相似点在于:

  • 引用一个对象可以作为参数传递或作为返回的结果;
  • 远程对象可以投到任何使用 Java 语法实现的远程接口的集合上;
  • 内置 Java instanceof 操作符可以用来测试远程对象是否支持远程接口。

不同点在于:

  • 远程对象的类是与远程接口进行交互,而不是与这些接口的实现类交互;
  • Non-remote 参数对于远程方法调用来说是通过复制,而不是通过引用;
  • 远程对象是通过引用来传递,而不是复制实际的远程实现;
  • 客户端必须处理额外的异常。

接口和类

所有的远程接口都继承自 java.rmi.Remote 接口。例如:

public interface bankaccount extends Remote{
    public void deposit(float amount)
        throws java.rmi.RemoteException;

    public void withdraw(float amount)
        throws OverdrawnException,
        java.rmi.RemoteException;
}

注意,每个方法必须在 throws 里面声明 java.rmi.RemoteException 。 只要客户端调用远程方法出现失败,这个异常就会抛出。

远程对象类

Java.rmi.server.RemoteObject 类提供了远程对象实现的语义包括hashCode、equals和 toString。

Java.rmi.server.RemoteServer及其子类提供让对象实现远程可见。

java.rmi.server.UnicastRemoteObject 类定义了客户机与服务器对象实例建立一对一的连接。

存根

Java RMI 通过创建存根函数来工作。存根由 rmic 编译器生成。自 Java 1.5 以来,Java 支持在运行时动态生成存根类。编译器 rmic 会提供各种编译选项。

定位对象

引导名称服务提供了用于存储对远程对象的命名引用。一个远程对象引用可以存储使用类 java.rmi.Naming 提供的基于 URL 的方法。例如:

BankAccount acct = new BankAcctImpl();
String url = "rmi://java.sun.com/account";
// bind url to remote object
java.rmi.Naming.bind(url, acct);

// look up account
acct = (BankAccount)java.rmi.Naming.lookup(url);
标图7 Java RMI 工作流程题

RMI 架构

RMI 是一个三层架构(图8)。最上面是 Stub/Skeleton layer(存根/骨架层)。方法调用从 Stub、Remote Reference Layer (远程引用层)和 Transport Layer(传输层)向下,传递给主机,然后再次经传 Transport Layer 层,向上穿过 Remote Reference Layer 和 Skeleton ,到达服务器对象。 Stub 扮演着远程服务器对象的代理的角色,使该对象可被客户激活。Remote Reference Layer 处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。Transport Layer 管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的 Skeleton 完成对服务器对象实际的方法调用,并获取返回值。返回值向下经 Remote Reference Layer 、服务器端的 Transport Layer 传递回客户端,再向上经 Transport Layer 和 Remote Reference Layer 返回。最后,Stub 程序获得返回值。

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

  • 生成一个远程接口;
  • 实现远程对象(服务器端程序);
  • 生成 Stub 和 Skeleton(服务器端程序);
  • 编写服务器程序 ;
  • 编写客户程序 ;
  • 注册远程对象;
  • 启动远程对象
标题图8 Java RMI 架构

RMI 分布式垃圾回收

根据 Java 虚拟机的垃圾回收机制原理,在分布式环境下,服务器进程需要知道哪些对象不再由客户端引用,从而可以被删除(垃圾回收)。在 JVM中,Java 使用引用计数。当引用计数归零时,对象将会垃圾回收。在RMI,Java 支持两种操作:dirty 和 clean。本地 JVM 定期发送一个 dirty 到服务器来说明该对象仍在使用。定期重发 dirty 的周期是由服务器租赁时间来决定的。当客户端没有需要更多的本地引用远程对象时,它发送一个 clean 调用给服务器。不像 DCOM,服务器不需要计算每个客户机使用的对象,只是简单的做下通知。如果它租赁时间到期之前没有接收到任何 dirty 或者 clean 的消息,则可以安排将对象删除。

第三代 RPC 以及 Web Services

由于互联网的兴起,Web 浏览器成为占主导地位的用于访问信息的模型。现在的应用设计的首要任务大多数是提供用户通过浏览器来访问,而不是编程访问或操作数据。

网页设计关注的是内容。解析展现方面往往是繁琐的。传统 RPC 解决方案可以工作在互联网上,但问题是,他们通常严重依赖于动态端口分配,往往要进行额外的防火墙配置。

Web Services 成为一组协议,允许服务被发布、发现,并用于技术无关的形式。即服务不应该依赖于客户的语言、操作系统或机器架构。

Web Services 的实现一般是使用 Web 服务器作为服务请求的管道。客户端访问该服务,首先是通过一个 HTTP 协议发送请求到服务器上的 Web 服务器。Web 服务器配置识别 URL 的一部分路径名或文件名后缀并将请求传递给特定的浏览器插件模块。这个模块可以除去头、解析数据(如果需要),并根据需要调用其他函数或模块。对于这个实现流,一个常见的例子是浏览器对于 Java Servlet 的支持。HTTP 请求会被转发到 JVM 运行的服务端代码来执行处理。

XML-RPC

XML-RPC 是1998年作为一个 RPC 消息传递协议,将请求和响应封装解析为人类可读的 XML 格式。XML 格式基于 HTTP 协议,缓解了传统企业的防火墙需要为 RPC 服务器应用程序打开额外的端口的问题。

下面是一个 XML-RPC 消息的例子:

<methodCall>
    <methodName>
        sample.sumAndDifference
    </methodName>
    <params>
        <param><value><int> 5 </int></value></param>
        <param><value><int> 3 </int></value></param>
    </params>
</methodCall>

这个例子中,方法 sumAndDifference 有两个整数参数 5 和 3。

XML-RPC 支持的基本数据类型是:int、string、boolean、double 和 dateTime.iso8601。此外,还有 base64 类型用于编码任意二进制数据。array 和 struct 允许定义数组和结构。

XML-RPC 不限制语任何特定的语言,也不是一套完整的软件来处理远程过程,诸如存根生成、对象管理和服务查找都不在协议内。现在有很多库针可以针对不同的语言,比如 Apache XML-RPC 可以用于 Java、Python 和 Perl。

XML-RPC 是一个简单的规范(约7页),没有雄心勃勃的目标——它只关注消息,而并不处理诸如垃圾收集、远程对象、远程过程的名称服务和其他方面的问题。然而,即使没有广泛的产业支持,简单的协议却能广泛采用。

SOAP

SOAP(Simple Object Access Protocol,简单对象访问协议),是以 XML-RPC 规范作为创建 SOAP 的依据,成立于1998年,获得微软和 IBM 的大力支持。该协议在创建初期只作为一种对象访问协议,但由于 SOAP 的发展,其协议已经不单只是用于简单的访问对象,所以这种 SOAP 缩写已经在标准的1.2版后被废止了。1.2版在2003年6月24日成为 W3C 的推荐版本。SOAP 指定 XML 作为无状态的消息交换格式,包括了 RPC 式的过程调用。

有关 SOAP 的标准可以参阅 https://www.w3.org/TR/soap/

SOAP 只是一种消息格式,并未定义垃圾回收、对象引用、存根生成和传输协议。

下面是一个简单的例子:

<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"> 
 <env:Header>
  <m:reservation xmlns:m="http://travelcompany.example.org/reservation" 
          env:role="http://www.w3.org/2003/05/soap-envelope/role/next"
           env:mustUnderstand="true">
   <m:reference>uuid:093a2da1-q345-739r-ba5d-pqff98fe8j7d</m:reference>
   <m:dateAndTime>2001-11-29T13:20:00.000-05:00</m:dateAndTime>
  </m:reservation>
  <n:passenger xmlns:n="http://mycompany.example.com/employees"
          env:role="http://www.w3.org/2003/05/soap-envelope/role/next"
           env:mustUnderstand="true">
   <n:name>Åke Jógvan Øyvind</n:name>
  </n:passenger>
 </env:Header>
 <env:Body>
  <p:itinerary
    xmlns:p="http://travelcompany.example.org/reservation/travel">
   <p:departure>
     <p:departing>New York</p:departing>
     <p:arriving>Los Angeles</p:arriving>
     <p:departureDate>2001-12-14</p:departureDate>
     <p:departureTime>late afternoon</p:departureTime>
     <p:seatPreference>aisle</p:seatPreference>
   </p:departure>
   <p:return>
     <p:departing>Los Angeles</p:departing>
     <p:arriving>New York</p:arriving>
     <p:departureDate>2001-12-20</p:departureDate>
     <p:departureTime>mid-morning</p:departureTime>
     <p:seatPreference/>
   </p:return>
  </p:itinerary>
  <q:lodging
   xmlns:q="http://travelcompany.example.org/reservation/hotels">
   <q:preference>none</q:preference>
  </q:lodging>
 </env:Body>
</env:Envelope>

 其中<soap:Envelope>是 SOAP 消息中的根节点,是 SOAP 消息中必须的部分。<soap:Header>是 SOAP 消息中可选部分,是指消息头。<soap:Body>是 SOAP 中必须部分,是指消息体。

上面例子的 SOAP 消息结构如下:

图9 SOAP 消息结构标题

SOAP 它只是提供了一个标准化的消息结构,为了实现它往往需要用 WSDL 来描述 Web Services 的方法。WSDL (Web Services Description Language) 是基于 XML 的一种用于描述 Web Services 以及如何访问 Web Services 的语言。

WSDL 文档包括以下几个部分:

  • 类型(Types):定义了 Web Services 使用的数据类型;
  • 消息(n/a):描述使用消息的数据元素或参数;
  • 接口(Interface):描述服务提供的操作。这包括操作以及每个操作所使用的输入和输出消息;
  • 绑定(Binding):为每个端口定义消息格式和协议细节。例如,它可以定义 RPC 式的消息;
  • 服务(Service):系统功能相关的集合,包括其关联的接口、操作、消息等;
  • 终点(Endpoint):定义了地址或者 Web Services 的连接点;
  • 操作(Operation):定义了 SOAP 的动作,以及消息编码的方式。

下面是一个 WSDL 2.0 版本的例子:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://www.w3.org/ns/wsdl" 
             xmlns:tns="http://www.tmsws.com/wsdl20sample" 
             xmlns:whttp="http://schemas.xmlsoap.org/wsdl/http/"
             xmlns:wsoap="http://schemas.xmlsoap.org/wsdl/soap/"
             targetNamespace="http://www.tmsws.com/wsdl20sample">

<documentation>
    This is a sample WSDL 2.0 document. 
</documentation>

   <!-- Abstract type -->
   <types>
      <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns="http://www.tmsws.com/wsdl20sample"
                targetNamespace="http://www.example.com/wsdl20sample">
                 
         <xs:element name="request"> ... </xs:element>
         <xs:element name="response"> ... </xs:element>
      </xs:schema>
   </types>

   <!-- Abstract interfaces -->
   <interface name="Interface1">
      <fault name="Error1" element="tns:response"/>
      <operation name="Get" pattern="http://www.w3.org/ns/wsdl/in-out">
         <input messageLabel="In" element="tns:request"/>
         <output messageLabel="Out" element="tns:response"/>
      </operation>
   </interface>

   <!-- Concrete Binding Over HTTP -->
   <binding name="HttpBinding" interface="tns:Interface1" 
            type="http://www.w3.org/ns/wsdl/http">
      <operation ref="tns:Get" whttp:method="GET"/>
   </binding>
   
   <!-- Concrete Binding with SOAP-->
   <binding name="SoapBinding" interface="tns:Interface1" 
            type="http://www.w3.org/ns/wsdl/soap" 
            wsoap:protocol="http://www.w3.org/2003/05/soap/bindings/HTTP/"
            wsoap:mepDefault="http://www.w3.org/2003/05/soap/mep/request-response">
      <operation ref="tns:Get" />
   </binding>

   <!-- Web Service offering endpoints for both bindings-->
   <service name="Service1" interface="tns:Interface1">
      <endpoint name="HttpEndpoint" 
                binding="tns:HttpBinding" 
                address="http://www.example.com/rest/"/>
      <endpoint name="SoapEndpoint" 
                binding="tns:SoapBinding" 
                address="http://www.example.com/soap/"/>
   </service>
</description>

Java 中的 XML Web Services

Java RMI 与远程对象进行交互,其实现是需要基于 Java 的模型。此外,它没有使用 Web Services 和基于 HTTP 的消息传递。现在,已经出现了大量的软件来支持基于 Java 的 Web Services。JAX-WS (Java API for XML Web Services) 就是作为 Web Services 消息息和远程过程调用的规范。它允许一个调用基于Java的web服务使用Java RMI(即。,相对透明的程序员)。JAX-WS 的一个目标是平台互操作性。其 API 使用 SOAP 和WSDL。双方不需要 Java 环境。

创建一个 RPC 端点

在服务器端,进行下面的步骤来创建一个 RPC 端点:

  • 定义一个接口(Java接口);
  • 实现服务;
  • 创建一个发布者,用于创建服务的实例,并发布一个服务名字。

在客户端:

  • 创建一个代理(客户端存根)。wsimport 命令根据 WSDL 文档,创建一个客户机存根;
  • 编写一个客户端,通过代理创建远程服务的一个实例(存根),调用它的方法。

JAX-RPC 执行流程如下:

  • Java 客户机调用存根上的方法(代理);
  • 存根调用适当的 Web 服务;
  • Web 服务器被调用并指导 JAX-WS 框架;
  • 框架调用实现;
  • 实现返回结果给该框架;
  • 该框架将结果返回给 Web 服务器;
  • 服务器将结果发送给客户端存根;
  • 客户端存根返回信息给调用者;
图12 JAX-WS 调用流程标题

超越 SOAP

SOAP 虽然仍然是广泛部署应用,但在许多环境中很多厂商已经抛弃了 SOAP,转而使用其他更轻量、更容易理解、或者与 Web 交互模型更干净的机制。例如,Google 的 API 在2006年后就不再支持 SOAP 接口,而是使用AJAX、XML-RPC 和 REST 作为替代。一个匿名的微软员工批评 SOAP 过于复杂,因为“我们希望我们的工具来阅读它,而不是人”。不管上述言论是否准确,有一点是可以肯定的,SOAP 显然是一个复杂和高度冗长的格式。

AJAX

Web 浏览器最初的设计,是为 Web 页面提供非动态的交互模型。Web 浏览器是建立在同步的请求-响应(request-response)的交互模型。发送一个请求到服务器,服务器返回整个页面。在当时没有更新部分页面的好方法,而唯一可行的方法是利用帧,即将不同的页面加载到每一帧,其实现是笨重的,也有很大的限制性。而改变了这一切的关键因素是:

  • 文档对象模型(Document Object Model)和 JavaScript 的出现,使得可以以编程方式来更改 Web 页面的各个部分;
  • AJAX 提供了与服务器以非阻塞方式进行交互,即允许底层 JavaScript 在等待服务器结果时,用户仍然可以与页面进行交互。

AJAX 全称是 Asynchronous JavaScript And XML(异步的 JavaScript 和 XML)。让我们看看这些三项:

  • 它是异步的,因为客户端等待服务器结果不会被阻塞;
  • AJAX 集成到了 JavaScript,作为浏览器解释 Web 页面的一部分。JavaScript 使用 HTTPRequest 来调用 AJAX 请求。JavaScript 也可能修改文档对象模型,定义了页面的样子;
  • 数据以 XML 文档形式发送和接收。(在后期发展中,AJAX 也支持其他的数据格式,比如 JSON)

AJAX 在推动 Web 2.0 的过程中发挥了重要的,比如产生了很多高度交互的服务,如Google Maps、Writely等。基本上,它允许 JavaScript 发出HTTP 请求,获取和处理结果,刷新局部页面元素而不是整个页面。在大多数浏览器请求的格式如下:

new XMLHttpRequest()
xmlhttp.open(“HEAD”, “index.html”, true)Tell object:

REST

SOAP 在创建自己的消息传递协议时是基于HTTP,但实际上 REST (REpresentational State Transfer) 的方式才是保持 Web 的原理和使用 HTTP 协议的核心部分。

原始的 HTTP 协议已经定义了四个命令,清晰地映射到各种数据(定义为“资源”)操作:

  • PUT (插入)
  • GET (选择)
  • POST (更新)
  • DELETE (删除)

REST 其背后的理念是使用这些 HTTP 命令来请求和操作数据。作为 HTTP协议的一部分,REST 使用 URL 来引用对象和操作。考虑这个 HTTP 操作列表的例子:

HTTP GET //www.waylau.com/parts

这个命令将返回一个 XML 文档,其中包含部分的列表。注意,返回的不是一个网页,只是一个包含所请求的数据 XML 数据结构。

<?xml version="1.0"?>
<p:Parts xmlns:p="http://www.waylau.com" 
         xmlns:xlink="http://www.w3.org/1999/xlink">
      <Part id="00345" xlink:href="http://www.waylau.com/parts/00345"/>
      <Part id="00346" xlink:href="http://www.waylau.com/parts/00346"/>
      <Part id="00347" xlink:href="http://www.waylau.com/parts/00347"/>
      <Part id="00348" xlink:href="http://www.waylau.com/parts/00348"/>
</p:Parts>

要特定部分的详细信息,发送一个HTTP get 命令:

HTTP GET //www.waylau.com/parts/00345

这将返回一个特定的信息部分:

<?xml version="1.0"?>
<p:Part xmlns:p="http://www.waylau.com"   
        xmlns:xlink="http://www.w3.org/1999/xlink">
      <Part-ID>00345</Part-ID>
      <Name>Widget-A</Name>
      <Description>This part is used within the frap assembly</Description>
      <Specification xlink:href="http://www.waylau.com/parts/00345/specification"/>
      <UnitCost currency="USD">0.10</UnitCost>
      <Quantity>10</Quantity>
</p:Part>

注意,上面例子简化了 partid 作为 URL 的参数。例如: 

HTTP GET //www.waylau.com/parts?partid=00345

REST 不是 RPC,但也有类似的请求-响应模式。制定透明度请求、封送数据、解析响应这些不属于 REST。REST 应用非常广泛,如 Yahoo! Search API、Ruby on Rails、Twiter 和 Open Zing Services 等。

JSON

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于 ECMAScript 的一个子集。JSON 采用完全独立于语言的文本格式,但是也使用了类似于 C 语言家族的习惯(包括C、C++、C#、Java、JavaScript、Perl、Python 等)。这些特性使 JSON 成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成。JSON 不是一个诸如 Google Protocol Buffers 的二进制格式,因此适合使用基于 HTTP的消息传递。JSON 是可以作为 XML 替代品,在远程过程调用中,很多语言都支持 JSON-RPC。记住,这只是一个消息传递格式,JSON 并没有试图提供 RPC 库来支持服务发现、绑定、托管和垃圾收集。

参考引用

本文摘录自:https://waylau.com/remote-procedure-calls/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值