Remoting是NET平台下比較成熟高效的分布式技術,我們習慣采用傳統的遠程 調用的方式使用Remoting。在客戶端所在的Application Domain,我們通過 Proxy(Transparent Proxy)遠程地跨Application Domain調用一個方法。當來 自Client端的調用請求通過Proxy到達Server端所在的Application Domain後, Remoting Infrastructure在Server 端激活(Activate)相應的遠程對象(一個 繼承子System.MarshalByRefObject類對象)——這裡僅僅以服務端 激活對象(Server Activated Object——SAO),然後再Server端執行相應的操作後把Result傳遞給Proxy,並最終到達Client。這是一種典型的 Request/Response的調用方式。
我之所以一直比較推崇在.NET平台下使 用Remoting而非XML Web Service是因為我覺得.NET Remoting是一種比較成熟的 分布式技術。它自身提供了XML Web Service很多不具備的特性,其中對雙向通 信的支持就是一個很好的體現。
相對於典型的Request/Response的消息 交換模式(Message Exchange Pattern——MEP),雙向通信實質上 是采用的Duplex的MEP。也就是說,Server端在執行操作的時候,可以回調 (Callback)Client端的操作(這個操作時再Client端的Application Domain中 執行的)。
現在我們來看如何一步一步實現在Remoting環境下的雙向通 信。在下面的Sample中,我們的邏輯是:調用一個數學計算的遠程調用,除了傳 遞相應的操作數之外,我們還傳遞一個對象,這個對象可以在Server端中回調 (Callback) 把運算結果在Client端顯示出來。
Step1:構建整個 Solution的整體構架。
Artech.DuplexRemoting.Contract:Class Library Project,定義遠程對象(Remote Object)和Callback對象的Contract (Interface)。實際上,站在Server端的角度上看,Callback的操作是在 Client端的Application Domain中執行的,所以從本質上講, Callback對象是 Server端的遠程對象。
之所以定義這樣一個Contract Project,其目的 主要有以下幾點:
1.如果沒有把遠程對象的Interface,對已某一個需 要調用這個遠程對象的Client來說,它必須引用遠程對象本身。從安全的角度考 慮,Server向Client過多暴露了操作的實現邏輯。如果我們把遠程操作的 Contract提取出來,Client只要引用這個Interface就可以了。
2.一般 來說,遠程對象的Contract相對時靜態的(static),而業務邏輯的實現則是經 常 變化的。因為Client只需要了解的是遠程對象的Contract,所在無論Server 端對遠程對象的實現作了多大的變動,對不回對Client產生任何影響。
Artech.DuplexRemoting.Remoting:Class Library Project,定義遠程 對象本身。由於遠程對象必須實現上邊定義的Contract。所以需要引用 Artech.DuplexRemoting.Contract。
Artech.DuplexRemoting.Hosting: Console Application Project,以Self-Host的方式Host Remoting。引用 Artech.DuplexRemoting.Remoting。
Artech.DuplexRemoting.Client: Console Application Project,引用Artech.DuplexRemoting.Contract。
Step 2 在Artech.DuplexRemoting.Contract中定義 Contract
IDuplexCalculator.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Artech.DuplexRemoting.Contract
{
public interface IDuplexCalculator
{
void Add(double x, double y, ICalculatorCallback callback);
}
}
ICalculatorCallback.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Artech.DuplexRemoting.Contract
{
public interface ICalculatorCallback
{
void ShowResult (double x, double y, double result);
}
}
Step 3 在Artech.DuplexRemoting.Remoting定義遠程對象
DuplexCalculatorRemoting.cs
using System;
using System.Collections.Generic;
using System.Text;
using Artech.DuplexRemoting.Contract;
namespace Artech.DuplexRemoting.Remoting
{
public class DuplexCalculatorRemoting:MarshalByRefObject, IDuplexCalculator
{
IDuplexCalculator Memberspublic void Add(double x, double y, ICalculatorCallback callback)
{
Console.WriteLine("Invoke the method Add({0}, {1}).",x,y);
double result = x + y;
callback.ShowResult(x,y,result);
}
#endregion
}
}
Step 4 在 Artech.DuplexRemoting.Hosting Host遠程對象
App.config
<configuration>
<system.runtime.remoting>
<application name="Calculator">
<service>
<wellknown mode="SingleCall"
type="Artech.DuplexRemoting.Remoting.DuplexCalculatorRemoting,Art ech.DuplexRemoting.Remoting"
objectUri="DuplexCalculator.soap" />
</service>
<channels>
<channel ref="http" port="8080">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Program.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Artech.DuplexRemoting.Hosting
{
class Program
{
static void Main(string[] args)
{
System.Runtime.Remoting.RemotingConfiguration.Configure ("Artech.DuplexRemoting.Hosting.exe.config",false);
Console.WriteLine("Calculator service has begun to listen ");
Console.Read();
}
}
}
這裡需要特別注意的有以下兩點:
1.在定 義Channel是需要指定一個雙向Channel(Bi-Directional Channel)。系統給我 們定義一一系列的System-Defined Channel用於調用遠程對象。其中有一些只能 提供單向的通信——比如只支持Client到Server的通信,而另一些可 以提供雙向的通信——比如TCP Channel 和Http Channel.
2 .在ServerProvider Section,我們必須設置typeFilterLevel為Full。出於安 全的考量,Remoting提供了兩個反序列化級別(Level)——Low & Full。Low是默認的,如果把typeFilterLevel設為Low,Remoting之會反 序列化Remoting基本功能相關的對象。而設為Full則意味著Remoting會反序列化 所有類型。如果你想知道那些類型是在Low Level下被限制,請參考 http://msdn2.microsoft.com/en-us/library/5dxse167.aspx。
之所以要把typeFilterLevel為Full,是因為我們的遠程調用裡包含一 Callback對象,它實際上是一個繼承System.MarshalByRefObject類對象(這個 的對象將在Artech.DuplexRemoting.Client中定義)。而這個對象是不會再Low Level下被自動反序列化。
<channels>
<channel ref="http" port="8080">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
public interface IDuplexCalculator
{
void Add(double x, double y, ICalculatorCallback callback);
}
Step 4 在 Artech.DuplexRemoting.Client定義Callback對象和調用遠程對象
CalculatorCallbackHandler.cs
using System;
using System.Collections.Generic;
using System.Text;
using Artech.DuplexRemoting.Contract;
namespace Artech.DuplexRemoting.Client
{
public class CalculatorCallbackHandler:MarshalByRefObject, ICalculatorCallback
{
ICalculatorCallback Memberspublic void ShowResult(double x, double y, double result)
{
Console.WriteLine("x + y = {2} where x = {0} and y = {1} ", x, y, result);
}
#endregion
}
}
App.config
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http" port="0">
<clientProviders>
<formatter ref="binary" />
</clientProviders>
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using Artech.DuplexRemoting.Contract;
namespace Artech.DuplexRemoting.Client
{
class Program
{
static void Main(string[] args)
{
System.Runtime.Remoting.RemotingConfiguration.Configure ("Artech.DuplexRemoting.Client.exe.config", false);
InvocateDuplexCalculator ("http://localhost:8080/Calculator/DuplexCalculator.soap");< br /> }
static void InvocateDuplexCalculator(string remoteAddress)
{
IDuplexCalculator proxy = (IDuplexCalculator)Activator.GetObject(typeof(IDuplexCalculator), remoteAddress);
proxy.Add(1,2,new CalculatorCallbackHandler());
Console.Read();
}
}
}
這裡有兩點需特別注意的 :
1.由於Server端時跨Application Domain遠程地調用運行Client Application Domain中的Callback對象(Callback的執行實際是在Client而不在 Server),所以Callback對象應該是一個MarshalByRefObject對象。
2.上面我們以經提及,對於Server端了來說Callback對象實際上是一個遠程對象( 在Callback過程中Client端轉變成Server端,而Server端轉變成Client端)。 Server端需要注冊一些Channel用於Client訪問寄宿在Server端的遠程對象,同 理,Server需要Callback一個寄宿在Client端Application Domain中的Callback 對象,Client端需要注冊相應的Channel
3.和Server端一樣,我們必須 設置typeFilterLevel為Full。
到現在為止我們已經完成了所有的 Program,我們來運行一下。
1.運行 Artech.DuplexRemoting.Hosting
2.運行Artech.DuplexRemoting.Client
將遠程對象Host到IIS中
我們知道,Remoting有兩種Host方式 Self Host和IIS Host,上面我們把Remoting Host到一個Console Application 中; 現在我們把試著把它Host到IIS中。實際上我們要做的工作很簡單。
1.在IIS Manager中添加一個虛擬目錄對應 Artech.DuplexRemoting.Remoting文件夾, 假設此虛擬目錄的Alias為 Artech.DuplexRemoting
2.在Artech.DuplexRemoting.Remoting根目錄 下中(也就是在http://localhost/Artech.DuplexRemoting根目錄下)添加一個 Web.config,並添加類似於Artech.DuplexRemoting.Hosting/App.Config中 的 Remoting Configuration。
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"& gt;
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall"
type="Artech.DuplexRemoting.Remoting.DuplexCalculatorRemoting,Art ech.DuplexRemoting.Remoting"
objectUri="DuplexCalculator.soap" />
</service>
<channels>
<channel ref="http">
<serverProviders>
<provider ref="wsdl" />
<formatter ref="binary" typeFilterLevel="Full" />
</serverProviders>
<clientProviders>
<formatter ref="binary" />
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="Microsoft.Transactions.Bridge, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="SMDiagnostics, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.IdentityModel.Selectors, Version=3.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.Web.RegularExpressions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Messaging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
<add assembly="System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
</system.web>
</configuration>
這樣我們 可以不需要Hosting,就可以運行Client了。
本文配套源碼