天天看點

.NET Remoting 體系結構評估

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

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

目錄

  • 概述
  • 産品特性
  • 使用 Remoting 的最佳方法
  • Remoting 和 ASP.NET 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) 是預先釋出和已知的。伺服器激活的對象有兩種激活模式:Singleton 和 SingleCall,下面将介紹這兩種模式。要建立伺服器激活類型的執行個體,可以通過程式設計的方法配置應用程式,也可以進行靜态配置。伺服器激活的配置相當簡單,例如,以下代碼片段

<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。

用戶端激活的對象

“用戶端激活的對象”是當用戶端調用 new 或 Activator.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 接口包括了 AddRef 和 Release 方法,需要由開發人員在适當的時候調用。有時程式員弄錯了,結果造成對象沒被删除,還導緻相關的記憶體洩露。

相反,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 替換 authenticationTypecredCache.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 中繼承。ServicedComponent 和 System.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 類型是通過引用進行遠端處理的,可序列化的類型是在用戶端程序中複制值并執行代碼。
  • 對象虛拟方法 Equals、GetHashCode 和 MemberwiseClone 等在本地執行。

了解了這些設計中應該注意的地方,就可以選擇使用以下方法釋出由 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 服務。

.NET Remoting 體系結構評估

圖 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 的影響,将是一件充滿樂趣的事情。 

繼續閱讀