远程过程调用(RPC)简介

Remote Procedure Calls

本文译自:Remote Procedure Calls

简介

sockets是客户端/服务器网络通信模型中的基础,它为程序与程序之间建立连接、收发信息提供了相对简单的机制(甚至可以使用read/write系统调用),两个程序可以位于同一个主机,也可以位于不同的主机。

然而,这种接口方式在某种程序上迫使我们使用read/write接口设计分布式应用系统,而这不是我们通常所设计集中式应用系统的方法。

在集中式应用系统设计中,过程调用是标准的接口模式。如果想要在集中式计算系统中使用分布式系统的计算能力,那么一般不使用基于输入/输出的通信方法。

1984年,Birrell and Nelson设计了一种使程序可以调用其他主机上方法的机制,主机A上的进程可以调用主机B上的方法。

当主机A的进程调用主机B上的方法时,A进程挂起,B进程开始执行,B执行结束后结果返回给A,A进程继续的执行。这个机制就是RPC(远程过程调用)。

对程序员来说,这一切跟普通的过程调用一样,但是很明显,RPC与本地调用在实现机理是大不相同的。

RPC步骤

先来看一下本地调用的执行过程。不同的编译器和架构下执行流程不同,这里仅概述一般流程。

  • 处理器执行调用指令,它把下一指令的地址压入栈中,并把控制流传递到调用指定的地址

  • 调用过程结束,产生返回指令

  • 弹出栈顶地址,传递控制流

这些基本的处理器机制使得过程调用流程很简单。识别参数、放入椎栈、执行调用指令的实际细节取决于使用的编译器。

在被调用函数中,编译器负责确保寄存器数据使用得当、为局部变量分配栈空间、恢复寄存器和待返回的栈顶指针。

这一切在远程主机上的过程调用中不再适用。这意味着编译器必须做一些改变以提供远程过程调用功能。这使得语言级架构的RPC与操作系统级别的sockets完全不同,

我们将使用我们拥有的工具来模拟远程过程调用,即本地过程调用和用于网络通信的套接字。

远程过程调用的核心是创建存根函数(stub functions)((知乎上的探讨)[https://www.zhihu.com/question/24844900]),使使用者看到调用像是本地的。

在客户端,使用者调用了一个stub function,它实际上包含通过网络收发数据的代码实现。下图展示了执行流程( p. 693 of W. Richard Steven’s UNIX Network Programming)。

Figure 1. Steps in executing a remote procedure call

执行流程如下:

  1. 客户端调用称为client stub的本地过程。对客户进程来说,因为它就是一个普通的本地过程,这一切就好象是一个真实的本地过程调用。只是真正的进程在服务器上。client stub把参数打包发送给远端进程(这一过程包括把参数转换成标准格式)。把参数打包成网络消息的过程称为编组(marshaling),还要求将所有数据元素序列化为平面数组字节格式。

  2. client stub把网络消息发送到远端系统(通过使用系统调用sockets接口调用本地内核)

  3. 内核通过某种协议把网络消息传递到远端系统(可能是无连接也可能是有连接的)

  4. server stub,也称为skeleton,在服务器上接收消息。它从消息中反编组参数,需要时将它们从标准网络格式转换为主机格式

  5. server stub调用服务器函数(对客户端来说就是远程过程),并把从客户端接收到的参数传递给它

  6. 服务器函数处理完成后,把返回值返回给server stub

  7. server stub在需要时将返回值编组转换为若干网络消息参数给client stub

  8. 消息通过网络发送到client stub

  9. client stub从内核获得消息

  10. client stub把结果返回给客户端函数,在需要时把数据从网络格式转换为本地格式

至此,客户端程序继续执行。

RPC的主机优势有两个方面:

  • 程序员可以使用过程调用的方法调用远程函数并获得结果

  • 由于RPC把网络代码隐藏在stub functions中,开发分机式应用系统得到简化

应用程序员不必关心诸如sockets、端口号、数据转换和解析之类的细节问题。在OSI参数模型中,RPC在会话层和表示层之间架起了桥梁(5层和6层)。

RPC的应用

应用RPC时会遇到下列一些问题。

参数传递

传值方式比较简单,只要把数据拷贝到网络消息中就行了。传址方式就比较困难,往远程主机传递地址是没有意义的。

如果需要支持传址方式,就要发送参数的副本,把它们存储在远程主机内存里,再把指向它的指针传递给服务器函数。然后服务器把对象返回给客户端,通过地址方式拷贝。

如果RPC需要引用复杂的数据结构,如树和链表,就需要把结构拷贝到无指针形式(如扁平树结构),传递后在远端重组数据结构。

数据表示

在本地系统中数据格式都是一致的,不存在数据兼容性问题。使用RPC,远程机器可能具有不同的字节顺序,不同的整数大小和不同的浮点表示。

比如,大端序会把高字节存储在内存低地址,小端序则把高字节存储在内存高地址。较老的处理器,比如Sun SPARCs 和 Motorola 680x0s使用大端序,Intel系统使用小端序,这种存储格式是主流。

有些处理器架构使用双端格式,处理器可以在引导时配置为以小端或大端模式操作。 这些处理器的示例包括ARM,MIPS,PowerPC,SPARC v9和Intel IA-64(Itanium)。

IP协议套件规定,在协议头中使用大端序填充16bit和32bit字段,以此来解决大小端序不一致的问题,这可以通过使用htons和htonl函数来实现。

对于RPC,如果我们要与异构系统通信,我们需要为所有可以作为参数传递的数据类型提供“标准”编码。

例如,ONC RPC使用XDR编码格式(外部数据表示)。这些数据表示格式可以使用隐式或显式类型。在隐式类型中,变量的名称和类型不传递,仅传递变量值。ONC RPC’s XDR 和 DCE RPC’s NDR使用隐式数据表达方式。

显式类型中,各个字段的类型和值一并传递。ISO标准的ASN.1(Abstract Syntax Notation), JSON (JavaScript Object Notation), Google Protocol Buffers 和各种基于 XML 的数据表达格式使用显式类型。

绑定的主机和端口

我们需要在客户机上找到远程主机和正确的进程(端口或传输地址)。

一种解决方案是维护一个集中式数据库,该数据库可以定位提供某种服务的主机。这是Birell and Nelson的1984年介绍RPC的论文中提出的方法。服务器向中央机构发送消息,表明其可以接受某些远程过程调用。 然后,客户在需要查找服务时联系该中央机构。

另一种不太优雅但易于管理的解决方案是要求客户端知道需要联系哪个主机。 该主机上的名称服务器维护本地提供的服务的数据库。

传输协议

某些应用仅使用单一的应用协议,如TCP。大部分RPC应用支持多种协议,用户可以自由选择。

异常处理

错误的发生是多种多样的,如服务器出错,网络异常,服务崩溃,客户丢失等。远程过程调用的透明性在此失效,因为本地过程调用没有过程调用失败的概念。

正因为如此,使用远程过程调用的程序必须准备测试远程过程调用中的失败和异常处理。

远程调用的含义

本地过程调用的含义简单明了:被调用的过程执行且只执行一次。在远程调用中,执行且只执行一次的目标就难以达到。远程调用的执行情况可能是以下几种:

  • 如果服务器崩溃或者服务进程在执行代码前就挂掉了,执行0次
  • 如果一切正常,执行1次
  • 如果服务器在执行完server stub后且在发送响应前崩溃了,可能执行了1次或多次:
    • 客户端没有收到响应,如果重新申请请求,则执行多次,如果不重新申请请求,则执行1次
  • 如果客户端超时并且重发,则执行多次。可能的情况是,原来的请求延迟了,那么多次请求都有可能被执行

RPC系统调用通常会执行最少1次或者最多1次,使用者需要理解应用和远程调用函数的原理,以决定可能发生的多次调用是否合理。

如果一个函数无论执行多少次都得到同样的结果,那么它就是幂等的(如获取时间、执行数学计算、读取一个静态数据等)。否则,就是非幂等的(如追加或者修改一个文件)。

性能

本地调用非常迅速,一般也就几个指令周期。RPC怎么样呢?想一下RPC需要执行的额外步骤。只是调用客户端存根函数并从中返回它会导致过程调用的开销。

除了这些,还要执行参数编组、调用系统网络服务(导致模式转换和内容转换),处理网络延迟,服务器接收消息并开始服务处理流程,反编组参数、调用服务函数,并在返回前重新反向做一遍。

毫无疑问,RPC要慢许多。不难预计远程调用的开销比本地调用慢几千倍。但是,这不应该阻止我们使用远程过程调用,因为通常有很强的理由将函数移动到服务器。

安全性

安全性绝对需要我们考虑。本地调用时,所有的函数限定在一个进程空间内,由操作系统通过进程内存映射进行内存保护,其他进程无法操作或检查函数调用。

使用RPC,我们需要考虑各种各样的安全性问题:

  • 客户端是否向正确的远程进程发送消息,或者该进程是冒名顶替者?
  • 客户端是否向正确的远程主机发送消息,或者该远程主机是冒名顶替者?
  • 服务器是否仅接受来自合法客户端的消息? 服务器可以识别客户端的用户吗?
  • 消息通过网络传输是否会被其他进程嗅探获取?
  • 当消息从客户端到服务器或反向通过网络传输时,消息是否可以被其他进程拦截和修改?
  • 协议是否会受到重播攻击? 也就是说,恶意主机是否可以捕获消息并在以后重新传输它?
  • 网络上的消息是否被意外损坏或截断?

RPC编程

大部分流行的编译语言(如C,C++, Python,Scheme等)在设计时没有内置RPC语法,因此不能生成需要的stub函数。

为了在这些语言中使用RPC,通常的解决方案是由单独的编译器产生客户端和服务器的stub函数。编译器从程序员指定的RPC接口定义中获取其输入。定义使用接口定义语言(Interface Definition Language)写成。

接口定义大体上像是函数原型声明,它枚举函数集合,以及输入和返回参数。RPC编译器运行后,客户端和服务器程序能够通过合适的stub函数编译和连接,如图所示。

Figure 2. Compilation steps for remote procedure calls

需要修改客户端的进程,以初始化RPC机制(如找到服务器并建立连接)并处理RPC中的异常和错误。

RPC的优势

  1. 不必担心获取唯一的传输地址(为主机上的socket选取一个独一无二胡端口号)。服务器能够绑定到任意一个可用的端口,并使用RPC名字服务注册它。客户端连接这个名字服务时,会找到与这它需要的程序相对应的端口号。所有这一切程序员不必关心。
  2. 该系统具有协议无关性。自动生成的server stub能够应用于系统上的任意传输协议,包括TCP/UPD。由于发送和接收消息的代码都是动态产生的,客户端无需编码即可动态选择所需的协议进行通信。
  3. 客户端应用只需要知道一个传输地址:对于给定的一组服务函数,名字服务负责告知应用连接到哪里。
  4. 函数-调用模型能够取代socket提供的send/receive(read/write)接口。使用者不必处理参数编组并在对端解析它们。

RPC的API

任何RPC应用程序需要提供一组它所支持的库。包括:

  • 名字服务操作

    • 注册和查找绑定信息(端口、主机)。使用应用能够使用动态(操作系统分配的)端口。
  • 绑定操作

    • client/server使用适当协议建立连接(建立起通信端点)。
  • 端点操作

    • 为名字服务注册端点信息(协议、端口号和主机名称),并监听调用请求过程。这些函数通常从自动生成的主程序,即服务器存根(骨架)中调用。
  • 安全操作

    • 系统为客户端和服务端提供互相认证的机制,并为它们提供安全通信的通道。
  • 通用化操作(可选的)

    • 这些通常不包含在RPC包里,但可能包括通过字符串表转换时间格式,货币格式和特定于语言的字符串的函数。
  • 编组/数据转换操作

    • 把数据序列化为扁平字节数组的函数,用于在网络上传输,以及重组数据的函数。
  • stub内存管理和垃圾收集

    • stub可能需要为存储的参数分配内存,特别是模拟传递引用语义。RPC数据需要分配并清除所有分配的内存。它们在创建网络缓存时可能也需要分配内存空间。对于支持对象的RPC数据来说,RPC系统需要某种方式来跟踪远程客户端是否还保持着对对象的引用,或者已经删除了这个对象。
  • 进程ID操作

    • 允许应用获取RPC接口集的标识(或句柄),这样就能使用服务器提供的接口集通信。
  • 对象和函数ID操作

    • 允许将对远程函数或远程对象的引用传递给其他进程,不是所有的RPC都支持此功能。

第一代RPC

ONC RPC(原Sun RPC)

Sun是最早推出商业RPC库和RPC编译器的厂商之一,在19世纪80年代的Sun主机上提供的RPC支持Sun的网络文件系统。该协议被ONC(Open Network Computing,一个由Sun和AT&T主导的财团)推广为标准。

它是一个非常轻量级的RPC(功能特性也比较轻量)系统,能够在大部分POSIX和类POSIX系统上运行,包括Linux,SunOS,OS X,和各种BSD衍生品。它通常被称为Sun RPC或ONC RPC。

ONC RPC提供了一个编译器,它接受远程过程接口的定义并生成客户端和服务器存根函数。编译器称为rpcgen。运行编译器之前,编程者需要提供接口定义。接口字义包括函数声明,按版本号分组并由唯一的程序编号标识。

程序编号使客户端能够识别它们所需要的接口,只需服务器仍然支持旧接口,未更新到最新代码的客户端就能通过版本号连接到新服务。

网络上的参数被编组为一种称为XDR的隐式类型化序列化格式(外部数据表示)。这样就能够将参数发送到使用不同字节序、不同大小整数、不同浮点或字符串表示的异构系统。最后,Sun RPC提供了一个运行时库,它实现了必要的协议和套接字例程以支持RPC。

程序员需要编写的就是一个客户程序(client.c),服务程序(server.c)和RPC接口定义(date.x)。使用rpcgen编译RPC接口定义时(以.x结尾的文件,如date.x),会创建3个或4个文件,下面是date.x的例子:

  • date.h

    • 包括程序、版本和函数声明的定义。客户程序和服务程序都应该包含这个头文件。
  • date_svc.c

    • 使用server stub的c代码
  • date_clnt.c

    • 使用client stub的c代码
  • date_xdr.c

    • 包含将数据转换为XDR格式的XDR例程。如果生成了这个文件,应该把它和客户端和服务端程序一场编译和连接

创建客户端和服务器可执行文件的第一步是编译数据定义文件date.x。然后,分别编译客户端和服务端程序,并把它们和rpcgen生成的相应stub函数链接。

在旧版本中,传输协议可以是字符串“tcp”,也可以是字符串“udp”,以指定相应的IP服务RPC。但是这必须和RPC的Linux实现一起使用。为了使接口更加灵活,UNIX System V版本4(自版本5以来的SunOS)网络选择例程允许更通用的规范。他们在配置文件(/ etc / netconfig)中搜索满足您要求的第一个提供程序。最后一个参数可以是:

“netpath”
在NETPATH环境变量中搜索一系列首选传输提供程序

“circuit_n”
在NETPATH列表中找到第一个虚拟电路提供程序,"circuit_n"在NETPATH列表中找到第一个数据报提供程序

“visible”
在/ etc / netconfig中找到第一个可见的传输提供程序

“circuit_v”
在/ etc / netconfig中找到第一个可见的虚拟电路传输提供程序

“datagram_v”
在/ etc / netconfig中找到第一个可见的数据报传输提供程序

每个远程过程调用初始化时都限制为接受单个输入参数以及RPC句柄。系统稍后修改为支持多参数。一些版本的rpcgen默认仍支持单参数,如Apple的OS X。要想传递多个参数,就要定义包含这些参数的结构,初始化并传递这个结构即可。

对远程过程的调用返回指向结果的指针而不是所需的结果。必须修改服务器函数以接受指向RPC定义(.x文件)中声明的值的指针作为输入,并返回指向结果值的指针。在服务器上,指针必须是指向静态数据的指针,否则,当过程返回并且过程的帧被释放时,指向的区域将是未定义的。在客户端上,指针的返回使我们可以区分失败的RPC(空指针)与来自服务器的空返回(间接空指针)。

RPC过程的名称是RPC定义文件中转换为小写的名称,后缀为下划线跟版本号。如,BIN_DATE成为函数bin_date_1的引用。服务器必须实现bin_date_1,客户端代码应该发出对bin_date_1的调用。

执行流程

服务器

当我们启动服务器时,服务器存根代码(程序)运行并将进程置于后台(记得运行ps找到进程,并在不再需要的时候kill掉)。它创建socket并绑定到本地端口,然后调用RPC库函数,svc_register,注册程序号和版本。

这会连接端口映射器。端口映射器是系统启动时启动的独立进程,它会跟踪端口号,版本号和程序号。在 UNIX System V release 4版本中,该进程称为rpcbind。在Linux,OS X和BSD系统上,它称为端口映射。

客户端

启动客户端程序后,它首先使用远程系统名称、程序号、版本号和协议信息调用clnt_create。它与远程系统上的端口映射器联系,以在该系统上查找适当的端口。

客户端然后调用RPC stub函数(在本例中为bin_date_1)。函数发送消息(如数据报)到服务器(使用之前找到的端口)并等待响应。对于数据报服务,如果没有收到回复,它将重新发送请求一定次数。

远程系统接收到消息后,调用服务函数(bin_date_1),把返回值返回给client stub。client stub 最后返回到执行调用的客户端代码。

分布式计算环境RPC


分布式计算环境(DCE)是由开放软件基金会(OFS)设计的一组组件,用于支持分布式应用程序和分布式环境。在与X / Open合并后,该组成为The Open Group。作为DCE的一部分提供的组件包括分布式文件服务,时间服务,目录,服务和其他几个。我们感兴趣的是DCE远程过程调用。

它与Sun RPC非常相似,接口使用Interface definition language(称为Interface Definition Notation)编写。类似Sun RPC,接口定义与函数原型类似。

Sun RPC的一个缺陷是使用一个“唯一”32位数字识别服务器。虽然这是sockets下比16bit数据空间大得多的空间,找出一个独一无二的数据仍然不易。

DCE RPC不让程序员来做挑选数字的工作,从而避免了这个问题。编写应用程序的第一步是使用uuidgen程序获取唯一ID。该程序生成一个原型IDN文件,其中包含一个保证永远不会再次使用的接口ID。它是一个128位值,包含位置代码和时间戳。然后,用户编辑原型文件,填写远程过程声明。

然后,IDN编译器dceidl(类似于rpcgen)生成头文件、client stub和server stub。

Sun RPC的另一个缺点是客户端必须知道服务器所在的主机。然后,它可以向该计算机上的RPC名称服务器请求与其希望访问的程序编号对应的端口号。DCE支持将多台计算机组织到称为单元的管理实体中。每台机器都知道如何与负责维护单元服务信息的机器进行通信:单元目录服务器。

使用Sun RPC,服务器仅使用本地名称服务器(portmapper)将其{程序编号注册到端口映射}。在DCE下,服务器使用RPC守护程序(名称服务器)在本地计算机上注册其端点(端口),并将其{程序名称到计算机}映射与其单元目录服务器一起注册。

当客户端想要与RPC服务器建立通信时,它首先要求其单元目录服务器找到服务器所在的机器。然后,客户端与该计算机上的RPC守护程序进行通信,以获取服务器进程的端口号。DCE还支持跨越单元格的更复杂的搜索。

DCE RPC为网络数据表示定义了一种称为NDR的编组消息,即网络数据表示。与表示各种数据类型的单一规范不同,NDR支持多规范格式。能够使用多种编码方式,客户端能够自由选择。一种理想情况是不需要它从本机类型进行转换。

如果这与服务器的本机数据表示不同,则服务器仍然必须进行转换,但是多规范格式避免了当客户端和服务器共享相同的本机格式时将客户端和服务器转换为外部格式的情况。例如,如果标准规定了大端网络数据格式,但客户端和服务器都支持小端,则客户端必须将每个数据从小端转换为大端,并且服务器在收到消息后必须转换每个数据。 回到小端。多规范网络数据表示将允许客户端发送包含小端格式数据的网络消息。

第二代RPC:对象支持


随着面向对象的语言在20世纪80年代后期开始流行,Sun(ONC)和DCE RPC系统明显都没有提供任何支持从远程类实例化远程对象,跟踪对象实例或提供对多态的支持。

现有的RPC机制仍在运行,但它们不支持面向对象那种自动、透明方式的编程技术。

Microsoft DCOM (COM+)

1992年4月,微软发布了Windows 3.1,其中包括一种称为OLE(对象链接和嵌入)的机制。

它使程序能够动态链接其他库,以提供诸如把电子表格嵌入到word文档中的功能(这不是微软的创新,它正奋起直追Apple已经提供的功能)。OLE演变成称为COM(组件对象模型)的东西,COM对象是二进制文件。

使用COM服务的程序能够访问COM的标准接口,但是不能访问其内部结构。COM对象使用全局唯一标识符(GUID)命名,对象类使用类ID标识。

存在几种创建COM对象的方法(例如,CoGetInstanceFromFile)。COM库在系统注册表中查找适当的二进制代码(DLL或可执行文件),创建对象,并返回指向调用者的接口指针。

DCOM(分布式COM)是在1996年与Windows NT 4.0一起引入的,它是组件对象模型的扩展,允许对象在机器之间进行通信。它随后更名为COM+。

由于DCOM旨在支持对远程COM对象的访问,因此需要创建对象的进程需要提供服务器的网络名称以及类ID。微软提供了几种机制做到这些。最透明的是将远程计算机的名称固定在注册表(或DCOM类存储)中,并与特定的类ID相关联。这样,应用程序不知道它正在访问远程对象,并且可以使用与本地COM对象相同的接口指针。或者,应用程序可以将机器名称指定为参数。

DCOM服务器能够在运行时为对象提供服务。称为服务控制管理器(SCM)的服务(DCOM库的一部分),负责连接到服务器端SCM并请求在服务器上创建对象。

在服务器上,代理进程负责加载组件并运行它们。这与诸如ONC和DCE之类的RPC模型的不同之处在于,特定接口的服务不是预先开始的。该代理进程能够并发处理客户请求。

为了支持识别类的特定实例(单个对象),DCOM提供了一个称为名字对象的对象命名功能。每个对象实例都能创建自己的名字对象并把它传回客户端。客户端随后可以引用它或者把名字对象传递到其他进程。

名字对象本身也是一个对象,它的IMoniker接口可用于定位,激活和访问绑定对象,而无需有关于对象所在位置的任何信息。

支持几种类型的名字对象:

  • 文件名字对象

    • 这种名字对象使用文件类型(如.doc)决定合适的对象(如Microsoft Word)。Microsoft提供对持久性的支持:将对象的数据存储在文件中。如果一个文件代表一种存储的对象,DCOM使用文件中的类ID来识别对象。
  • URL名字对象

    • 这通过COM接口中的因特网协议(例如,http,https,ftp)抽象对URL的访问。绑定URL名字对象允许检索远程数据。 在内部,URL名字对象使用WinInet API来检索数据。
  • 类名字对象

    • 这与其他标记一起使用以覆盖类ID查找机制。
DCOM之下:ORPC

Microsoft DCOM自身不提供远程过程调用功能。它是一组假定RPC可供系统使用的库。在DCOM之下是微软的RPC机制,名为对象RPC(ORPC)。

这是DCE RPC协议的略微扩展,增加了支持接口指针标识符(IPID),版本控制信息和可扩展性信息。接口指针标识符用于标识将要处理的调用的类的特定实例。它还提供了引用功能 - 可以传递远程对象引用(IPID)。

编组机制与DCE RPC中的网络数据表示(NDR)相同,其中一种新类型表示编组接口(IPID)。DCOM和ORPC需要明确方法、参数和数据结构才能进行编组。这可以通过接口定义语言(称为MIDL,全称为Microsoft Interface Definition Language)获取。正如所预料的那样,这与DCE的IDN相同,IDN具有定义对象的扩展。

MIDL文件使用IDL编译器编译,该编译器生成用于编组和解组的C++代码。客户端称为代理,服务器端称为存根。 两者都是COM对象,可以根据需要由COM库加载。

远程引用计数

面向对象编程和基于函数的编程之间服务器的主要区别在于,当对象被实例化并从其类中删除时,函数仍然存在(代码库保持固定,但每次创建对象时都会分配数据区域)。

对于服务器,这意味着它必须准备好创建新对象并知道何时在不再需要(对象没有引用)对象时释放内存(销毁对象)。

Microsoft DCOM明确而不是自动执行此操作。对象生存期由远程引用计数控制。当添加对对象的新引用时调用RemAddRef,并在删除引用时调用RemRelease。当引用计数达到零时,将在服务器上删除对象本身。

这种机制很好但不是万无一失的。如果客户端异常终止,客户端使用的对象的引用计数就不会减少。为了处理这种情况,服务器为每个对象设置一个超时时间,并依靠客户端发送的周期性消息维护对象引用。这种机制称为ping。服务器维护ping频率(pingPeriod)和超时周期(numPingsToTimeOut)。

客户端运行后台程序发送ping包:特定服务器上所有远程对象的ID。如果超时时间到了,还没有收到ping包,则清除所有的引用。

DCOM总结

Microsoft DCOM是早期RPC系统的重大改进。Object RPC层是对DCE RPC的增量改进,允许对象引用。DCOM层建立在COM访问对象(通过函数表)之上,并提供访问远程对象的透明性。

远程引用管理仍然存在一些它必须明确地解决的问题,但至少有一种机制来支持这一点。名字对象机制提供了一个COM接口来支持命名对象,无论它们是远程引用,文件系统中的存储对象还是URL。最大的缺点是DCOM是仅限Microsoft的解决方案。

它也不能很好地跨防火墙(大多数RPC系统的问题),因为防火墙必须允许流量在ORPC和DCOM使用的某些端口之间流动。

CORBA

虽然Sun RPC借助DCE解决了一些问题,但仍有某些缺点。例如,如果服务没有启动,客户端就无法连接到它调用远程进程。管理员负责保证所有的服务在客户端连接之前启动。如果系统中新增了服务或者接口,客户端也无从得知。在某些环境中,客户端能够在运行时发现服务和接口是有用的。最后,面向对象的编程语言期望函数调用时具有多态性(方法对不同类型的数据有不同的响应)。传统RPC不支持这种功能。

创建CORBA(公共对象请求代理体系结构)是为了解决这些问题和其他问题。这个架构由超过300家企业组成的工业联盟OMG(Object Management Group)创建。

该架构的规范自1989年提出演进至今。目标是支持分布式异构面向对象应用。对象可以跨计算机网络托管(单个对象不是分布式的)。规范独立于任何编程语言、操作系统、网络架构,具有跨平台的互通性。

在CORBA下,当客户端希望调用对象上的操作(方法)时,它会发出请求并获得响应。请求和响应都通过对象请求代理(ORB)。ORB表示整套接口库,存根函数和服务器,它们隐藏了从客户端通信,激活和存储服务器对象的机制。它允许对象在运行时相互发现并调用服务。

当客户端发出请求时,ORB执行:

  • 在客户端对参数编组
  • 找到该对象的服务器。必要时在服务器端创建一个处理请求的进程
  • 如果服务器是远程的,则发送请求(使用RPC或sockets)
  • 在服务器端反编组参数为服务器格式
  • 在服务器端编组返回值
  • 如果服务器是远程的则发送返回值
  • 在客户端反编组结果

接口定义语言(IDL)用于指定类的名称,属性及其方法。它不包含对象的实现。IDL编译器生成处理编组、反编组和ORB/网络接口的代码,并生成客户端和服务端存根。

IDL是一种中立的编程语言。许多语言都存在绑定,包括C,C ++,Java,Perl,Python,Ada,COBOL,Smalltalk,Objective C和LISP。简单IDL的例子如下:

Module StudentObject {
    Struct StudentInfo {
        String name;
        int id;
        float gpa;
    };
    exception Unknown {};
    interface Student {
        StudentInfo getinfo(in string name)
            raises(unknown);
        void putinfo(in StudentInfo data);
    };
};

IDL数据类型包括:

  • 基本类型:logn, short, string, float, …
  • 构造类型:struct, union, enum, sequence
  • 键入对象引用
  • 任何类型:动态类型值

编程最常通过对象引用和请求来完成:客户端使用对象引用在CORBA对象上发出请求,并在其中调用所需的方法。例如,使用上面的IDL,可能生成下面的代码:

Student st = ... // get object reference
try {
    StudentInfo sinfo = st.getinfo("Fred Grampp");
} catch (Throwable e) {
    ... // error
}

在这个场景下,IDL编译器生成存根函数。这些代码会调用存根函数,然后对参数编组并把它们发送给服务器。只有在类名称和方法在编译时是已知的,客户和服务器存根函数才能使用。

然而,CORBA支持动态绑定:它通过动态调用接口(DLL)在运行时汇编函数调用。接口提供调用用于设置类、创建参数列表,并执行调用。服务器的对端(用于动态创建服务接口)称为DSI(Dynamic Skeleton Interface)。

客户端在运行时能够通过接口库识别类名和方法,即名称服务器,可以通过查询它获取服务器支持的类和已经实例化的对象。

CORBA标准化函数接口和功能,但它们的实际应用和数据表示格式由具体的ORB提供者决定。这导致某一种CORBA应用可能不必与其他应用互通。通常需要修改应用才能把一种CORBA产品应用到另速算CORBA上。

1996年,CORBA2.0版本在规范中把增加互操作性作为目标。标准定义了一种称为IIOP(Internet Inter-ORB Protocol)的网络协议,它能够在任何基于CORBA应用的TCP/IP上应用。

实际上,既然有了标准的、文档化的协议,IIOP本身可以在没有提供CORBA API的系统中使用。例如,它可以用作Java RMI的传输通道(基于IIOP的RMI)。

提供文档完善的诸如IIOP的网络协议和全功能的CORBA是希望引入范围广泛的网络服务。一些机构能够提供知名的CORBA服务。网络中的客户端能够查询这些服务,动态查找它们的接口和调用函数。这些服务的遍布能够像HTML web服务一样广泛。这不会发生,基于HTTP的web服务胜出了。

–CORBA总结–

总体上讲,CORBA基于早期RPC系统创建,提供的额外功能如下:

  • 静态或动态方法调用(RPC仅提供静态调用)
  • 每种ORB支持运行时元数据描述系统中的每种服务接口
  • ORB能够使用单个进程、同一主机上的多进程或者分布式进程代理调用
  • 多态消息:ORB基于目标对象调用函数。同一个函数对不同的对象类型会产生不同的结果
  • 自动实例化未运行的对象
  • 与其他ORB应用通信
  • CORBA能够提供全面的服务(称为COS,全称COrba Services),用于管理对象
  • 生命周期服务:提供创建、复制、移动、删除组件的操作
  • 持续服务(外在):提供接口用于在存储服务器上存储组件
  • 名称检索服务:组件可以通过名称查找其他组件
  • 事件服务:组件能注册/注销指定事件
  • 并发控制服务:对象可以获取事务锁
  • 传输服务:提供双相委托协同(该协议后期会支持更多)
  • 查询服务:允许对象查询操作
  • 许可服务:测量组件使用
  • 属性服务:允许组件关联名称/属性

性能和灵活性的代价是昂贵的。当CORBA越来越可靠,并提供对管理分布式服务的深度支持时,部署和使用CORBA的学习曲线也很陡峭。把它和语言结合在一起也并不总易于理解。

除非能够真正充分利用CORBA的功能,使用简单且功能一般的系统去调用远程过程一般比较容易。CORBA并不是那么成功,除了在大规模用户而非互联网社区。CORBA由于较晚在TCP/IP协议上标准化和在网络上部署服务而遭到诟病。

Java RMI

CORBA旨在提供在异构环境(不同的语言、操作系统、网络)中管理对象的广泛服务。Java原生支持从远程站点下载代码,但它仅支持通过sockets进行分布式通信。

1995年,Java创建者Sun公司开发创建Java的一个扩展,称为Java RMI(Remote Method Invocation)。Java RMI使程序员能够通过远程对象的方法创建分布式应用,这些远程对象的方法能够从其他Java虚拟机(JVMs)上调用。

只要应用(客户端)有对远程对象的引用,就可以进行远程调用。这通过查找RMI提供的名称服务(RMI注册表)中的远程对象完成,并接收引用作为返回值。Java RMI在概念上与RPC类似,但它支持在不同地址空间进行对象调用的语义。

与CORBA和大多数RPC系统的一个不同之处是RMI只为Java构建。Sun RPC、DCE RPC、Microsoft’s COM+、ORPC、CORBA都是独立于语言、架构和操作系统(除了微软)。没有也这些特性,RMI的优势是它能够无缝接入Java,而且不需要对数据表达方式进行
标准化(Java在任何地方字节序均一致)。

这些设计理念使Java RMI具有以下特点:

  • 适配语言,能够与语言相结合,易于使用
  • 支持无缝远程对象调用
  • 支持应用对服务器的回调
  • 保持Java对象环境安全性
  • 支持分布垃圾回收
  • 支持多通道传输

分布式对象模型与本地Java对象模型在以下方面类型:

  1. 对对象的引用可以作为参数传递或结果返回
  2. 远程对象可以分派给任意使用Java语法的应用所支持的远程接口
  3. 内置Java操作实例,用于测试远程对象所支持的远程接口

对象模型在以下方面区别于本地Java对象模型:

  1. 远程对象的类与远程接口相互联系,不与这些接口的类的应用有任何关系
  2. 远程方法调用的非远程参数通过复制传递,不通过引用
  3. 远程对象通过引用传递,不拷贝实际远程应用
  4. 客户端必须处理额外的异常情况
接口和类

所有远程接口扩展自接口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。当远程方法调用失败时,客户端通过RMI系统抛出异常。然后可以为远程接口创建应用。

远程对象类

java.rmi.server.RemoteObject对程序员不可见,但它通过应用hashCode, equals, toString提供对象的远程语义。创建对象并使它们远程可用的函数由java.rmi.server.RemoteServer和它的子类提供。java.rmi.server.UnicastRemoteObject类定义统一远程对象,它的接口仅当服务器进程正常运行时有效。

Stubs

java RMI通过创建存根函数工作。存根函数使用rmic编译器生成。java1.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);
RMI架构

RMI是一个三层架构,如图所示。顶层是stub/skeleton层。它通过编组流发送数据到远程引用层(Remote reference layer)。编组流使用称为对象序列化的方法使对象可以在地址空间之间传递(以拷贝方式传递,除非它们是远程对象,则以引用方式传递)。

Java RMI logical view

任意将要作为远程方法参数的类必须应用序列化接口。这确保对象数据能够转换(序列化)为字节流(编组)以便在网络上传输。序列化是编组的核心:把数据转换为字节流使得它能够在网络上传输或者存储在文件或数据库中。

客户端存根执行如下步骤:

  1. 初始化远程对象调用
  2. 编组参数
  3. 告知远程引用层执行调用
  4. 反编组返回值或异常
  5. 告知远程引用层调用执行完成

服务器存根:

  1. 解析参数
  2. 调用实际的远程对象应用
  3. 编组调用或异常的返回值

在运行时决定使用stub/skeleton类并在需要时动态加载。

远程引用层处理低层传输接口。它负责执行独立于客户端存根和服务器存根的特定远程引用协议。

每个远程对象应用选择自己的远程引用子类。可能使用各种不同的协议,如:

  • 点对点单播
  • 调用复制对象组
  • 支持特定复制策略
  • 支持远程对象的持续引用(使能远程对象)
  • 重连策略

RMI传输层是协议栈特定传输部分。它执行以下功能:

  • 创建、管理连接
  • 监控连接状态
  • 监听调用请求
  • 维护驻留在地址空间中的远程对象表
  • 为到来的调用建立连接
  • 定位远程调用的目标,并把连接传递给分发器
RMI分布式垃圾收集

RMI创建一个分布式环境,运行在一个JVM的进程能够访问驻留在运行在另一个JVM(也可能是不同的系统)上的进程的对象。这意味着服务器进程需要知道对象何时不再被客户端引用并删除它(垃圾回收)。

在JVM内,Java使用引用计数和调度对象为引用计数减小到0的对象实现垃圾回收。跨JVM时,Java使用RMI支持两种操作:污染和清理。当对象仍在使用时,JVM周期性地发送dirty调用到JVM服务器。

dirty调用由服务器提供的定时器触发。当客户端不同有对远程对象的本地引用时,就会服务器发送清理函数调用。与DCOM不同,服务器不必计算每个客户对对象的使用,只需要简单地接收该对象不同使用的通知信息就可以了。

如果服务器在特定周期内未接收到dirty或者clean消息,则调度删除该对象。

第三代RPC和Web服务

自从Web浏览器问世之后,因特网的使用量猛增,浏览器成为获取信息的主要途径。大多数设计重点是提供使用者通过浏览器而不是编程来获取或者处理数据。

程序员对远程主机服务比较狂热。Web页面着重于对内容的展示。解析表达的方方面面困难重重。传统的RPC解决方案能在一定程度上工作于互联网上,但是问题是它通常依赖于动态端口分配(连接名字服务器查找服务提供特定接口的端口)。

这与防火墙配置的最佳应用不一致,它限制可用的端口并检查协议以确保正确,比如,确保HTTP通道中的数据确实是有效的HTTP格式。

Web服务作为协议集出现,可以发布、发现和以技术中性的格式使用。也就是说,服务不应该依赖客户端的语言、操作系统和硬件架构。

Web服务的一个通常的应用是使用Web服务作为服务请求的导流。客户端通过HTTP协议发送到服务器上的Web服务请求服务。Web服务器的配置使其能够识别URL路径名和文件名后缀,并把请求传送到指定的浏览器插件模块。

浏览器模块能够裁剪出URL的数据头部、解析数据,并调用其他函数或模块。通常的例子是浏览器支持Java servlets,它把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>

这是使用两个整型参数5和3请求运行sample.sumAndDifference的例子。XML-RPC支持的基本数据类型包括:整型、字符串、布尔、双精度和日期时间(iso8601)。另外,对二进制数据、数组和结构体进行base64编码,使得能够分别定义数组和结构。

XML-RPC不是基于任意一种特定的语言或是一套完整的用于处理远程调用的软件包而设计的。该协议中不包括存根生成、对象管理和服务发现。现在它们是许多不同语言的库,包括Java、python和perl使用的Apache XML-RPC。python和perl提供类似于过程调用的接口。

XML-RPC规范简单(大约7页)明了:它只专注于消息处理,不处理垃圾回收、远程对象、名字服务和远程过程的其他方面。然而,即使没有广泛的工业支持,协议的简洁性也使用它被广泛采用。

SOAP

SOAP(Simple Object Access Protocol)产生的基础是XML-RPC规范。该缩略语自从协议不再简单、也不再局限于获取对象时便不再使用了。该协议由Microsoft和IBM强力支持的工业组织创建于1998年。最新版本发布于2007年。

SOAP为无固定格式消息交换、包括RPC格式的过程调用和更一般化的可能包括多重回复的消息详细阐述了XML的格式。

它在XML-RPC停止的地方继续下来,提供用户定义的数据类型,能够指定接收者,指定不同类型的消息和许多其他功能。SOAP大多应用于基于HTTP的XML消息。

类似于XML-RPC,SOAP是消息格式。它不定义垃圾回收、对象引用、存根函数,甚至连传输协议也没有。

SOAP成为构建Web服务的主流框架之一。然而,它只提供了标准化的消息结构,作为补足,需要一种定义服务以便构建合适的SOAP消息的方式。

WSDL(Web Services Description Language),是描述Web服务的一种方法。它是XML文档,能够输入到一个程序中以产生能够发送和接收SOAP消息的软件程序。本质上,WSDL作为接口定义语言产生存根(然而,由于依赖于语言,它对程序员来说可能不如函数调用那样透明)。

WSDL文档包括四个部分:

  1. 类型:定义Web服务使用的数据类型。
  2. 消息:描述了消息使用的数据元素和参数。
  3. 端口类型:描述服务提供的操作。包括每个操作所使用的动作、输入和输出消息。
  4. 绑定:为每个端口定义消息格式和细节。例如,可能定义RPC类型的消息。注意,术语“端口”在这里是指一种抽象的消息终端,而非TCP/UDP端口号。

尽管它们使用XML,SOAP和WSDL都无法直接看懂。程序员一般在某些原始语言中提供服务创建接口定义,比如Java,并使用类似于Java2WSDL工具或者wsdl.exe创建WSDL文档。程序员想要使用服务时会使用WSDL文档并把它运行在WSDL2JAVA(一个apache Eclipse插件)或者wsdllib(python)来产生模板代码。

Microsoft .net 远程

Microsoft的COM+(DCOM)机制最初是为程序中动态嵌入式对象设计的,也是某种程度上低层级的应用。程序通常必须显式提供引用计数并支持跨语言和库是不平衡的。例如,VB提供了对框架的大力支持,但Visual c++却需要大量的编程。Microsoft进行了革新,并在.NET中增强了这些功能。

Microsoft的.NET包括一个开发框架和运行时环境来创建独立于平台的应用。.NET API提供一套为网络感知和面向对象编程的类。

通用中间语言CIL(Common Intermediate Language)是基于栈、低层级、面向对象汇编语言,它独立于机器并能够在不同语言和支持.NET的操作系统之间交互操作。

通用语言运行时(CLR, Common Language Runtime)是ECMA标准。它描述了指令集,基类和.NET Framework执行环境。它提供了一套通用特性,包括对象生命周期管理、垃圾回收、安全性和版本控制。

支持.NET的编译器(比如Visual Studio C++,C#和VB,将近40种语言在某种程序上支持.NET)能够生成CIL代码,然后编译成部署DLL和EXE文件,与可移植执行(PE,Portable Executable)格式兼容。

当CLR运行这些文件时,它先把CIL翻译成本地机器码。

.NET远程是.NET处理远程对象的组件,使用它可以调用远程对象。要能够调用远程对象的方法,远程对象必须派生自MarshalByRefObject。这与Java的远程类相似,确保远程对象的引用能够被创建。

当对象激活或者CLR阻止对对象的调用时,代理(存根)就创建了。CLR知道哪个类是远程的以能够处理进程请求新远程对象而不是本地对象的情形。任何对象作为参数传递时,就必须使用iSerializable接口,这与java的序列化类相似。它把对象的数据转换为一种扁平的网络友好型格式:能发送到网络消息的数据字节。

创建对象有两种类型:服务触发对象和客户触发对象。服务触发对象不依赖于客户端控制对象的生命周期。与客户触发对象不同,客户端不创建新的对象,使用然后删除它。有两种模式的服务触发对象。

单一调用对象是完全无固定形态的。每次调用都会创建一个对象的新实例。该操作会饿客户端产生一个对象的方法。单例模型对象是持久化的。对象的同一实例为所有客户端的请求所引用。只有在对象不存在的时候,服务端才会创建对象。

从上述观点来看,所有客户端的请求共享同样的对象。当客户请求一个新的对象时,客户端触发对象在服务端被创建。这与COM+模型相似,支持同一对象的多重引用和不同客户端从同一类创建多个对象。它也支持分布式垃圾回收。

租期分布式垃圾收集

.NET远程使用LDGC(Leasing Distributed Garbage Collector)为客户端触发对象进行垃圾收集。服务触发对象不需要该机制是因为服务端的对象要么在方法调用的整个生命周期有效,要么永久有效。

租期管理器管理服务器上的对象租期。服务器对象在没有到期时会一直被使用。到期后客户端想要被连接时,它必须提供赞助对象。客户的赞助对象在需要时可以选择延长租期。

垃圾收集为对象使用下面的参数:

  • InitialLeaseTime: 初始化远程对象的生命周期(默认5分钟)
  • LeaseManagePollTime:租期管理器到期间隔(默认10s)
  • sponsorshipTimeOut: 框架等待赞助者变为可用的时间(默认2分钟)

每次调用方法时,租期时间都会设置为最大值(租期时间-到期时间,RenewOnCallTime)。RenewOnCallTime用于刷新每次方法调用后的租期。请求方在LeaseTime到期后必须刷新它的租期。基于租期的方法意味着服务器不必像COM+那样维持引用计数。

.NET远程组件是为同构环境设计的:在.NET进程间通信。当.NET支持Web services, .NET远程不是针对它设计的。一个叫做WSE(Web Services Enhancement)的扩展用于处理SOAP类型的Web服务。

WSE支持以下协议的语法和格式:

  • HTTP: HyperText Transfer Protocol,在TCP/IP上运行的通信协议
  • XML: eXtended Markup Language,数据表示格式
  • SOAP: Simple Object Access Protocol,用于发送接收消息和调用函数的协议
  • WSDL: Web Services Definition Language,定义可用服务的接口的XML文档
  • UDDI: Universal Description,Discovery,&Intergration,用于发现服务的协议

与.net相比,SOAP只是更低级的消息协议。要想创建一个.net web服务,需要写一个能够被本地客户获取的.net对象,并把它的属性标记为可以被Web客户使用。

ASP.NET,是微软Web服务IIS的扩展,提供能够获取HTTP请求和把它们映射为对象调用的框架。WSDL文档描述了在.net对象中自动生成的可用服务。

.net组件的进化是WCF(Windows Communication Framework),它另一个提供统一通信框架也提供不同平台的互操作性。它仍然使用.net CLR环境也支持库。

使用WCF,服务应用于服务类(Service Class),应用一个或多个方法,并包含一个服务连接(Service Contract),它定义了客户能够使用的方法,和一个数据连接(Data Contract),定义了数据结构。

服务类编译进基于CLR的语言。服务类通常编译进库并运行在一个主进程中。主进程是下述中的一个:

  • IIS(Web服务器)主机进程(通信协议必须使用基于HTTP的SOAP)
  • Windows触发服务
  • 一个任意进程

终端为服务的定义指定以下:

  • 地址:服务的URL
  • 绑定:协议描述和安全机制
  • 连接:指示终端使用的服务连接的名字

WCF提供的绑定集(通信选项)如下:

绑定 描述
BasicHttpBinding HTTP协议之上的SOAP(可选HTTPS)
WsHttpBinding 同上,但支持可靠消息传输、安全和事务
NetTcpBinding 基于TCP的二进制编码SOAP,支持可靠消息传输、安全和事务
WebHttpBinding 无SOAP的HTTP或HTTPS,RESTful通信的理想之行:xml、Json、二进制形式的数据
NetNamedPipesBinding 基于命名管道的二进制编码SOAP(非多平台,仅支持人WCF-WCF通信)
NetMsmqBinding 基于MSMQ的二进制编码SOAP(非多平台,仅WCF-WCF)

XML Web服务的Java接口

Java RMI设计用于远程对象交互,但是要预先使用Java模型创建。而且,它并非使用Web服务和基于HTTP消息创建。

出现许多支持基于Java的WEB服务软件。在本简介我们将介绍它们中的一个。JAX-WS(Java API for XML Web Services)是用于Web服务的消息和远程过程调用的Java接口。

它允许使用Java RMI调用基于Java的Web服务。JAX-WS的一个目标是平台间的互操作性。该API使用SOAP和WSDL。通信双方不需要安装Java环境,并且客户端可通过WSDL文档获得服务端的定义。

创建RPC终端

在服务端,以下步骤可以创建一个RPC终端(一个面向客户的RPC服务):

  1. 定义接口(Java 接口)
  2. 应用服务
  3. 创建一个公开版,也就是创建一个服务的实例并以一个名字公开它

在客户端:

  1. 创建一个代理(客户端存根),wsimport命令利用WSDL文档创建客户存根
  2. 写一个客户端通过代理(存根)创建远程服务的实例并在其上使用方法

使用JAX-RPC的执行流程与其他RPC系统相同,区别只是框架通常由服务的web服务主导。如图7所示。

JAX-WS call flow

  1. Java客户端调用存根(代理)方法
  2. 存根调用适当的web服务
  3. web服务获取调用信息并把它定向到JAX-WS框架
  4. 该框架调用应用
  5. 应用返回结果给框架
  6. 框架返回结果给web服务
  7. 服务把结果发送给客户存根
  8. 客户存根返回调用者信息

SOAP之上

当SOAP正且继续广泛部署之时,许多环境由于更经量级、更易于理解或者适合web交互模型更清晰而弃用了它。比如,google的API在2006年不同支持SOAP的接口,而提供了AJAX/XML-RPC/REST作为选用。

一位匿名的微软员工指责SOAP过于复杂,因为“我们想要让工具阅读它,而不是人”。这些话是否准备并不重要,重要的是SOAP确定复杂。

AJAX

web浏览器原始设计的一个限制是web页面提供的非动态交互模型。web浏览器基于同步请求/响应交互模型创建。请求发送到服务器,服务返回完整页面。这样没有好的方式更新部分页面。

可见唯一的方法是使用框架(一旦可用)并在每个框架加载不同页面。即使这样笨重且受到限制。

DOM(Document Object Model)和JavaScript的出现改变了这一局面,它允许编程更改web页面的各个部分。另一个组成是使用非阻塞方式与服务器交互,允许用户在JavaScript仍等待服务返回结果时依然能够与页面交互。这个组件叫做AJAX。

AJAX表示Asynchronous JavaScript And XML。它有三个元素,分别如下:

  • 它是异步的,因为客户在等待服务返回结果时非阻塞
  • AJAX融入JavaScript并设计由浏览器触发,作为解释web页面的一个部分。AJAX请求由JavaScript使用HTTPRequest。JavaScript也可能改变决定页面效果的DOM
  • 数据使用XML文档格式收发

在web2.0中引入了AJAX:调度交互服务比如google地图等。基本上,它允许JavaScript发起HTTP请求,获取并处理结果,在不刷新整个页面的情况下改变页面内容。在多数浏览器中,结果的格式如下:

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

这个JavaScript代码告诉xmlhttpRequest对象发起请求的类型,请求的url,发起请求时的回调函数和请求体中的消息。

注意AJAX早从RPC中移除了,它不提供服务函数的功能接口,它专为web浏览器设计的。

REST

SOAP创建了在HTTP协议上传输的自定义消息协议,而REST(REpresentational State Transfer)的方法是使用与web协议一致的原则并把HTTP作为协议的核心部分。

原始的HTTP协议已经使用4个命令定义,它们清晰地映射到对数据的不同操作:

  • PUT(insert)
  • GET(select)
  • POST(update)
  • DELETE(delete)

REST的用意是使用这个HTTP命令请求数据并对数据操作。作为HTTP协议的一部分,REST使用URL引用对象和操作,它们使用了分级命名格式和属性-值列表的参数(如http://mydomain.com/mydata/getlist?item=123&format=brief)。

作为使用bloc交互的例子,考虑这些操作:

REST模型对于资源导向型服务意义重大,比如bloglines,Amazon,flikr,del.icio.us等。

另一个例子,使用HTTP获取组件列表:

HTTP GET //www.parts-depot.com/parts

这个命令会返回包含组件列表的XML文档。注意返回的不是web页面,仅是一个包括请求数据的XML数据结构。

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

要想获取特定组件的详细信息,需要发送HTTP GET命令请求特定组件:
HTTP GET //www.parts-depot.com/parts/00345

这里会返回特定组件的信息:

<?xml version="1.0"?>
<p:Part xmlns:p="http://www.parts-depot.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.parts-depot.com/parts/00345/specification"/>
      <UnitCost currency="USD">0.10</UnitCost>
      <Quantity>10</Quantity>
</p:Part>

注意,这是一个例子,组件请求可以仅简单地使用组件号作为url的参数,如HTTP GET //www.parts-depot.com/parts?partid=00345

REST不是RPC,但是具有相似的请求/响应模型。透明地格式化请求、重组数据、解析响应非REST的组成部分。REST受到广泛支持并在诸如Yahoo Search APIs,Ruby on Rails,Twiter和Open Zing Services的服务中使用。

最后是XM-Sirius Radio的编程接口,获取频道列表的url是:svc://Radio/ChannelList

可以使用如下utl获取特定频道的信息:svc://Radio/ChannelInfo?sid=001-siriushits1&ts=2012091103205

Google 协议缓冲: 仅编组

当既不需要RPC又不需要web服务而仅需要简化对网络数据进行编组和反编组时,google协议缓冲提供了对结构化数据进行序列化的有效机制,使得对网络数据进行编码和解码接收到的数据变得简单。

协议缓冲是紧凑的二进制格式,比xml更简单、小巧、快速。它独立于语言,并且仅以定义的数据类型提供服务。每条消息都是结构化的数据名字、类型和值集合。

消息结构定义为高层级格式,与许多接口定义语言类似。随后文件编译成生成所选语言的转换规则。

协议缓冲在google内应用广泛。目前定义了超过48000个不同的消息类型。它们用来构造类RPC消息和持久存储中需要把数据转换为标准序列化格式写入文件中。

下面是从google的协议缓冲开发指南中摘录的协议缓冲定义的一个例子:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

注意,这只定义了数据结构,而非函数。使用该结构的一个例子是:

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

与紧凑xml版本相比,协议缓冲在解析时间和空间上都更高效。而且,从开发手册中对比xml版本:

<person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
</person>

和未编译的协议缓冲对比:

person {
   name: "John Doe"
   email: "jdoe@example.com"
}

从协议缓冲中产生二进制消息大概28字节,解析需要耗时100-299ns。相比之下,xml版本超过69字节(2.5倍),解析耗时5000-10000ns(50倍)。

JSON

另一种编组流行的编组格式是json。它不是google协议缓冲类型的二进制格式,适合应用在通过HTTP传输的消息负载。

json基于JavaScript,可读写,易解析。它是xml的“免费”替代版本。

当前有超过50种语言的转换器,也使用JSON-RPC增加了远程过程调用。

但是,json只是一种消息格式,它并不提供RPC库,也不支持服务发现、绑定、托管和垃圾收集。


References (partial)

The Component Object Model Specification, Draft version 0.9, October 24, 1995, © 1992–1995 Microsoft Corp, http://www.microsoft.com/oledev/olecom/title.htm

CORBA Architecture, version 2.1, Object Management Group, August 1997, pp 1–1 – 2–18. [introductory CORBA concepts straight from the horse’s mouth]

The OSF Distributed Computing Environment: Building on International Standards – A White Paper, Open Software Foundation, April 1992. [introduction to DCE and DCE’s RFS]

Networking Applications on UNIX System V Release 4, Michael Padovano, © 1993 Prentice Hall. [guide to sockets, Sun RPC, and Unix network program-ming]

UNIX Network Programming, W. Richard Stevens, © 1990 Prentice Hall. [guide to sockets, Sun RPC, and Unix network programming]

JAVA Remote Method Invocation (RMI), © 1995–1997 Sun Microsystems, http://java.sun.com/products/jdk/rmi/index.html

Distributed Operating Systems, Andrew Tanenbaum, © 1995 Prentice Hall, pp. 68–98, 520–524,535–540. [introductory information on sockets and RPC]

Modern Operating Systems, Andrew Tanenbaum, © 1992 Prentice Hall, pp. 145- 180, 307–313, 340–346. [introductory information on sockets and RPC]

This is an update of a document originally created on October 30, 2012.


This is the default behavior. A command-line flag to rpcgen disables the server automatic running as a daemon. ↩

Polymorphism means that you can create multiple functions with the same name but that take different parameters. A function foo(int) is different code from a funciton foo(char). ↩

a DLL is a dynamically linked library. That is, it is a library that is linked into the process at run time rather than statically into the program during compilation and linking. ↩

Some systems use the term stub to refer to the client stub and skeleton to refer to the server stub. CORBA is one of them. Microsoft uses the term proxy for the client stub and stub for the server stub. ↩

the toString method returns the reference of the object as a string. ↩

In Internet Explorer, the request is: new ActiveXObject(“msxml3.XMLHTTP”). ↩

展开阅读全文

没有更多推荐了,返回首页