2.面向对象RPC系统:
面向对象技术已在计算机技术的各个方面得到广泛的应用,当然也包括分布计算方面。分布对象计算在当今的计算世界中处于越来越重要的地位,也应用越来越广泛。面向对象技术和传统RPC系统的结合产生了多种面向对象RPC系统,下面就剖析一个成熟的面向对象RPC系统——微软的DCOM系统。
2.1 分布对象:
既然是面向对象技术,理解对象的概念就非常重要。一个对象有一些封装好的数据,叫做它的状态(state),有一些对这些数据的操作,叫做它的方法(method),这些方法只能通过接口(interface)来操作对象的状态。一个对象可以实现多个接口,同样,给出一个接口定义,也可以有多个对象来实现它。
分布对象的概念就是把对象的接口和对象的实现分离,可以把对象放在一台机器上,而把对象的接口放在另外一台机器上。客户端应用程序只需看到这些接口,无需理会这些接口的具体实现,也无须知道它要访问的对象具体在网络上的位置以及运行在哪一种操作系统上。分布对象还可以有动态性,它们可以在网络上到处移动。
分布对象和传统意义上的对象还是有一定区别的。传统的面向对象技术强调两个特性:封装性和继承性。而分布对象又经常被称为组件,它可以是一个简单的对象,也可以是一组相关对象的组合,可以共同提供某种服务(注意这里服务的概念)。分布对象技术主要使用面向对象技术的封装性,对外界来说,主要关心的是组件的界面(接口),至于内部是如何实现的则无需考虑。另外,分布对象计算系统都不支持分布继承性(跨不同机器的继承),也就是说,如果两个对象在不同的机器上,那么其中一个对象就不能继承另一个对象的数据和代码(实现这种继承性的复杂性和代价都很大,因此一般不实现)。
2.2 DCOM系统:
自从面向对象技术流行之后,DCE RPC系统也曾用面向对象方法做过修改,但这里想阐述一种更成熟而且更流行的模型——微软的DCOM系统。一般说法是,DCOM是建立在DCE RPC系统上的,是面向对象技术和传统RPC的完美结合。DCOM使用的正是一种ORPC(object RPC)系统,下面就阐述一下DCOM的架构。
2.2.1 DCOM的有关概念:
在介绍DCOM的核心架构之前,有必要先解释清楚DCOM中的一些重要概念。
(1)Interface相关:
IID—对象的每一个接口都表示着对此对象的一个不同的视图。接口被一个128位的全局唯一标示符来标示,此标示符就叫interface ID(IID).
DCOM中用到的接口(Interface)概念类似于RPC系统中的,也是一组抽象的操作(方法),代表一定的功能。这里的接口也是由接口描述语言(IDL)编辑成的文件,在DCOM系统中是用微软的IDL编译器(MIDL)来生成客户端和服务端的interface stub代码(同RPC系统的stub,用来执行marshaling和unmarshaling),不过在DCOM系统中,客户端的stub称为interface proxy,服务端的叫做interface stub.
由MIDL产生的interface stub和interface proxy也作为类库被存储在客户端和服务端,当需要时从com库中导出并生成相应的com对象。每个interface要在系统注册表中注册(利用它的IID),相应的stub和proxy也要注册,例如:
[HKEY_CLASSES_ROOT\Interfaces\{<IID_SampleInterface>}\ProxyStubClsid32]
@={<CLSID_ProxyStub>}
[HKEY_CLASSES_ROOT \CLSID\{<CLSID_ProxyStub>}\InprocServer32]
@="c:\proxy-stub.dll"
这里用到的CLSID的概念将在下部分阐述。
(2)对象和类相关:
Object class(class)—是一个或几个Interface的具体实现。
Object instance(object)— 类实例化后产生的对象。
CLSID—class ID 每一个类的标示符。
每个类也要在系统注册表中注册,例如:
[HKEY_CLASSES_ROOT\CLSID\{<clsid-guid>}]"AppId"="<appid-guid>"
还有上部分例子中注册的ProxyStub类。
Class factory— 类厂是DCOM系统中一个很重要的概念,在C++语法中,可以用new运算符来生成所需的对象,但这仅限于生成所需对象是在同一个进程空间中。在DCOM系统中,要能允许在不同的进程空间,甚至是不同的机器上生成所需对象,比如所需生成的对象将在远程的某台机器上,那么在当地机器上使用一般意义上的new操作就没有多大意义了。为了给应用进程提供对象生成的位置透明性,也即应用进程无需知道所需创建和使用的对象具体在哪台机器上,DCOM系统引入了类厂的概念。
在DCOM系统系统中,大部分实现类都用一个和自身关联的类厂类,此类厂类实现了IClassFactory接口,其中IClassFactory::CreateInstance方法则用来具体生成所需对象(当然,对象可能在相同进程空间,也可能在远程机器上,IClassFactory::CreateInstance会根据不同的情况执行不同的过程),对客户应用程序来说,它只需调用CoCreateInstance函数(此函数内部将调用相应的IClassFactory::CreateInstance),而无需理会所创建对象的具体位置。
(3)Object Exporter:
如果一个服务实现了一个或几个对象,而这些对象又可以被远程的客户端访问,那么这个服务就可以被称做一个对象输出者(Object Exporter)。每个Object Exporter被一个8个字节的标示符来标示,这个标示符叫做对象输出者标示符(object exporter identifier ),即OXID.
(4)binding information:
一个进程想要触发一个远程的对象,它需要知道远程对象的一些信息,这些信息包括对象所在的远程主机的网络地址,所用的协议,对象用的端口。这些信息就是绑定信息(binding information),这里是用一个字符串来包含上述所有信息,也称为字符串绑定(string binding)。
(5) OXID resolver:
每台主机上都有一个OXID解析器(OXID resolver),每个OXID resolver都维持(缓存)着一张表,这张表上的每一项记载着一个OXID和它实现的对象相应的绑定信息。如下图:
当服务器端启动一个服务时,其OXID和相应的绑定信息就被存储在当地的OXID resolver中,以便在需要时把绑定信息传给客户端。
当客户端的某进程需要某远程对象A的绑定信息时,它首先查找当地的OXID resolver中缓存的表,如果没有,当地的OXID resolver就会调用IOXIDResolver::ResolveOxid方法向对象A所在的服务端上的OXID resolver询问(因为服务端上的OXID resolver的绑定信息会在给客户端传送接口指针时传给客户端,这点在介绍接口指针时还会有相关的介绍),然后服务端上的OXID resolver再把所需的对象绑定信息发送给客户端的OXID resolver.
这里存在的一个问题是:为什么服务端在向客户端传送接口指针时不直接把客户端所需的对象的绑定信息传送过去,而是先向客户端传送它的OXID resolver的绑定信息?这是因为DCOM系统要支持不同的协议,如TCP/IP, UDP/IP, IPX/SPX.同一个服务要支持不同种的协议,需要在启动后初始化时注册所有的协议信息并且载入支持各种协议的代码,这样的就会有很大的资源浪费。优化的做法就是,当某客户端调用IOXIDResolver::ResolveOxid向服务端的OXID resolver发送请求时,相应的服务对象再根据具体情况进行所需的协议注册和所需的协议代码载入。这样,同一服务也就可以根据具体不同的客户端向客户端传送它们需要的对象的绑定信息(可能是同一对象对于不同的客户端因协议不同而导致的不同版本的绑定信息)。
(6) Service Control Manager(SCM):
SCM是DCOM系统中的重要组成部分,其主要功能有两个:定位实现(Locate implementation)和激活实现(Activate implementation)。可以简要具体的将其功能做如下描述:
客户端的scm内部也维持着一张表,缓存着可能用到的clsid信息,用户进程调用到某个类时,scm就先查看内部表寻找所需的clsid信息,如果没有,它就访问注册表找到相应信息。如果找到,它就启动远程服务(向远程主机上的scm发送请求)。服务端的scm则先将所用到的类厂(class factory)装入内部维持的表中(如果已经存在,则不需此操作),然后在内部表中找到所需的类厂信息,并得到此类厂实例的指针,然后激发类厂中的createinstance()方法,产生所需的对象和接口。
(7) Marshaled Interface Pointer:
前面传统RPC系统中提到要想进行远程调用,就要对所需的参数marshal成一种标准的格式(在DCE RPC系统中,此格式称做Network Data Representation(NDR)格式)后传送给远程主机。而在DCOM的ORPC系统中则扩展了这一格式,增加了接口指针(Interface Pointer)marshal而成的NDR格式,也即支持marshal接口指针。
一提到接口指针,也许有人会立即想到一个指向一个结构的指针,这个结构里面则含有此接口所对应对象的各个函数的指针。但要把此接口指针marshal成数据包并传送给客户进程,从而使客户进程方便地调用远程对象的方法时,这样的理解就没有多大意义了(客户进程和服务对象进程位于不同的机器上,也就根本不在同样的运行空间)。那么,这个marshal后要传给客户端的接口指针究竟是什么呢?
事实上,它代表着一个对象引用(an object reference),具体主要的内容是一个OBJREF结构(OBJREF结构有standard, handler, 和 custom三种形式,这里只介绍standard形式):
typedef struct tagOBJREF
{
unsigned long signature; // Must be OBJREF_SIGNATURE
unsigned long flags; // OBJREF flags
GUID iid; // IID
STDOBJREF std; // Standard OBJREF
DUALSTRINGARRAY saResAddr; // Resolver address
};
其中,iid即此接口ID(前面有介绍),saResAddr即服务端OXID resolver(前面有介绍)的绑定信息,std是如下的一个结构:
typedef struct tagSTDOBJREF
{
unsigned long flags; // SORF_ flags
unsigned long cPublicRefs; // Count of references passed
OXID oxid; // OXID of server with this OID
OID oid; // OID of object with this IPID
IPID ipid; // IPID of interface
} STDOBJREF;
其主要信息是此服务的OXID(oxid),此对象的ID(oid),以及此接口指针的ID(ipid).
这里通过剖析两个结构,先把需要远程传送的接口指针主要内容阐述清楚,对于它的具体使用将在阐述DCOM系统架构部分再进行解释。
(8) proxy manager和stub manager:
在DCOM系统中,客户进程要与一个proxy object打交道,这个proxy object就叫做proxy manager,服务端对应的有一个stub object,叫做stub manager. proxy manager通过调用一些方法生成客户进程所需的interface proxy对象(给客户进程返回interface proxy对象指针)。一个类可以实现多个接口,每个proxy manager也可以生成多个不同的interface proxy对象,这些生成的interface proxy 对象和proxy manager聚合(aggregate)在一起( 还可看成interface proxy 对象是proxy manager的一部分),使客户应用进程看起来所有的接口就通过一个对象暴露给它,也既客户进程要得到这些接口指针时只需要同一个对象打交道,从而更方便地使客户进程得到所需的接口指针。服务端的stub manager可以生成 interface stub,但它并不聚合这些interface stub,因此服务端的stub manager相对比较简单。
以上简单地介绍了DCOM系统中的一些重要概念,再下面阐述DCOM系统架构的部分中还会涉及到其他的一些概念。