前言:
分布计算在当今不断网络化的世界中扮演着越来越重要的角色。本文力图深入分布计算系统的内部,挖掘分布计算的实质原理,并尽力详细地剖析主流分布计算系统的核心架构。相信对分布计算系统从原理上认识则能极大地帮助解决实际开发遇到的问题。本文先简要介绍了最原始的RPC(remote procedure call)系统,因为其他分布计算系统可以说都是由此发展而来的。然后详细介绍了一个成熟的面向对象RPC系统—DCOM系统,力图简明但又深入地理清DCOM系统各个组成部分的机理,并阐明了表面上简单的应用程序背后所隐藏的DCOM系统复杂的执行过程。面向web的服务是分布计算发展的趋势,本文的最后部分简要介绍了web services系统和微软的.NET Framework.
进程间规范的信息交换机制和应用进程对资源的访问透明性是构建分布计算系统极其重要的因素,birrell和nelson在1984年引进了一种不同以往的通信机制--RPC,即远程过程调用,这种方法允许一台机器上的进程可以调用位于远程机器上的过程,并实现了调用时的透明性(即用户进程无需知道这些过程具体位于远程的哪台机器上)。而随着面向对象技术的发展ROI(remote object invocation)系统就成了分布计算系统的主流,如Microsoft的DCOM,OMG的CORBA以及java的RMI(remote method invocation),而这些也可以说是从传统RPC上发展而来的。这几个系统在表面上确实多有不同,但当探究其内部原理时就会发现这几个系统相似的原理是很多的。相信深入了解其中之一不仅有助于深入理解分布计算实质原理,有助于掌握本种标准进行分布式应用系统的开发,也有助于更容易地理解和掌握其他系统。因为Microsoft的DCOM是建立在相对基本的面向对象RPC系统之上的,而其他两个改进的机制相对较多,因此本文就主要阐述一下DCOM系统。而大家看过前面部分后,再看本文最后对web service和.NET Framework的简单介绍,相信会更容易地把握web service和.NET Framework的实质。
本人查阅了相当多的英文资料,并经过对这些英文资料的阅读,比较,联系以及反复思索尽力试图简明,准确地将这些系统的核心架构及其内部机制阐明,但相信难免还有些不太确切之处。同时希望那些做分布计算系统研究和开发的人能在本文中找到他们所遇到的一些困惑已久的问题的答案,也希望本文对分布计算原理的研究者,分布对象计算技术的学习者,以及分布式应用系统的开发者会有一定的参考价值。
1.RPC系统简介:
自RPC机制提出以来就被广泛地采纳为中间件和分布式系统的基础部分,也就有很多种成熟的RPC模型,其中非常具有代表性的是DCE(distributed computing environment) RPC,它是由The Open Group组织(前身为OSF(open software foundation))开发的,微软的分布计算对象DCOM所用的RPC系统就是基于DCE RPC开发出的。DCE RPC的specification总共有700多页,内容详尽地阐述了整个RPC系统的各个部分,以及实现细节。因为本文主要涉及RPC的核心原理,许多部分(并非不重要)就暂时省去。下面就简要介绍一下DCE RPC.
1.1 DCE RPC核心模型:
图表 1
先介绍一下上图的各个部分:
(1) uuidgen是一个程序,它的功能是产生一个包含了一个标示符的IDL原型文件(有待用户编辑),此标示符是一个128位的二进制数,用来标示这个IDL文件,并要能保证自身在整个域中的全局唯一性。
(2)Interface definition file 是一个接口文件,它应该由用户利用IDL(interface definition language)语言编辑uuidgen产生的IDL原型文件而成。IDL是一种用来说明操作(过程或函数),操作的参数以及数据类型的语言,它在语法上继承于C语言,但形式上和C语言有很多不同,当然也有一些符合自己特点的适合于分布计算的特殊语法。具体可参看DCE RPC specification中的IDL language specification部分。
(3) IDL compiler的功能就是对编辑好的IDL文件进行编译,编译后生成了下面的三个文件:
1.Header是一个头文件,此头文件包含了此IDL文件中的全局唯一标示符,数据类型定义,有关的常量定义,以及函数原型。它要同时在客户代码和服务器代码中包含(比如在C语言中用#include语句)。
2.Client stub即客户端的stub程序。
3.Server stub即服务器端的stub程序。
先介绍一下stub:RPC的一个重要思想就是使远程调用看起来象当地的调用一样,也就是说调用进程无需知道被调进程具体在哪台机器上执行。Stub就是用来保证此特性的很重要的部分。具体的讲,比如在客户端,一个进程在执行过程中调用到了某个函数fn(),此函数的具体实现是在远程的某台机器上,那么此进程实际上是调用了位于当地机器上的另外一个版本的fn()(起名为c_fn()),此c_fn()就是客户端的一个stub. 对应的,当客户端的消息发送到服务器端时,服务器端也不是把消息直接就交给真正的fn(),而是同样先交给一个不同版本的fn()(起名为s_fn()),此s_fn()就是服务器端的一个stub.
Stub的主要功能是对要发送的参数进行marshal(可理解成一种打包操作)和对接受到的参数(或返回值)进行unmarshal(解包)。Marshal操作将要发送的数据制成一种标准的格式(在DCE RPC系统中,此格式称做Network Data Representation(NDR)格式),unmarshal再从NDR格式数据包中读出所需数据。事实上,在实际系统中这些操作涉及的机制是相当复杂的。就拿marshal操作来说,实际上要传送的参数类型可能是多种的,如传值,传指向数组的指针,传指向结构的指针,而结构里又可能包含强制(arbitrary)指针,包含循环(cyclic)指针等。对这些复杂情况的具体操作这里就不再介绍,可参看DCE RPC specification.
接下来就看看客户端和服务端的stub具体做些什么:
Client stub的功能:负责收集调用远程函数需要的参数,并将这些参数marshal成消息,然后调用运行时系统(runtime system)将此消息发送给服务器端。还负责当服务器端将结果消息返回后,将结果消息unmarshal,把结果返回给应用进程。
Server stub的功能:负责对发送给它的参数消息unmarshal收集参数,然后调用位于本机上的真正客户应用进程需要调用的过程。还负责将此过程执行的结果marshal成消息,然后调用服务器端的运行时系统将结果消息发送给客户端。
(4) Client code和Server code就是由应用程序开发人员所写的客户代码和服务器代码,客户代码可能调用到各种各样的过程,服务器代码就是这些过程的真正实现。图中针对的是C语言写的代码,因此用到的编译器都是C编译器。客户代码和客户stub代码分别经过编译生成各自的目标文件,这些目标文件再与运行时库连接就生成了整个客户端可执行文件。服务器端也同理。
1.2 RPC执行过程:
下面就根据图2简要介绍一下RPC系统的执行过程:
首先,客户进程调用了一个远程过程add(i,j),则实际上调用了本地相应的stub函数,stub函数就将两个参数和函数名一起marshal成一个消息(如图),然后通过底层的操作系统将消息发到了服务器端。服务器端的操作系统把消息交给了服务器端相应的stub函数,此函数对消息unmarshal,提出参数信息后就利用这些参数进行了一个当地的add调用,而add的真正实现代码就被执行,得到需要的结果。同理可知结果的返回过程。
上述可以说是一个最简单的RPC执行过程,而在实际的RPC系统中,系统要有通用的平台性(要能为应用程序开发者屏蔽异构的平台,能处理各种不同情况下的调用),要能处理远程调用出现的异常,以及跨域调用的安全性等问题,都导致真正的系统是非常复杂的。这里对传统RPC系统只做了最简单的描述,在下面的面向对象系统(DCOM)中再做较为复杂的阐述。