.NET Remoting是.NET平臺上允許存在于不同應用程序域中的對象相互知曉對方并進行通訊的基礎設施。調用對象被稱為客戶端,而被調用對象則被稱為服務器或者服務器對象。簡而言之,它就是.NET平臺上實現分布式對象系統的框架。
傳統的方法調用是通過棧實現,調用方法前將this指針以及方法參數壓入線程棧中,線程執行方法時將棧中的參數取出作為本地變量,經過一番計算后,將方法的返回結果壓入棧中。這樣我們就完成了一次方法調用。如下圖所示:

基于棧的方法調用在同一個應用程序域中很容易實現,但是如果要調用的方法所屬的對象位于另一個應用程序域或另一個進程甚至是另一個機器,又當如何?應用程序域之間是無法共享同一個線程棧的,此時我們將轉而使用另一種方法調用機制——基于消息的方法調用機制。在客戶端通過代理對象將原先基于棧的方法調用信息(定位遠程對象的信息、方法名、方法參數等)封裝到一個消息對象中,再根據需要將這些消息對象轉化成某個格式的數據流發送到遠程對象所在的的應用程序域中。當經過格式化的消息到達服務器后,首先從中還原出消息對象,之后在遠程對象所在的的應用程序域中構建出相應方法調用棧,此時就可以按照傳統的基于棧的方法調用機制完成方法的調用,而方法返回結果的過程則按照之前的方法反向重復一遍。如下圖所示:

在基于消息的遠程方法調用中主要有以下幾個重要角色:
Client Proxy: 負責在客戶端處理基于棧的參數傳遞模式和基于消息的參數傳遞模式之間的轉換。
Invoker:與Client Proxy的功能相反。
Requestor: 負責將消息對象轉換成可在網絡上傳輸的數據流,并將其發送到服務器。
Marshaller: 負責消息對象的序列化與反序列化。
Client Request Handle:負責以數據流的格式發送客戶端的請求消息。
Server Request Handel:負責接收來自客戶端的請求消息。
那么在.NET Remoting框架下,這些重要角色又各自對應了哪些對象呢?下圖是一個Remoting框架的示意圖:

圖1
從中我們可以看到客戶端的Transparent Proxy與服務器端的StackBuilderSink分別扮演了Client Proxy與Invoker的角色。Remoting依靠這兩個對象實現了基于棧的方法調用與基于消息的方法調用的轉換,并且這一過程對于開發者是完全隱藏的。
Marshaller的角色由Formmatter Sink完成,在Remoting中默認提供了兩種Fommatter:一個實現了消息對象與二進制流的相互轉換,另一個實現了消息對象與Soap數據包的相互轉換,而支持Soap格式則說明Remoting具有實現Web Service技術的可能。
Client Request Handle與Server Request Handel都是.NET中實現網絡底層通訊的對象,如HttpWebRequest、HttpServerSocketHandler等。在Remoting中我們并不直接接觸這些對象,而是通過Channel對它們進行管理。在框架中默認提供了三種Channel:HttpChannel、TcpChannel與IpcChannel。但是在上面這副圖中,我們并沒有看到Channel對象,那么Channel又是如何影響網絡底層的通訊協議的呢?
其實在上面這幅圖中,真正能對通訊時所采用的網絡協議產生影響的元素是Transport Sink,對應不同的協議Remoting框架中提供了三種共計六個Transport Sink:HttpClientTransportSink、HttpServerTransportSink與TcpClientTransportSink、TcpServerTransportSink以及IpcClientTransportSink、IpcServerTransportSink,它們分別放置在客戶端與服務器端。既然Transport Sink才是通訊協議的決定元素,那么Channel肯定與它有著某種聯系,讓我們先暫時擱置此話題,留待后面進一步介紹。
觀察上面這副圖,我們可以發現其中包含了一系列的Sink,而所謂的Sink就是一個信息接收器,它接受一系列的輸入信息,為了達到某種目的對這些信息做一些處理,然后將處理后信息再次輸出到另一個Sink中,這樣一個個的Sink串聯起來就構成了一個Pipeline(管道)。Pipeline模式在分布式框架中經常可以看到,應用該模式可以使框架具有良好的靈活性。當我們需要構建一個系統用于處理并轉換一串輸入數據時,如果通過一個大的組件按部就班的來實現此功能,那么一旦需求發生變化,比如其中的兩個處理步驟需要調換次序,或者需要加入或減去某些處理,系統將很難適應,甚至需要重寫。而Pipeline模式則將一個個的處理模塊相互分離,各自獨立,然后按照需要將它們串聯起來即可,此時前者的輸出就會作為后者的輸入。此時,每個處理模塊都可以獲得最大限度的復用。當需求發生變化時,我們只需重新組織各個處理模塊的鏈接順序,或者刪除或加入新的處理模塊即可。在這些處理模塊(Sink)中最重要的兩類是Formatter Sink與Transport Sink,也就是圖1中的紅色部分,當我們需要通過網絡訪問遠程對象時,首先要將消息轉化為可在網絡上傳播的數據流,然后需要通過特定的網絡協議完成數據流的發送與接收,這正是這兩類Sink所負責的功能。雖然它們本身也可以被自定義的Sink所替換,不過Remoting中提供的現有實現已經可以滿足絕大多數的應用。
那么.NET Remoting中又是如何創建這個Pipeline的呢?以HttpChannel為例,在創建客戶端的Pipeline的時候,會調用它的CreateMessageSink方法,此時又會進一步調用HttpClientChannel的構造函數,在構造函數中調用了一個名為SetupChannel()的私有方法,看一下它的實現:
private void SetupChannel()
{
if (this._sinkProvider != null)
{
CoreChannel.AppendProviderToClientProviderChain(this._sinkProvider,
new HttpClientTransportSinkProvider(this._timeout));
}
else
{
this._sinkProvider = this.CreateDefaultClientProviderChain();
}
}
再看看在沒有提供自定義SinkProvider的默認情況下CreateDefaultClientProviderChain()會創建出哪些Sink
private IClientChannelSinkProvider CreateDefaultClientProviderChain()
{
IClientChannelSinkProvider provider = new SoapClientFormatterSinkProvider();
provider.Next = new HttpClientTransportSinkProvider(this._timeout);
return provider;
}
其中包含了SoapClientFormatterSinkProvider與HttpClientTransportSinkProvider,自然地,到了創建Pipeline的時候,它們會分別創建出SoapClientFormatterSink與HttpClientTransportSink,前者用于實現消息對象的Soap格式化,而后者則表示將用Http協議來實現消息的通訊。這樣我們就明白了為什么使用HttpChannel就會采用Http協議作為網絡通訊協議,并且消息將以SOAP格式傳遞。但是仔細觀察SetupChannel方法中的下段代碼,我們可以發現通過提供自定義的SinkProvider,我們可以改變消息的編碼格式,因為此時只創建了一個HttpClientTransportSinkProvider,并沒有定義FormatterSinkProvider,而FormatterSinkProvider完全可以在自定義的SinkProvider中自由設定。
if (this._sinkProvider != null)
{
CoreChannel.AppendProviderToClientProviderChain(this._sinkProvider,
new HttpClientTransportSinkProvider(this._timeout));
}
那么我們又如何在.NET Remoting中定義自己的SinkProvider并讓它發揮作用呢?下面這幅圖演示如果使用自定義SinkProvider對Pipeline進行定制。
圖2
首先在配置文件中引用自定義的ChannelSinkProvider的類名及其所在程序集,然后編寫自定義的ChannelSinkProvider的具體實現,也就是加入你需要的ChannelSink,最后在自定義的ChannelSink中實現具體的處理操作。這樣我們向Pipeline中成功地添加了自定義處理模塊。這里需要提醒大家注意,接收方的Pipeline信道與發送方Pipeline之間存在著一個根本的差異。發送方Pipeline為每個遠程對象的真實代理創建一個接收器(Sink)鏈。接收方信道在其創建之時創建了接收器(Sink)鏈,這條鏈將為所有通過這個接收方Pipeline進行轉送的調用所使用,不論與這個調用相關聯的對象是哪個。下圖展示了這個差異:

圖3
細心的讀者可能已經注意到之前我們定制的是ChannelSink,為什么要加上一個Channel呢?讓我們回過頭去再看看圖1,其中有兩個綠色的Sink,他們都是Remoting中可選的組件,也是我們對Pipeline進行擴展的地方。你可以在這兩個地方加入自定義的Sink,從而對流經Pipeline的數據做某些需要的處理,前者需實現IMessageSink接口,后者需要實現IXXXChannelSink接口,那么它們又有何不同呢?注意看圖1,我們會發現其中隔了一個Formmatter Sink,而Formmatter Sink的作用就是將原來的.NET消息變成可在網絡上傳播的數據流(可以是Binary或Soap格式),這也就表明前者的輸入是消息對象,而后者的輸入是數據流(Stream)。這也就決定了他們各自所能實現的擴展功能是不同的。比如,操作消息對象,我們可以把遠程方法的參數變一變(比如從英文翻成中文),而操作數據流,我們可以實現數據流的加密或壓縮之類功能。如何定制新的ChannelSink并將其加入到Pipeline中已經在圖2中演示過了,那么MessageSink呢?請關注下節內容。
參考資料:
《Advanced Remoting》
《Pattern oriented software architecture vol1》
《Remoting Patterns》
《Pratical .NET2 and C#2》
---
做個小廣告,最近剛剛與博客園中的一些朋友合作翻譯完了《Pratical .NET2 and C#2》一書,這本針對初級到中級開發人員的洋洋900頁的書,對.NET 2.0的討論不光詳盡,而且深入淺出,中間還穿插了很多C#例碼。可以說這是市面上介紹.NET 2.0最全面且非常有深度的一本書,其中涉及各個方面的話題,如CLR、類型系統、安全性、XML、Winform、ASP.NET、Remoting、進程線程與同步、事務、Webservices等等,每個話題的介紹都非常深入并且與.NET2.0緊密關聯,不像以往的書僅僅是點到為止,在翻譯這本書的過程中我也學到很多新的知識,而且在翻譯過程中我和腦袋都在找工作,我們把它戲稱為《.NET面試寶典》。盡管由于涉及話題較多,有些章節敘述的不是那么連貫,但是這本書不乏一些精彩章節,比如介紹CLR、進程線程與同步、Remoting以及反射的這些章節就非常精彩,很多內容在其他書中都不曾有過介紹。等翻譯完全定稿后,我們會在博客園公布一些樣章,到時歡迎大家瀏覽。總之,這是一本相當不錯的書,并且在Amazon上評價也很高。
浙公網安備 33010602011771號