.NET Remoting 體系結構評估(摘自MSDN)

 

.NET Remoting 體系結構評估


Pat Martin
Microsoft Corporation
2003年5月

適用於:
    Microsoft® .NET Framework
    Microsoft® .NET Remoting

摘要:本文適用於要將 .NET Remoting 用於分佈式多層應用程序設計的人員。文章從開發人員的角度介紹了該技術的功能。開發人員曾得益於這項技術所提供的方便的 RPC 機制,也曾感受過其不足之處帶來的不便。本文假設讀者熟悉 .NET Remoting,即使沒有實際使用過,至少對其概念也有所瞭解。

產品特性一節對使用 Remoting 進行設計的人員很有用,最佳方法一節適用於使用 Remoting 進行構建的人員,Remoting 和 Web 服務一節試圖消除有關“在何時選用何種技術”的困惑,摘要是對內容的精煉。

目錄

概述

.NET Remoting 被譽爲管理應用程序域之間的 RPC 的首選技術。應用程序域是公共語言運行庫的隔離單元,它們是在進程內創建並運行的。這與 CLR 和非 CLR 託管的進程之間的進程間通信(互操作)不同。後一種類型的 RPC 通信(特別是 Web 上的)一般被認爲是 Web 服務領域的問題。遺憾的是,這種看似清楚的區分,卻由於可以在 IIS 下集成 .Net Remoting 服務器而變得模糊,正如 Microsoft .NET Remoting 框架簡介一文中所述:

“通過在 IIS 中集成 .NET Remoting 對象,可以將其作爲一種 Web 服務提供……”

一些 Microsoft 客戶可能對 .NET Remoting 或多或少有些疑惑。我經常聽到有人問“應該在什麼時候使用 Remoting?”、“Remoting 何時會支持 NTLM?”、“如何保證遠程會話的安全?”、“COM+ 怎麼樣?”以及“Remoting 如何管理事務?”

除了回答這些問題,本文還將介紹一些使用 .NET Remoting 的最佳方法,並概要介紹當前可以獲得的功能。摘要預測了該技術的未來發展方向,特別是有關 Web 服務和新興的全局 XML Web Service 體系結構 (GXA) 規範的問題。

產品特性一節的信息大部分來自 TechED N.Z. 2002,這次演示重點介紹了在分佈式解決方案中使用 Remoting 的不同方法,闡明瞭 Remoting 的優點,也提到了一些不足之處。

最佳方法一節源於在多層 .NET 應用程序中使用 Remoting 的個人經驗,其中介紹了很多在開發過程中用到的簡單易行的最佳方法。

某些節包括了根據非正式談話整理得到的資料,談話的對象是對該技術及其發展方向都有深刻理解的 Microsoft 內部人員,但這裏提供的信息決不代表未來的產品發佈計劃或安排。

產品特性

本節介紹 .NET Remoting 的功能和產品特性。

客戶端/服務器通信

.NET Remoting 提供了一種很有用的方法,用於管理跨應用程序域的同步和異步 RPC 會話。遠程對象代碼可以運行在服務器上(如服務器激活的對象和客戶端激活的對象),也可以運行在客戶端上(其上的遠程對象已經通過客戶端/服務器的連接進行了序列化)。在任何一種情況下,只要完成初始化和配置(這並不困難),即可使用非常簡單的編程語言,只需要少量的代碼。遠程對象(在按引用封送時是代理的對象)的使用對程序員是透明的。例如,早期的 Windows RPC 機制要求熟悉的類型和使用 IDL 工具的封送處理知識,並向開發人員公開 RPC 客戶端和服務器存根的管理。Remoting 在爲 .NET 提供 RPC 時要容易得多,而且由於使用簡單易懂的 .NET 數據類型,從而消除了早期 RPC 機制中存在的類型不匹配的情況(這是一個非常大的威脅)。

默認情況下,可以將 Remoting 配置爲使用 HTTP 或 TCP 協議,並使用 XML 編碼的 SOAP 或本機二進制消息格式進行通信。開發人員可以構建自定義的協議(通道)或消息格式(格式化程序),並在需要時由 Remoting 框架使用。服務器和客戶端組件都可以選擇端口,就象可以選擇通信協議一樣。由此帶來的一個好處是,很容易建立並運行基本的通信。

但是,在選擇通信類型時還要考慮狀態管理。本節接下來將介紹 Remoting 提供的各種通信選項及其相關的設計含義。

服務器激活的對象

“服務器激活的對象”是由服務器控制生存期的對象。它們只在客戶端調用對象的第一個方法時,根據需要由服務器創建。服務器激活的對象只支持默認的構造函數。要對遠程對象使用參數化的構造函數,可以使用“客戶端激活”或“動態發佈”(參見下文)。服務器激活的對象也被稱爲衆所周知的對象類型,因爲其位置 (URL) 是預先發布和已知的。服務器激活的對象有兩種激活模式:SingletonSingleCall,下面將介紹這兩種模式。要創建服務器激活類型的實例,可以通過編程的方法配置應用程序,也可以進行靜態配置。服務器激活的配置相當簡單,例如,以下代碼片段

<service>
  <wellknown mode="SingleCall" type="Hello.HelloService, Hello" 
                   objectUri="HelloService.soap" />
</service>

描述了一個服務器激活的 (wellknown) 類型,其激活方式設置爲 SingleCall。有關配置服務器激活的 Remoting 的詳細信息,請參閱 MSDN 上 .Net Framework Developer's Guide 中的“Server-Side Registration”。

Singleton

這些對象遵循傳統的 Singleton 設計模式,在這種模式中,任何時候內存中都只有一個實例,所有客戶端都接受該實例提供的服務。但要注意,這些類型都有與之相關的默認生存期(請參閱下文的對象生存期管理一節)。這意味着對於可進行遠程處理的類,客戶端不必總是接收對這個類的同一實例的引用。後一種情況對狀態管理很有意義,也是這種 Remoting 模式與傳統的 Singleton 模式(要求對象標識相同)的不同之處。如果您的設計需要使用傳統的 Singleton 狀態管理模式,有兩種方法可以解決此問題。一種方法是忽略默認的對象租用行爲,以便“在主機應用程序域運行時始終”將對象保存在內存中。以下代碼片段說明了如何做到這一點:

public class MyClass : MarshalByRefObject
{
  public override Object InitializeLifetimeService()
  {
      return null;
  }
}

如上所述,這種機制將對象鎖定到內存中,防止對象被回收,但只能在主機應用程序運行期間做到這樣。對於 IIS 集成,如果集成 Remoting 會話的 IIS 或 IIS 進程被回收(很多原因可以導致這種現象),那麼對象將被破壞。

要完全依賴 Remoting 的線程安全的 Singleton 狀態數據,我們需要做三件事:

  1. 忽略租用機制,使租用成爲無限期的,如上所述。
  2. 將遠程服務器集成在我們自己設計的進程中,例如,可以完全控制其生存期的系統服務。雖然此進程也可以被回收,但與回收 IIS 輔助進程相比,其操作更明顯,更易察覺。有關此機制的詳細信息,請參閱下文的產品特性一節。
  3. 將遠程服務器開發爲線程安全的服務器,因爲這樣可以使用多個線程來完成客戶端的併發請求。這意味着,管理併發將寫入共享資源並通常關注對靜態內存的共享訪問。

SingleCall

SingleCall 遠程服務器類型總是爲每個客戶端請求設置一個實例。下一個方法調用將改由其他實例進行服務。從設計角度看,SingleCall 類型提供的功能非常簡單。這種機制不提供狀態管理,如果您需要狀態管理,這將是一個不利之處;如果您不需要,這種機制將非常理想。也許您只關心負載平衡和可伸縮性而不關心狀態,那麼在這種情況下,這種模式將是您理想的選擇,因爲對於每個請求都只有一個實例。如果願意,開發人員可以向 SingleCall 對象提供自己的狀態管理,但這種狀態數據不會駐留在對象中,因爲每次調用新的方法時都將實例化一個新的對象標識。

動態發佈

還需要考慮服務器激活方法的最後一個類型,即動態發佈。這是一種服務器激活的類型,通過提供程序化的發佈機制,可以對對象結構進行更多的控制。它允許在特定的 URL 發佈特定的對象,並可以選擇使用參數化的構造函數。從結構上講,這種類型可以看作是服務器激活的 Singleton 類型的一個微小變形。有關動態發佈的信息,請參閱 .NET Framework Developer's Guide

客戶端激活的對象

“客戶端激活的對象”是當客戶端調用 newActivator.CreateInstance() 時在服務器上創建的。客戶端本身使用生存期租用系統,可以參與到這些實例的生存期中。這種激活機制能夠提供最廣泛的設計靈活性。如果使用客戶端激活,當客戶端試圖激活對象時,激活請求將發送到服務器。這種機制允許使用參數化的構造函數和針對每個客戶端的連接狀態管理。使用客戶端激活,每個客戶端接受其特定的服務器實例提供的服務,從而簡化了多個調用時對象狀態的保存過程。但使用這些對象時一定要謹慎,因爲很容易忘記會話是分佈式的,對象實際上不僅在進程之外,而且在多層應用程序的情況下,還有可能在計算機之外(在 Internet 上設置一個屬性並不過分)。實用而不花哨的接口應該成爲這裏的準則:爲了提高性能,我們可能需要在高度結合與鬆散耦合之間進行權衡。要創建客戶端激活類型的實例,可以通過編程的方法配置應用程序,也可以進行靜態配置。在服務器上進行客戶端激活的配置相當簡單,例如,以下代碼片段

<service>
  <activated type="Hello.HelloService, Hello" 
             objectUri="HelloService.soap" />
</service>

描述了一個客戶端激活的類型。請注意,我們不再需要 URL,因爲對於客戶端激活的類型,類型本身就足以激活了。另外,wellknown 標記已被 activated 標記替代。有關配置客戶端激活的 Remoting 的詳細信息,請參閱 MSDN 上 .Net Framework Developer's Guide 中的“Server-Side Registration”。

擴展性

在處理遠程方法調用的過程中,.NET Remoting 將格式化的“消息”沿 Remoting 的“通道”從客戶端發送到服務器。消息格式和通道本身都是完全可擴展和可自定義的。默認的通道或格式化程序都可以由自定義構建的組件所替代。消息在傳輸過程中可以在多個“接收點”被截取和更改,允許對消息進行自定義的處理(例如消息加密)。.NET Framework Developer's Guide (Sinks and Sink Chains) 中介紹了自定義機制,而且 Internet 上已經出現了一些自定義的通道和格式化程序(例如,Named Pipe 通道的實現)。大多數人對這種擴展性並不感興趣,因爲該技術提供的默認格式化程序和通道已經可以在最廣的範圍內使用(即 TCP 和 HTTP,尤其是與 SOAP 消息格式化程序一起使用)。但是在最初的設計階段,需要考慮各種解決方案選項,記住這種功能還是有必要的。

異常傳播

.NET Remoting 完全支持跨 Remoting 邊界的異常傳播,這是對使用錯誤代碼,如 DCOM 的重大改進。

使用 Remoting 異常,最好將異常類標記爲可序列化的並實現 ISerializable 接口。這樣,可以跨 Remoting 邊界對異常進行正確地序列化,也可以在構造過程中將自定義的數據添加到異常中。對於需要遠程處理以及在使用中要保持一致的異常,最好定義您自己的異常類。確保使用此方法能捕獲所有異常並進行正確傳播,而且不允許未處理的異常跨過 Remoting 邊界。

對象生存期管理

.NET Remoting 爲管理遠程對象的生存期提供了功能強大的機制。如果我們的服務器對象不保留任何狀態(如 SingleCall 對象),那麼不必關注此進程,只需讓 Remoting 基礎結構完成要完成的工作即可,需要時,對象將作爲垃圾被回收。如果我們保留狀態,無論是服務器激活的 Singleton 還是客戶端激活的對象,我們可能都要參與生存期管理進程:對象租用。我們已經看到很小程度的參與,使用了一種簡單(且有用)的方法,就是忽略 InitializeLifetimeService 方法,如以上對 Singleton 的介紹中所述。這就使我們能夠在集成對象的進程運行期間始終保留對象。那麼,這個對象生存期進程如何工作呢?

Remoting 提供的對象管理機制基於租用原則:您永遠不會擁有一個對象,只是借用它,只要持續支付就可以一直使用它。此過程將在下文中進一步介紹。但是,首先要簡單介紹一下在 COM 領域中是如何處理對象清理的。DCOM 綜合使用 ping 和引用計數兩種方法來確定對象是否仍在運行,這樣做不僅容易出錯,而且對網絡帶寬的要求很高。使用引用計數時,最壞的情況是從來不會被完全理解,最好的情況也是很脆弱。過去(現在仍是)要對引用計數應用一些簡單的規則才能使其發揮作用。COM 對象的 IUnknown 接口包括了 AddRefRelease 方法,需要由開發人員在適當的時候調用。有時程序員弄錯了,結果造成對象沒被刪除,還導致相關的內存泄露。

相反,Remoting 基於租用的生存期管理系統綜合利用了租用、負責人和租用管理器。每個應用程序域都包含一個租用管理器,它將每個 Singleton 或客戶端激活的對象的租用對象引用保存在其域中。每個租用可以有零個或多個相關的負責人,負責人能夠在租用管理器確定租用過期時重新租用。這種租用功能是由 Remoting 基礎結構通過 ILease 接口提供的,通過調用 InitializeLifetimeService 獲得,如上文所述。ILease 接口定義了很多用於管理對象生存期的屬性:

  • InitialLeaseTime。確定租用最初的有效期。
  • RenewOnCallTime。在每個方法調用後,更新此時間單元的租用。
  • SponsorshipTimeout。負責人通知租用過期後,Remoting 要等待的時間。
  • CurrentLeaseTime。距租用到期的時間(只讀)。

租用過期後,租用管理器將通知所有租用負責人,詢問他們是否要更新租用。如果不更新,將釋放相關的對象引用。

負責人是可以爲遠程對象更新租用的對象。要成爲負責人,您的類必須從 MarshalByRefObject 中導出並實現 ISponsor 接口。一個租用可以有多個負責人,一個負責人也可以參與多個租用。

有關使用這些接口進行編程的租用管理機制,請參閱 Lifetime Leases(英文)上的 .NET Framework Developer's Guide 文檔,這裏就不重複介紹了。但值得注意的是,這種功能強大的機制只是對管理有狀態的遠程對象的生存期有意義。如上所述,您或者完全忽略它,利用它在其進程容器運行時將對象保存在內存中,或者完全參與到租用機制中。

遠程服務器集成

有很多方法可以集成 .NET 遠程服務器,主要分爲兩大類,如下所述。

ASP.NET 下的 IIS 集成

在 IIS 下集成遠程服務器端對象的能力是作爲標準功能提供的。它有很多優勢,包括支持安全性和可伸縮性。

要在 IIS 下集成對象:

  1. 開發遠程類並從 MarshalByRefObject 中繼承(或將類聲明爲可序列化)。
  2. 使用 IIS 管理器創建一個虛擬的 Web 應用程序。
  3. 將包含您的類的程序集放到虛擬 Web 應用程序的 bin 子文件夾中。
  4. 創建一個 web.config 文件以保存 Remoting 服務器的配置定義,並將它放置到 Web 應用程序的虛擬根目錄中。

就這麼簡單。但是,您應該瞭解一些限制:

  • 不能爲 IIS 集成指定應用程序名稱,因爲它是虛擬應用程序名稱。
  • 必須使用 HHTP 通道。
  • 如果 Remoting 客戶端也是一個 Web 應用程序,則啓動時必須調用 RemotingConfiguration.Configure,它通常在 Global.asax 文件的 Application_Start 方法中。不能使用 <client> 標記來自動配置客戶端 Web 應用程序。
  • 不要指定端口,因爲 IIS 會進行端口分配。如果需要,您仍可以使用 IIS 管理來爲虛擬應用程序指定端口。

Remoting 應用程序域將集成在 Aspnet_wp.exe 輔助進程中,默認情況下,它將採用該進程的標識。

注意:目前 ASP.NET 中有一個錯誤,要求將 Aspnet_wp.exe 輔助進程的進程標識設置爲“system”或本地計算機帳戶,默認設置中,machine.config 中的“machine”配置不正確,導致在域控制器的 IIS 下集成時,ASP.NET 應用程序出現錯誤 500“內部服務器錯誤”。可以論證的是,該錯誤是由於缺乏說明如何適當地配置計算機帳戶的文檔所造成的。

在 IIS 下集成有很多功能上的優勢:默認情況下,可以提供伸縮性、線程、審覈、身份驗證、授權和安全通信等功能。ASP.NET 輔助進程一直在運行,並且可以使用 machine.config 中的 <processModel> 元素進行線程和錯誤管理方面的微調。簡而言之,IIS 的優勢和功能都可用於遠程服務器。

但它也有一些缺點:您必須使用比 TCP 速度慢的 HTTP。另外,IIS 可能循環執行 ASP.NET 輔助進程,這將破壞所有 Singleton 的狀態。對您來說,這可能是問題也可能不是問題,要取決於您的設計需要,因爲客戶端的下一個調用將重新啓動 Singleton。您可以將 IIS 配置爲不循環執行輔助進程,但這種能力很有限,特別是在 IIS 5 中,而且可能造成更進一步的影響。這裏最根本的意思是,如果要求遠程服務器的安全性,那麼無疑要使用 IIS 集成。至於性能,只有在系統測試/使用過程中實際察覺到問題時,才需要考慮,而且總能在硬件上找到解決問題的辦法。

IIS 下要考慮的身份驗證問題

身份驗證選項

.NET Remoting 沒有自己的安全模式:身份驗證和授權是由通道和主機進程執行的,在這種情況下則由 IIS 執行。Windows 身份驗證可用於 Remoting,配置方法是在 web.config 中設置 <authentication mode="Windows"/>。不能使用表單或 Passport 身份驗證,因爲 Remoting 客戶端不能訪問 Cookie,也不能重新定向到登錄頁面(因爲遠程服務器是爲非交互使用設計的)。

將憑據傳遞到遠程對象

如果遠程對象是 IIS 集成的(在 ASP.NET 輔助進程中)並配置爲使用 Windows 身份驗證,則必須使用通道的憑據屬性指定要使用的憑據,否則將導致不傳遞任何憑據就進行遠程調用。這種疏忽是 HTTP 訪問拒絕響應的常見原因。要使用集成遠程對象代理的進程(Remoting 客戶端進程)的憑據,請將通道的憑據屬性設置爲由進程憑據緩存維護的 DefaultCredentials。這可以使用通道元素(用於 Web 客戶端),即 <channel ref="http" useDefaultCredentials="true"/> 公開地完成,也可以使用以下代碼通過編程方式完成:

IDictionary channelProperties;
channelProperties = ChannelServices.GetChannelSinkProperties(proxy);
channelProperties["credentials"] = CredentialCache.DefaultCredentials;

要隨遠程對象調用一起傳遞“特定的”憑據,請禁用默認憑據,即設置 <channel ref="http" useDefaultCredentials="false"/> 並使用以下代碼:

IDictionary channelProperties =
ChannelServices.GetChannelSinkProperties(proxy);
NetworkCredential credentials;
credentials = new NetworkCredential("username", "password", "domain");
ObjRef objectReference = RemotingServices.Marshal(proxy);
Uri objectUri = new Uri(objectReference.URI);
CredentialCache credCache = new CredentialCache();
// 用 Negotiate、Basic、Digest、
// Kerberos 或 NTLM 替換 authenticationType
credCache.Add(objectUri, "authenticationType", credentials);
channelProperties["credentials"] = credCache;
channelProperties["preauthenticate"] = true;
注意:preauthenticate 屬性設置爲真(如上所述)將使 WWW 身份驗證標頭隨初始請求傳遞。這將停止 Web 服務器拒絕對原始請求的訪問,並對隨後的請求執行身份驗證。

在 IIS 之外集成

在 IIS 之外進行遠程集成的方法有很多,如下所示。

在控制檯應用程序中集成

開發人員可以編寫一個啓動 Remoting 基礎結構的控制檯應用程序,然後把它“留在附近”。把它留在附近的唯一原因,是因爲它包含集成了遠程調用的應用程序域。編寫一個這樣的程序非常簡單:只需調用 RemotingConfiguration.Configure 方法,把您的遠程主機配置文件傳遞給它,然後只需等待由某個事件,比如按鍵或收到特定的消息等來終止進程。

這種方法的優勢是不要求使用中間層上的 IIS,但不可以隨時生成,因此適用於演示、開發和測試。這並不是說它一無是處,只是用途有限而已。

在 GUI 應用程序中集成

開發人員還可以編寫一個啓動 Remoting 基礎結構的 Windows GUI 應用程序,然後把它“留在附近”。同樣,需要繼續執行的唯一原因是它包含集成了遠程調用的應用程序域。它的開發方法與控制檯應用程序的方法相同:Remoting 主機可以直接啓動,也可以根據用戶的交互操作啓動。同樣,這種方法也具有不需要中間層上的 IIS 的優勢,並可用於演示和測試。對該程序做一些變化可以得到對等網絡(邏輯)winforms 應用程序,例如,聊天類型的應用程序。同樣,該程序的使用範圍也很有限。

在系統服務中集成

這種可能性非常有意思,因爲 Remoting 基礎結構提供的功能竟沒有系統服務概念本身所提供的功能多。系統服務可以配置爲在計算機啓動時啓動,並保留在周圍直到您讓它們離開,這對於遠程集成是非常理想的。請注意,通過爲虛擬應用程序設置“高隔離模式”,也可以將 IIS 應用程序配置爲具有類似行爲。關於這個問題還有很多內容值得探討,本文就不討論了。客戶詢問了許多關於這種機制的難題,包括它的用途。首先,介紹一些它的優點:我們已經介紹了服務本身的好處;另外,我們可以完全控制主機進程的激活,例如,可以選擇是使用動態發佈還是使用客戶端激活;不需要 IIS,因爲我們可以加載用戶配置文件,並可以使用 TCP 上的二進制編碼消息獲得良好的性能。

但它的缺點也很多。首先,如果需要,您要構建自己的身份驗證和授權機制。.NET Remoting Security Solution, Part 1:Microsoft.Samples.Security.SSPI Assembly(英文)一文完整詳細地介紹了 .NET Remoting 的安全性解決方案:“……實現了 SSPI 的託管包裝,提供了驗證客戶端和服務器以及簽名和加密在二者之間發送的消息所需的核心功能。”這無疑是一筆寶貴的資源,它提供了一種添加此功能的機制,這非常有用。但問題是它並不是一個受支持的產品,而是一個提供補充功能的“非正式”方法。而且對開發人員還有一點威脅,因爲該解決方案要依賴格式化程序和通道的可擴展性。所有這些都需要回避,要獲得功能,必須向 Remoting 配置添加條目以說明使用 Windows NT Challenge/Response (NTLM)。但此類安全機制很有可能要加入到 .NET Remoting 的未來版本中。

系統服務也需要具有可伸縮性,並可作爲 Remoting 服務器重新使用,因爲多層的分佈式應用程序將需要這些功能。例如,如果沒有 IIS,集成服務將不得不管理自己的審覈和授權,而這二者都是 IIS 在標準情況下附帶的。

由於這些原因,系統服務集成機制的用途很有限,也許要在一個受約束的環境下使用,這種環境中的消息要排隊進行單獨交換,而安全性不是問題,或者還可以使用 TCP 上的 IPSec。

企業服務管理

爲了使遠程組件參與到 COM+ 環境中(並在 COM+ 的上下文中運行),需要從 ServicedComponent 中繼承。ServicedComponentSystem.EnterpriseServices 命名空間中提供的其他功能都允許 CLR 組件指定多個 COM+ 屬性,如表示事務要求和服務器進程執行的屬性等。再加上嚴格命名機制和使用 regsvcs 命令,遠程組件可以成爲整個 COM+ 環境中的一部分。

假設遠程組件需要從 MarshalByRefObject 中繼承,COM+ 組件需要從 ServicedComponent 中繼承(而且在 .NET 託管代碼中沒有多重繼承功能),如何實現這一點呢?幸運的是,ServicedComponent 是從 ContextBoundObject 派生的,而後者又是從我們需要的 MarshalByRefObject 派生的。在 Remoting 上直接構建 COM+ 集成是完全可以的,而且確實能夠獲得由企業服務提供的顯而易見的優勢,例如對象池、分佈式的事務支持和基於角色的安全性等。但是,如何做到這一點以及這樣的方法對未來驗證的體系結構會產生什麼樣的影響,還是不得而知的。

我們有理由期待,隨着時間的推移,COM+ 的上下文基礎結構和 Remoting 的上下文基礎結構將越來越接近。但在現階段,如何做到這一點以及何時做到這一點還不很清楚。

使用 Remoting 的最佳方法

一直以來,開發和測試分佈式組件不僅項目開銷大,而且很令開發人員頭疼。以下指導原則是在實踐中摸索得到的,來之不易。

入門

Basic Remoting Task List(英文)一文提供了良好的開端,可以對照此文章檢查在首次設置 Remoting 時要執行哪些任務,最好在整個過程中都將此文章作爲參考資料。下面簡單介紹一下要執行的步驟:

主機任務

  • 設計服務,選擇應用程序域、激活模式、通道、端口和發佈。
  • 實現 Remoting 主機應用程序域(例如 IIS/系統服務)。
  • 配置主機激活、通道和協議設置。建議使用配置文件,可以通過調用 RemotingConfiguration.Configure 加載。
  • 發佈接口,供客戶端使用(有關詳細信息,請參閱下文中的“接口發佈選擇”)。

客戶端任務

  • 設計客戶端,選擇應用程序域和激活模式。
  • 考慮是否需要註冊通道和端口。
  • 獲取遠程類型元數據。
  • 實現客戶端應用程序域。
  • 配置客戶端激活模式和其他類型的信息,如應用程序名稱、通道和對象 URI 等。建議使用配置文件,可以通過調用 RemotingConfiguration.Configure 加載。

格式化選擇

作爲標準,Remoting 可以配置爲在 HTTP 通道上使用 SOAP 或二進制格式化程序,或者在 TCP 通道上使用二進制格式化程序。一般情況下,在客戶端配置文件中輸入適當的條目和調用靜態的 RemotingConfiguration.Configure 方法都可以實現這種配置。

例如,要將 Remoting 連接配置爲使用 HTTP 上的二進制格式化程序,可以按以下方法完成配置條目:

<channel ref="http" useDefaultCredentials="true" port="0">
  <clientProviders>
    <formatter ref="binary"/>
  </clientProviders>
</channel>

這裏的“channel ref”指 HTTP 協議,“formatter ref”指要在通道上發送的消息格式,在此示例中爲二進制。

遺憾的是,在開發過程中將二進制格式化程序用於 HTTP 通道,會產生屏蔽服務器端錯誤的副作用,例如,一般的服務器錯誤或訪問衝突都會誤報給客戶端。這是因爲使用二進制格式化程序時,客戶端的 Remoting 組件需要以二進制格式返回消息,它無法正確解釋純文本的錯誤結果,並報告以下錯誤:

mscorlib.dll 中出現無法處理的異常類型 System.Runtime.Serialization. SerializationException。其他信息:BinaryFormatter 版本不兼容。需要使用 1.0 版。收到的版本爲 1008738336.1684104552。

這種錯誤大部分“不是”因爲版本不兼容,而是因爲客戶端無法分析文本格式的錯誤響應。雖然我們相信這種協議缺陷能夠在產品的未來版本中得到解決,但還是強烈建議您在開發過程中使用 SOAP 格式化程序。證實之後,可以將此格式化程序切換爲二進制以增強性能,但應該在性能優勢充分且必要的情況下才這樣做。

接口發佈選擇

設計並構建 Remoting 服務器之後,應將其提供的接口發佈給客戶端使用,以解析編譯時的引用並允許動態地創建代理對象。有很多方法可以完成此操作,這裏有必要重複一下。但首先有幾點提示:

  • 靜態字段和方法永遠都不能進行遠程處理,.NET Remoting 始終處理某些形式的實例成員。
  • 私有方法/類型不能進行遠程處理。
  • MarshalByRef 類型是通過引用進行遠程處理的,可序列化的類型是在客戶端進程中複製值並執行代碼。
  • 對象虛擬方法 EqualsGetHashCodeMemberwiseClone 等在本地執行。

瞭解了這些設計中應該注意的地方,就可以選擇使用以下方法發佈由 Remoting 服務器導出的接口:

  • 向客戶端提供服務器端的程序集,以在編譯時使用。當只需要接口而不需要實現時,不建議也沒必要使用這種方法。
  • 對於 SOAP/HTTP 客戶端(這裏的 Remoting 服務器的功能是提供 Web 服務,儘管對這種服務還有些疑惑),Remoting 服務器可以提供說明服務器對象和方法的 Web 服務說明語言 (WSDL) 文件。.NET Framework SDK 附帶的 SOAPSUDS 實用程序可用於生成這些 WSDL 文件,以作爲元數據使用。實際上,這種方法更適合 Web 服務(從嚴格的 asmx 意義上講)而不是 Remoting,因爲 Remoting 接口的 WSDL 並不能與 Web 服務接口的 WSDL 完全兼容。Soapsuds Tool(英文)上的 .NET Framework Tools 文檔詳細介紹了 SOAPSUDS 實用程序。
  • 在單獨的庫中聲明一個接口並使用客戶端部署該庫。發佈執行該接口的服務器類,客戶端將可以使用它,方法是獲取它執行的接口的代理。這是一種非常清楚的設計選擇,因爲它是人們特別感興趣的接口。這種方法只能用於服務器激活的對象(請參閱產品特性一節),因爲無法創建接口的實例。
  • 使用 SOAPSUDS 爲客戶端構建替代類作爲元數據使用。您可以對 Remoting 服務器程序集運行 SOAPSUDS,生成輸出程序集(可以作爲元數據直接使用)或源文件(可以直接包括在應用程序中)。 這種機制對於構建多層應用程序很有用,在這種應用程序中,一層中的對象要訪問另一層中的遠程對象。這種方法很有意思,上文的簡介部分中引用的多層應用程序就使用了此方法。

假設我們在以下文件夾中打開一個命令窗口:

$FRAMEWORKSDK/Samples/Technologies/Remoting/Basic/RemotingHello/Service

我們可以編寫:soapsuds -id:.-types:Hello.HelloService,Hello -oa:HelloInterface.dll

這將創建一個輸出程序集 HelloInterface.dll,它包含在當前目錄的 Hello 程序集中找到的只基於 Remoting 服務器 Hello.HelloService 的元數據。該程序集可由客戶端直接使用。Remoting 服務器的位置是根據標準的 Remoting 配置,基於運行時提供的配置數據派生得到的。爲客戶端程序集生成的 MSIL

ldfld object [System.Runtime.Remoting]System.Runtime.Remoting.Services.RemotingClientProxy::_tp

清楚地顯示出我們沒有使用 Remoting 服務器實現,而是使用了由 SOAPSUDS 生成的元數據所構建的代理類。

不能確保/支持 SOAPSUDS 使用二進制進行格式化,因爲它在輸出程序集元數據中嵌入/映射了一些 SOAP 特有的內容。

建議您儘量保持 Remoting 接口的簡單,使用“充實”而不“花哨”的接口,也就是說,要試着限制設計中遠程調用的數量。在某些情況下,這可能需要傳遞冗餘參數。將遠程接口放在單獨的類中,與實際實現的類相區分。這樣可以獲得一種表面類型模式:在需要時,可以輕鬆地使用另一種技術替換其中的 Remoting 層。

管理錯誤

本節介紹在開發(和使用)Remoting 解決方案的過程中可能會遇到的錯誤情況。在任何情況下,都應該記住要使用標準的設備使用和監視方法。事件記錄仍是非常有價值的信息資源,就象網絡監視器工具一樣,網絡監視器可以專門用於詳細查看客戶端/服務器的 Remoting 會話。中間層的 Remoting 服務器仍可以使用 Visual Studio .NET 提供的標準調試工具進行調試,例如,對於由 IIS 集成的 Remoting 服務器,可以通過向 ASP.NET 輔助進程附加調試會話(Visual Studio .Net | Debug [調試] | Processes [進程] | Attach [附加]) 來設置斷點(如果資源可用)。但 Remoting 的錯誤很獨特,下面列出了一些。請注意,所有錯誤都已使用 .NET Framework SDK 提供的 Basic Remoting Hello Sample 的各版本進行了復現,服務器和客戶端也已在單機上運行。故障現象與在網絡鏈接上的相同,只是由於 HTTP/TCP 的超時設置不同,需要相當長的時間才能出現錯誤。

丟失 MarshalByRef

由於 Remoting 要通過引用以用於給定的類,該類必須只做一件事,就是繼承 MarshalByRefObject。假設開發人員忘記做這項工作,我們將得到一個 System.Runtime.Remoting.RemotingException 類型的異常,說明我們有一個“丟失的 MarshalByReference”.

是否能正確捕獲和處理這個 RemotingException 將取決於程序員。(想想這個開發人員忘記了他應記住的唯一一件事。)

解決方法是:記住繼承 MarshalByRefObject

衆所周知的服務器激活的錯誤服務器端點

對於服務器激活(請參閱產品特性一節),Remoting 服務器將其偵聽處聲明爲端點。該端點一般包括一個對象 URI(遠程對象的衆所周知名稱),一個協議和一個端口號。當然,所有這些都可能配置錯誤。

錯誤的 URI

由服務提供的 Basic Remoting Hello Sample 的 URI 是 HelloService.soap,如相關的 web.config 文件中所指定:

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="SingleCall" type="Hello.HelloService, Hello"
                   objectUri="HelloService.soap" />
      </service>
    </application>
  </system.runtime.remoting>
</configuration>

此服務是 IIS 集成的。IIS 集成要求 URI 帶有後綴 .rem 或 .soap,我們在服務器上使用 .rope。在此實例中,我們將再次收到 RemotingException,這次顯示的文本是“對象 </Hello.soap> 在服務器上已斷開或不存在”。

請確保各個 URI 相互匹配!當 IIS 集成 Remoting 服務器時,還要確保 URI 以 .rem 或 .soap 結尾。

不匹配的協議/端口

爲了進行此項測試,我們切換到控制檯集成的服務器,以下是該服務器的配置文件:

<configuration>
  <system.runtime.remoting>
    <application name="RemotingHello">
      <service>
        <wellknown mode="SingleCall" type="Hello.HelloService, Hello"
                   objectUri="HelloService.soap" />
      </service>
      <channels>
        <channel ref="http" port="8000" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

假設我們要在服務器端將協議更改爲 TCP,而使客戶端保留 HTTP。

我們將再次收到 RemotingException,這次的文本是“底層連接已關閉:接收時出現意外錯誤”。

端口設置錯誤也會導致上述異常,唯一的不同是這種情況下,要用較長的時間纔會出現錯誤。服務器和客戶端之間的端口和協議必須匹配。

丟失 URI

另一種可能性是遠程服務器沒有運行,例如,服務器由 IIS 集成,而虛擬應用程序或相關的程序集丟失。再次使用 Basic Hello Remoting 服務器,我們需要虛擬應用程序 RemotingHello 能夠運行。如果不能運行,我們將收到未處理的異常(取決於調用代碼),但這次的異常將是:“無法加載類型 clr:Hello.HelloService, Hello”。

在這些情況下,請確保虛擬應用程序在運行,而且所需的程序集正確地放置在相關的 bin 子文件夾中。

總而言之,客戶端必須正確地引用服務器定義的端點以便激活服務器,這意味着,端口、協議和 URI 的定義必須相互匹配。這太容易出錯了。因此,如果服務器的位置定義爲:

<service>
   <wellknown mode="SingleCall" type="Hello.HelloService, Hello" 
              objectUri="HelloService.soap" />
</service>

那麼,客戶的設置必須爲:

<client url="http://localhost/RemotingHello">
   <wellknown type="Hello.HelloService, Hello" 
              url="http://localhost/RemotingHello/HelloService.soap" />
</client>

其中,URL 表示集成 Remoting 服務的 IIS 虛擬應用程序,類型表示類和程序集名稱。

Remoting 和 ASP.NET Web 服務

IT 設計中最好也是最壞的事情就是可以選擇的體系結構組件太多了。Web 服務和 .NET Remoting 就屬於這種情況,有時很難決定針對不同的目的應該選用哪種技術。當然,正確的答案是爲要解決的問題選擇最佳的技術。不要使用“始終使用 Web 服務”或“Web 服務是 Remoting 的子集,因此它就等於所有的 Remoting”等指令性的評述。本節將主要介紹這兩種技術,說明在特定的情況下,爲什麼是選擇這一種更有意義而不是另一種。

ASP.NET Web 服務和 .NET Remoting

讓我們從 Web 服務的定義開始,定義說 Web 服務就是可以在 Web 上提供的服務。這個定義並不是很有用,我們不妨進一步把它提煉成“通過 SOAP 和 HTTP 訪問的、可尋址的處理單元,這個處理單元是用 WSDL 描述的,可以通過 UDDI 發佈。”這個定義就有用多了,因爲它把 Web 服務和將 HTML 發送回瀏覽器的 Web 服務器區分開了。爲了與 .NET Remoting 進行比較,我們特別強調了 Web 服務的定義,它與可在 Web 上提供的程序化的服務不同。例如,根據我們的定義,可以使用 WSDL 從客戶端通過 HTTP 訪問的遠程主機就是 Web 服務。鑑於此(並強調 Microsoft ASP.NET Web 服務實現),對於分佈式解決方案,在選擇 ASP.NET Web 服務和 .NET Remoting 的“結合點”時,應該考慮哪些因素呢?

互操作性

一種常見的 Microsoft 理論是:如果需要在不同系統之間進行互操作,應該選擇使用開放標準 (SOAP、XML、HTTP) 的 Web 服務方法,而使用 .NET Remoting 決不是一種交互的解決方案;如果各種系統中的所有組件都是 CLR 託管的,則 .NET Remoting“可能”是正確的選擇。這一原則的適用範圍很廣,但有所區分還是非常有用的。.NET 遠程對象的客戶端應該是 .NET 客戶端。如果您的功能必須在 Web(這裏的 Web 即 Internet)上通過鬆散耦合的 SOAP 客戶端(例如 Unix 進程)才能實現,則 Web 服務將是正確的選擇。當然,Intranet 就不受這種限制:所有客戶端都可以是 .NET 客戶端,而且在這種配置中並不排除 .NET Remoting。同樣,對於應用程序的中間層在防火牆之後並與 Web 層直接通信的環境,仍可選擇 .NET Remoting。

強大的類型支持

.Net Remoting 支持所有託管的類型、類、接口、枚舉、對象等,這通常被稱爲“多類型保真”。這裏的關鍵在於,如果客戶端和服務器組件都是在應用程序域中運行的 CLR 託管的對象,則數據類型的互操作是不成問題的。從根本上講,我們擁有的是一個封閉的系統,會話的兩端可以完全被理解,因此我們可以充分利用這一事實,處理好用於通信的數據類型和對象。

在各種系統並存的情況下,我們需要考慮系統之間的互操作性。對於可互操作的數據類型,我們要謹慎處理。例如,Web 服務數據類型的定義要基於 XML 架構定義 (XSD) 關於數據類型的說明。任何可以使用 XSD 進行描述並可以在 SOAP 上進行互操作的類型都可以使用。但是,這也確實使得某些數據類型不能使用。例如,對於無符號的字符類型或枚舉,不存在相應的 W3C XSD 表示法。對於不同的 Web 服務實現,集合的處理不同,異常和數據集的處理也不同。另一個問題是,私有字段和屬性不在 Web 服務調用之間傳遞,這對字段和屬性本身來說並不是關鍵問題,但如果您的系統要求在不同的技術之間進行互操作,則在設計和測試系統時,這卻是一個要考慮的因素,因爲可以發送內容並不意味着可以接收到它。

再重複一遍,如果需要在不同的系統之間進行互操作,就不應該考慮使用 .NET Remoting 技術。如果是封閉的、CLR 託管的解決方案,則可以使用它。

狀態管理

我們已經看到很多方法,使用基於激活方式(客戶端激活或 Singleton)的 .Net Remoting 實現狀態管理。對 .NET Remoting 和 Web 服務來說,通過 HTTP(帶有不確定超時的無狀態協議)來管理每個客戶端的連接狀態是件煩瑣且不切實際的事情。但是,如果您需要維護狀態,那麼 Remoting 提供了一種基於每個對象的解決方案。Web 服務沒有提供這種每個客戶端的連接狀態管理,但提供了對 ASP.NET 會話和應用程序對象的訪問。

生存期管理

與狀態管理有關的是生存期管理。正如我們所看到的,Remoting 爲管理遠程對象的生存期提供了功能強大的機制。Web 服務對象隨 Web 服務的調用而存在和消失(從概念上講,對同步和異步都是這樣)。在這方面,Web 服務與 Remoting 相比,是一種單一調用類型。Remoting 對遠程對象的激活和終止提供了更大程度上的控制。這對於您的設計可能有意義,也可能沒意義。

按值調用和按引用調用

傳遞到 Web 服務調用的對象是經過序列化的,並按值進行傳遞。傳遞到 Remoting 的對象或被調用的對象本身可以按值或按引用進行傳遞。序列化的遠程對象方法是在客戶端進行處理的。在 Remoting 和 Web 服務之間進行選擇時應該考慮這些不同。當然,這些考慮對您來說是否重要,也取決於要解決的問題的性質。

支持的協議

Web 服務調用僅限於 HTTP 上的 SOAP 編碼的 XML。Remoting 可以使用 TCP 傳輸,或者擴展基礎結構以支持自定義的協議。例如,在 www.gotdotnet.com 上的 jhawk 用戶示例部分提供了一個使用 Named Pipe 的 Remoting 實現。

這裏是 NamedPipe 自述文件的一個片段,闡明瞭 Remoting 的可擴展性:

通過實現 IChannel* 接口,可以使用可插入式通道結構將通道插入到 .NET Remoting 中。
Named Pipe 通道支持以下功能:
* 通過命名管道進行通信
* 同步消息
* 異步消息
* 單程消息
* 回調
* 通道接收
* 通道屬性
* 自動生成管道名稱

因此,如果您需要 Named Pipe,Remoting 可以提供解決方案。但是,與 SSPI NTLM 身份驗證解決方案一樣,Microsoft 目前也不支持這種解決方案,也許將來 Microsoft 會滿足這種需要。

性能

如果性能對您的設計確實至關重要,那麼通過 TCP 使用二進制消息格式的 Remoting 確實提供了一些顯著的性能優勢。對於本文所介紹的結果,如果要完整了解產生此結果的測試環境和測試,請參閱性能比較:.NET Remoting 與 ASP.NET Web 服務一文。

這裏是從這篇文章中總結出的一些性能統計:

圖例:ASMX - Web 服務,其他都是 Remoting 解決方案
WS 表示集成遠程組件的 Windows 服務。

圖 1:性能統計

文章接下來對性能圖表進行了解釋,如下所述:

“如上所示,對於 WS_TCP_Binary,其中的對象被配置爲使用 TCP 通道和 Binary 格式化程序,而主機是 Windows 服務,其性能要優於其他的分佈式技術。這是因爲該方法通過原始 TCP 套接字傳輸二進制數據(比 HTTP 的效率高),且數據不需要編碼/解碼,因而降低了系統開銷。可以看到,WS_TCP_Binary 和最慢的方法之間存在約 60% 的性能差距。
雖然 IIS_HTTP_Binary 與 WS_HTTP_Binary 產生的二進制負載相同,但其速度較慢,原因是從 IIS (Inetinfo.exe) 到 Aspnet_wp.exe 之間有額外的進程躍點。IIS_HTTP_SOAP 與 WS_HTTP_SOAP 的性能差別也是由此造成的。
WS_HTTP_Binary 和 WS_TCP_SOAP 的性能幾乎相同。儘管前者有 HTTP 分析方面的額外系統開銷,後者有 SOAP 分析方面的額外系統開銷,但在本例中 HTTP 分析的系統開銷與 SOAP 分析的系統開銷幾乎相同。
ASP.NET Web 服務的性能優於 IIS_HTTP_SOAP 和 WS_HTTP_SOAP,因爲 ASP.NET XML 序列化比 .NET Remoting SOAP 序列化的效率高。從上述內容可以看出,ASP.NET Web 服務與 IIS_HTTP_Binary 的性能幾乎相同。”

如果原始速度確實非常重要,那麼這“60% 性能差距”就很有意義了。其缺點是要將服務器集成在 Windows 服務中,以便使用 TCP 協議(請參閱前面的遠程集成一節)。它有效地權衡了性能的安全性,而且是一種“最好不要用於 Internet 或不安全的 Intranet”的方法。

小結

ASP.NET Web 服務基於 XML,用於要求使用 HTTP(假定它們集成在 IIS)的實際應用中,能夠提供簡單的編程模式和強大的跨平臺支持,它通過使用 SoapExtensions 提供了一定程度的擴展性,例如加密數據流。Remoting 的編程模式更爲複雜,但就協議和消息格式而言,它在類型保真、狀態管理和擴展性方面具有明顯的優勢。Remoting 不能用於非 .NET 客戶端,因此無法實現 Internet 客戶端與遠程主機的直接連接。當在 IIS 之外集成時,Remoting 不能提供安全性模型。當集成在 IIS 時,Remoting 可以提供與 ASP.NET 相同的安全性功能,包括使用 SSL 等安全協議。如果不需要考慮與其他平臺的互操作性,而且客戶端和服務器的配置完全在您的控制之下,則可以考慮使用 .NET Remoting。使用 Remoting 時,使用了 HTTP 通道的 IIS 集成要優於非 IIS 集成,這樣,可以得益於相關的安全性和伸縮性基礎結構。當然,這意味着您必須能夠在解決方案中與 IIS 進行互操作。如果這無法實現,那麼使用 Remoting“可能”就是件無法實現的艱鉅任務了,這與要解決的問題的性質有關。由於 .NET Remoting 要求使用 .NET 客戶端,因此有必要使用最快的可用格式化程序,這樣一來,選擇二進制而不選擇 SOAP 將產生更好的性能。請記住上文的最佳方法一節的建議,在發佈時使用此格式化程序,而不要在開發過程中使用。

摘要

.NET Remoting 是在某些分佈式解決方案中使用的有效工具,它在所支持的協議和消息格式方面提供了可擴展的模型,並能在特定的情況下提供性能優勢。它不應直接部署在 Internet 上,而且它的服務器對象應該集成在 IIS 之下,以充分利用 IIS 爲在其控制下運行的進程提供的安全性和性能特性。

對於“封閉”的分佈式解決方案,其中的客戶端和服務器都是 CLR 託管的進程,應該考慮使用 Remoting。例如,Intranet 解決方案中使用安全 TCP 通道(如 IPSec)或 HTTP 的任意層中的組件,或者通過防火牆與 .NET Web 層組件會話的中間層應用程序組件。在這種情況下,當證實應用程序使用 SOAP 格式化程序後,應該選擇二進制格式化程序和 HTTP 通道。

對於要與非 CLR 客戶端進行互操作的系統,請使用 ASMX Web 服務,但要謹慎處理某些數據類型(請參閱強大的類型支持一節)。

請注意,使用 TCP 在 IIS 之外集成會帶來性能優勢,但需要自定義的安全性。

設計與實現

實現和配置 Remoting 是一個相當容易的過程。在此過程中,首先要選擇 Remoting 主機、協議和激活模式。請儘量簡化設計和實現過程,並認真考慮哪種接口發佈機制對您的解決方案最有意義。建議的方法是,只把接口作爲最易懂的概念模型來發布,但這樣一來就不能使用客戶端激活的對象。調試程序、事件日誌和網絡監視是開發過程中非常有用的工具,在開發遠程組件時,它們也能助您一臂之力。

Remoting 的未來

象“何時使用 Remoting、何時使用 Web 服務”等問題都是很難回答的問題,更何況術語的定義也不是很清楚。例如,如果 Web 服務的定義不清楚,Remoting 就有可能配置爲 Web 服務。

或許將來 Remoting 和 ASMX 技術能逐步融合。但在目前,我們至少可以比較合理地說明何時使用哪種技術,如上所述。

當前的開發重點是提供路由、安全性和事務支持的 GXA 實現。這種實現要基於使用 SOAP 標頭,而目前的直接目標是擴展 Web 服務的功能。雖然如本文所述,從傳統意義上講,GXA 不支持 .NET Remoting,但它支持 Remoting 解決的很多問題,如狀態和事務管理等。雖然現在的 GXA 實現可以解決 Web 服務所面對的許多問題,但它最根本的目的是儘量以不需要太高技術含量的方式解決這些問題。看到 GXA 的開發對 Web 服務和 .Net Remoting 的影響,將是一件充滿樂趣的事情。

其他資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章